文章目录
647.回文子串
题目:
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
示例 1 :
输入 :s = "abc"
输出 :3
解释:三个回文子串: "a", "b", "c"
示例 2 :
输入 :s = "aaa"
输出 :6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
1 <= s.length <= 1000
s 由小写英文字母组成
思路:
题目要找:字符串里所有连续的、正读反读一样的子串数量。
回文串一定有中心点,只有两种情况:
- 奇数长度 :中心点是 1 个字符(如
a、aba) - 偶数长度 :中心点是 2 个相邻字符(如
aa、abba)
中心扩展法:
- 遍历字符串的
每一个位置,把它当作奇数回文中心 - 遍历
每一对相邻位置,把它们当作偶数回文中心 - 从中心往左右两边扩散,只要左右字符相等,就是一个回文
- 每找到一个回文,总数 +1
代码实现(Go):
go
package main
import "fmt"
// expand 函数作用:
// 从给定的左指针 l、右指针 r 开始,向左右两边扩展
// 统计以 l、r 为中心的回文子串个数
func expand(s string, l, r int) int {
// 记录本次扩展能找到的回文数量
count := 0
// 获取字符串长度
n := len(s)
// 循环条件:
// 1. 左指针不越界(l >= 0)
// 2. 右指针不越界(r < n)
// 3. 左右两边字符相等(s[l] == s[r])
// 满足 → 说明是回文
for l >= 0 && r < n && s[l] == s[r] {
count++ // 找到一个回文,计数 +1
l-- // 左指针继续向左扩展
r++ // 右指针继续向右扩展
}
// 返回本次中心扩展找到的回文总数
return count
}
// countSubstrings 作用:统计字符串中所有回文子串的数量
func countSubstrings(s string) int {
// 最终结果:所有回文子串的总数
res := 0
n := len(s)
// 遍历字符串的每一个位置,作为回文的中心
for i := 0; i < n; i++ {
// 情况 1:奇数长度回文,中心是 1 个字符 i
res += expand(s, i, i)
// 情况 2:偶数长度回文,中心是 2 个字符 i 和 i+1
res += expand(s, i, i+1)
}
// 返回最终统计的总数量
return res
}
func main() {
// 示例 1:输入 abc → 输出 3(a、b、c)
fmt.Println(countSubstrings("abc"))
// 示例 2:输入 aaa → 输出 6
fmt.Println(countSubstrings("aaa"))
}
-
时间复杂度 :O(n²)
- 外层遍历所有中心:
O(n) - 每个中心最多向左右扩展
n次:O(n) - 总复杂度:
n * n = n²
- 外层遍历所有中心:
-
空间复杂度 :O(1)
- 只使用了固定几个变量(
res、left、right、n) - 没有开辟额外的数组/切片,和输入长度无关
- 只使用了固定几个变量(
5.最长回文子串
题目:
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1 :
输入 :s = "babad"
输出 :"bab"
解释:"aba" 同样是符合题意的答案。
示例 2 :
输入 :s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
思路:
所有回文串,都一定有一个中心点。
回文只有两种:
- 奇数长度 :中心点是 1 个字符
如:a、aba、abcba - 偶数长度 :中心点是 2 个相邻字符
如:aa、abba、acc a
- 遍历字符串的每一个位置。
- 对每个位置,做两种尝试 :
- 把它当作 奇数长度回文中心,向左右扩展
- 把它和下一个字符当作 偶数长度回文中心,向左右扩展
- 每次扩展时,只要左右字符相等,就是回文。
- 全程记录最长回文的起始位置和长度。
- 最后根据记录,截取出最长回文子串返回。
代码实现(Go):
go
package main
import "fmt"
// expand 从 l、r 中心向两边扩展,返回【最长回文的长度】
func expand(s string, l, r int) int {
n := len(s)
// 不越界且字符相等,就继续扩展
for l >= 0 && r < n && s[l] == s[r] {
l--
r++
}
// 循环结束时,最后一次有效回文长度 = r - l - 1
return r - l - 1
}
// longestPalindrome 寻找最长回文子串
func longestPalindrome(s string) string {
n := len(s)
if n == 0 {
return ""
}
// 记录最长回文的起始位置和长度
// 最短回文长度是1(单个字符)
start := 0
maxLength := 1
// 遍历每个可能的回文中心
for i := 0; i < n; i++ {
// 奇数长度回文
len1 := expand(s, i, i)
// 偶数长度回文
len2 := expand(s, i, i+1)
// 取两种情况中更长的那个
maxLen := max(len1, len2)
// 如果当前长度 > 记录的最长长度,则更新
if maxLen > maxLength {
maxLength = maxLen
start = i - (maxLen-1)/2
}
}
// 截取并返回最长回文
return s[start : start+maxLength]
}
func main() {
fmt.Println(longestPalindrome("babad")) // 输出 bab / aba
fmt.Println(longestPalindrome("cbbd")) // 输出 bb
}
- 时间复杂度 :O(n²)
遍历每个中心 O(n),每个中心最多扩展 O(n) - 空间复杂度 :O (1)
只使用了几个变量,没有开辟额外空间