给出golang 最长回文子串算法题的完整版 代码
时间: 2025-04-09 18:42:42 浏览: 47
<think>嗯,用户让我用中文介绍一个Golang的最长回文子串算法题的完整代码。首先,我需要回忆一下这个问题。最长回文子串,我记得常见的解法有动态规划和中心扩展法。动态规划的时间复杂度是O(n²),空间复杂度也是O(n²),而中心扩展法的时间虽然也是O(n²),但空间是O(1),可能更高效一些。所以可能用户需要的是中心扩展法的实现?
用户要的是完整的代码,所以不能只讲思路,必须给出完整的可运行的代码。然后可能需要解释代码的结构和关键部分。比如,如何在每个可能的中心点向两边扩展,检查是否是回文。
然后,我要考虑Golang的实现方式。比如,主函数可能是longestPalindrome,输入是字符串,返回字符串。然后需要处理空字符串的情况吗?比如输入长度为0或者1的话,直接返回。
接下来,中心扩展的函数部分。每个中心点可能是一个字符或者两个字符之间,比如对于奇数长度和偶数长度的回文。所以可能需要遍历每个可能的中心点,然后调用expandAroundCenter函数两次,分别处理单字符中心和双字符中心的情况。
然后,每次扩展的时候,比较左右字符是否相等,直到不相等为止,记录此时的左右索引,然后计算长度,看是否比当前最长的大。如果是,更新最长回文的起始位置和长度。
最后,根据起始位置和长度,用切片操作返回子串。
另外,代码的注释是否必要?用户可能需要注释来解释每一步的作用,但如果是完整版代码的话,可能用户希望代码本身足够清晰,或者注释可以帮助理解。
然后测试一下示例情况,比如输入"babad",输出应该是"bab"或者"aba",但算法会返回第一个找到的最长的。或者输入"cbbd",应该返回"bb"。
现在,代码的大致结构应该是这样的:
定义一个函数longestPalindrome,参数s string,返回值string。
初始化start和maxLength变量,初始值分别为0和0。
遍历字符串中的每个字符的位置i,作为中心点:
对于每个i,计算expandAroundCenter(s, i, i)得到长度len1,处理奇数长度的回文。
再计算expandAroundCenter(s, i, i+1)得到长度len2,处理偶数长度的回文。
比较len1和len2中的较大者,如果超过当前maxLength,则更新maxLength,并计算对应的start位置。
最后返回s[start:start+maxLength]
然后,expandAroundCenter函数的实现是,给定左右起点,向两边扩展,直到字符不相等。返回此时回文的长度,或者左右的位置差减一?
或者,该函数返回回文的长度,比如每次循环当左右字符相等时,左减一,右加一,直到超出边界或者不相等。最后回文的长度是right - left - 1,或者类似的?
比如,初始left和right是给定的,比如对于奇数情况,left和right都是i,对于偶数情况,left是i,right是i+1。然后循环条件为left >=0 && right < len(s) && s[left] == s[right],然后left--,right++。循环结束后,此时的最大回文长度是right - left -1。或者需要计算正确的长度。
比如,当退出循环时,left和right的位置是不满足条件的,所以实际回文的范围是left+1到right-1。因此,回文的长度是 (right -1) - (left+1) +1 = right - left -1。所以,当返回这个长度之后,需要比较哪一个更大。
然后,比较当前的长度是否大于maxLength,如果是的话,需要计算start的位置。比如,对于奇数的情况,扩展后的长度是len1,此时起始位置是i - (len1-1)/2,长度是 len1。同理,偶数的情况,起始位置是i - (len2)/2 + 1?
或者,可能在expand函数中返回最大的回文长度后,如何确定起始位置?
或者,更好的办法是在expand函数中返回回文的最左和最右的位置,或者在主函数中根据扩展后的结果来更新start和maxLength。或者,直接每次计算可能的start和长度,然后更新。
比如,每次扩展后,计算可能的长度,比如当前扩展后的长度是 right - left -1。然后,如果这个长度大于maxLength,那么maxLength等于这个长度,start等于left+1,因为此时回文的起始位置是left+1,结束位置是right-1,所以长度是 (right-1)-(left+1)+1 = right-left-1,即maxLength。然后,子串就是s[start : start+maxLength]。
所以,在循环中,对于每个i,进行两次expand,然后比较两次的结果,取最大的那个,然后看是否超过全局最大值,然后更新start和maxLength。
或者,是否在expand函数中直接处理这些计算?或者让主函数来处理?
可能更高效的方式是,每次expand返回最大的扩展后的长度,然后根据这个长度计算起始位置。
例如,当处理奇数的扩展时,得到长度len1,此时起始位置是i - len1/2(因为len1是奇数,比如长度3,i是中间的位置,那么起始位置是i-1,结束是i+1)。而偶数的扩展得到len2,起始位置是i - (len2/2 -1),比如长度2,i是左边那个的位置,起始位置是i -0,i+1 -1 =i+0。
但可能更好的方式是,当expand函数返回长度后,起始位置可以通过i - (length-1)/2,而结束位置是i + length/2。这样,无论奇偶都可以适用?
比如,假设len是扩展后的长度,比如对于奇数情况,假设len是3,那么起始位置是i -1,结束是i+1。而如果是偶数情况,假设长度是2,那么起始位置是i -0(因为 (2-1)/2=0.5,取整的话可能有问题),所以可能需要用不同的方式计算。
或者,在扩展之后,left和right已经被扩展到了不符合条件的点,所以回文实际的左右是left+1和right-1。因此,长度是 right-1 - (left+1) +1 = right - left -1.
此时,当完成扩展之后,得到left和 right,那么此时回文的起始位置是 left+1,长度是 right - left -1。然后,比较这个长度是否大于当前maxLength,如果是的话,更新start和maxLength。
所以,可能应该让expand函数返回最大的扩展后的left和 right,或者返回长度?
或者,直接在循环中处理,每次扩展后,计算可能的长度,并更新最大值和起始位置。
所以,修改后的思路:
对于每个i:
左1,右1 = expand(s, i, i) → 得到的是扩展后的左右,但此时实际回文是左1+1到右1-1,长度是 (right1 - left1 -1)
左2,右2 = expand(s, i, i+1) → 同理,回文是左2+1到右2-1,长度是 (right2 - left2 -1)
然后比较这两个的长度,如果哪个更大,就看看是否超过当前的maxLength,如果超过,则更新maxLength,并记录对应的起始位置。
比如,假设最大的长度是L,对应的起始位置是 left+1,结束是 right-1,所以起始索引是 left+1,长度是 L = right - left -1。所以子串是 s[left+1 : right](因为Go的切片是左闭右开的)。
所以,在expand函数中,我们只需要进行扩展,返回最终的left和 right的值。然后主函数中根据这两个值计算长度和起始位置。
所以,代码的大致结构可能是:
func longestPalindrome(s string) string {
if len(s) < 1 {
return ""
}
start, end := 0, 0
for i := 0; i < len(s); i++ {
left1, right1 := expand(s, i, i)
left2, right2 := expand(s, i, i+1)
if right1 - left1 > end - start {
start, end = left1, right1
}
if right2 - left2 > end - start {
start, end = left2, right2
}
}
return s[start:end+1] // 因为end是right-1,切片需要到end+1?
}
func expand(s string, left, right int) (int, int) {
for left >=0 && right < len(s) && s[left] == s[right] {
left--
right++
}
return left+1, right-1
}
等等,或者需要再仔细检查。
比如,在expand函数中,当跳出循环时,left和 right已经是不满足条件的位置,所以正确的回文起始是left+1,结束是right-1。所以,回文的长度是 (right-1) - (left+1) +1 = right - left -1。而start和 end的索引是left+1和 right-1。所以,当比较长度时,可以用 (right-1 - (left+1) +1) = right - left -1。而当前保存的start和 end是之前最大的情况的start和 end。所以,如果新的长度是 (right- left -1) > (end - start),则更新。
或者,在代码中,直接比较新的长度是否更大,如果是,就更新start和 end。例如,每次扩展之后,计算可能的长度,然后比较。
或者,在expand函数返回left+1和 right-1,这样主函数中直接计算长度是 (right-1) - (left+1) +1 = right - left -1。例如:
假设expand返回新的left和 right是有效的范围,例如,left和 right是回文的最左和最右索引。所以,长度是 right - left +1。
比如,在expand函数中,返回的是 left+1和 right-1,这样这两个之间的子串是回文的,并且包含这两个端点。例如,初始传入i, i的话,如果s[i]是字符,则left和 right在循环中会扩展,直到无法继续。所以,例如,对于单字符的情况,初始时left=i, right=i,进入循环的条件是s[left]==s[right],即true。然后left--变成i-1,right++变成i+1。如果此时i-1或者i+1越界,或者字符不等,循环结束。返回的left+1是i-1+1 =i,right-1是 i+1 -1 =i。所以,返回的left和 right是i和i,对应的子串是s[i:i+1],即正确。例如,当i=0,字符串是"a",那么返回left=0, right=0,子串长度1。
那么,主函数中的处理是,当调用expand返回left和 right时,计算长度是 right - left +1。例如,对于上述例子,right - left +1 =0-0+1=1。
所以,正确的逻辑应该是:
在expand函数中,返回的是回文的最左和最右的索引。所以,长度是 right - left +1。
这样,主函数中比较各个可能的长度,记录最大的情况下的start和 end。
所以,修改后的代码:
func longestPalindrome(s string) string {
if len(s) == 0 {
return ""
}
start, end := 0, 0
for i := 0; i < len(s); i++ {
// 处理奇数长度
left1, right1 := expand(s, i, i)
// 处理偶数长度
left2, right2 := expand(s, i, i+1)
// 比较两种情况的长度
if right1 - left1 > end - start {
start, end = left1, right1
}
if right2 - left2 > end - start {
start, end = left2, right2
}
}
return s[start:end+1]
}
func expand(s string, left, right int) (int, int) {
// 当左右在边界内且字符相等时,扩展
for left >=0 && right < len(s) && s[left] == s[right] {
left--
right++
}
// 返回有效的最左和右
return left+1, right-1
}
这里,每次调用expand之后,返回的left+1和 right-1是回文的最左和最右索引。例如,对于单字符的情况,初始i=0,调用expand(s,0,0),循环条件满足,left减到-1,right增到1。退出循环,返回0,0。此时长度为1,对应的子串是s[0:1],即正确。
当处理偶数情况时,例如i=0,i+1=1。如果s[0]==s[1],则left减到-1,right增到2。返回0和1,此时子串是s[0:2],即两个字符。例如"aa",得到正确的回文。
这样,主函数中的start和 end是保存最大回文的左右索引,最后返回s[start:end+1],因为切片是左闭右开的。
需要测试几个案例:
案例1:输入"babad",可能的回文是"bab"或者"aba"。假设代码会找到第一个最长的。例如,当i=1时,奇数扩展得到left=0,right=2,所以子串长度3。此时start=0, end=2。后续其他i可能无法找到更长的,所以最终返回s[0:3]即"bab"。
案例2:输入"cbbd",调用i=1时,偶数扩展,得到left=1, right=2。子串是"bb",长度2。此时start=1, end=2,返回s[1:3]即"bb"。
这样,代码应该能正确处理这些情况。
综上所述,完整的Golang代码应该如上面所述。在代码中,expand函数负责从中心扩展,并返回有效的最左和最右索引,主函数遍历每个可能的中心点,比较奇数长度和偶数长度的回文,保存最大的情况,最后返回对应的子串。</think>以下是使用中心扩展法实现的Golang最长回文子串完整代码:
```go
func longestPalindrome(s string) string {
if len(s) == 0 {
return ""
}
start, end := 0, 0
for i := 0; i < len(s); i++ {
// 处理奇数长度回文(如 "aba")
l1, r1 := expandAroundCenter(s, i, i)
// 处理偶数长度回文(如 "abba")
l2, r2 := expandAroundCenter(s, i, i+1)
// 更新最大回文范围
if r1 - l1 > end - start {
start, end = l1, r1
}
if r2 - l2 > end - start {
start, end = l2, r2
}
}
return s[start : end+1]
}
// 从中心向两边扩展,返回回文的最左/右边界
func expandAroundCenter(s string, left, right int) (int, int) {
for left >= 0 && right < len(s) && s[left] == s[right] {
left--
right++
}
// 回退到最后一次满足条件的边界
return left + 1, right - 1
}
```
### 算法说明:
1. **中心扩展思想**
遍历每个可能的回文中心(每个字符和每两个字符中间的位置),向两侧扩展检测回文。
2. **双中心处理**
- 奇数长度:以单个字符为中心(如 "aba" 以 'b' 为中心)
- 偶数长度:以两个字符中间为中心(如 "abba" 以中间两个 'b' 为中心)
3. **复杂度分析**
- 时间复杂度:O(n²),遍历每个中心点 O(n),每个中心扩展 O(n)
- 空间复杂度:O(1),仅使用常数级额外空间
### 使用示例:
```go
fmt.Println(longestPalindrome("babad")) // 输出 "bab" 或 "aba"
fmt.Println(longestPalindrome("cbbd")) // 输出 "bb"
fmt.Println(longestPalindrome("a")) // 输出 "a"
```
### 代码特点:
- 处理空字符串的边界情况
- 统一处理奇偶两种回文类型
- 直接返回字符串切片,避免额外内存分配
- 清晰标记回文中心扩展过程
该实现是解决最长回文子串问题的经典方法,在时间和空间复杂度之间取得了较好平衡。
阅读全文
相关推荐



















