回文子串 图解题解
这道题到底在问什么
- 输入
- s = "abacaba"
- 输出
- 12
先想最直接的笨办法
核心一句话:枚举每个中心,向两边扩,每对称成功一次就是一个新回文,计数加一。下面每一帧都在套它。(动画第 3 步)
最优解:为什么这么做
一句话答案:LeetCode 647 回文子串的主流解法是中心扩展计数:把 n 个字符与 n-1 条缝隙共 2n-1 个位置都当作对称中心向两边扩,每成功对称一层就恰好对应一个新的回文子串,计数加一。每个回文有且只有一个中心,所以不重不漏,时间 O(n²)、空间 O(1)。
回文子串这题在数什么
给字符串 s,统计其中回文子串的总个数。两条规则决定了计数口径:子串必须连续;只要起止位置不同就算不同的子串,哪怕内容一模一样。比如 "aa" 里两个 "a" 要算两个。s = "abacaba" 的答案是 12——单字符 7 个,再加 "aba"(两处)、"aca"、"bacab"、"abacaba"。
为什么不逐个子串验证回文
暴力路线是枚举全部 O(n²) 个子串、每个花 O(n) 验证,总共 O(n³)。真正的浪费在于验证之间零复用:"bacab" 从两端往里比,比到一半其实撞上了 "aca" 已经回文的事实,却当作不知道重比一遍。
把视角从「子串」换到「中心」就能复用这份信息:任何回文都关于自己的中心对称。固定一个中心向外扩,里层对称成立时只需再比最外侧两个字符,就能判定大一圈的子串是不是回文——里层的结论被直接继承,每往外一层只花 O(1)。
为什么每扩一层计数就加一
中心扩展和计数能严丝合缝地咬合,靠的是一个对应关系:以某中心向外扩张,第一层对称成功对应以它为中心的最短回文,再扩一层成功就对应大一圈的另一个回文。也就是说「对称成功一次」和「一个回文子串」一一对应,所以 while 循环里每比中一次就 ans 加一。
不重不漏也由中心保证:每个回文子串的中心是唯一确定的(奇数长度中心在字符上,偶数长度中心在缝隙上),它只会在枚举到自己中心时被数到一次,不同位置的相同内容因为中心不同而分别计数,正合题意。
2n-1 个中心怎么用一层循环枚举
中心必须包含 n 个字符和 n-1 条缝隙,共 2n-1 个——漏掉缝隙中心,所有偶数长度的回文(如 "bb")就永远数不到。参考代码用一个循环变量 c 从 0 走到 2n-2 统一编码两类中心:l = c // 2,r = c // 2 + c % 2。c 为偶数时 l 与 r 重合,是字符中心;c 为奇数时 r 比 l 大一,正好落在缝隙两侧。这样一层循环替代了奇偶两套代码。
复杂度多少,还有什么别的解法
中心 2n-1 个,每个最多向外扩约 n/2 层,时间 O(n²);只用两个指针和一个计数器,空间 O(1)。写扩展循环时越界判断要放在字符比较之前——while 条件先查 l >= 0 且 r < n,顺序反了会越界访问。
同类解法还有区间动态规划:dp[i][j] 表示 s[i..j] 是否回文,由 s[i] == s[j] 且 dp[i+1][j-1] 推出,时间同为 O(n²) 但要 O(n²) 空间。追求线性时间可用 Manacher 马拉车算法,靠对称性复用回文半径做到 O(n),实现复杂,通常面试点到为止。
▶ 动画逐步走查(共 35 步)——想跟着动画一帧帧对照就展开
- 3核心一句话:枚举每个中心,向两边扩,每对称成功一次就是一个新回文,计数加一。下面每一帧都在套它。
- 4以单字符 'a'(下标 0)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 5左右指针落在同一个字符 'a' 上,它自己就是回文,计数变成 1,再往外扩一格。
- 6以下标 0、1 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 7左 s[0]='a' 与右 s[1]='b' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 8以单字符 'b'(下标 1)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 9左右指针落在同一个字符 'b' 上,它自己就是回文,计数变成 2,再往外扩一格。
- 10左 s[0]='a' 与右 s[2]='a' 相等,"aba" 是回文,计数加到 3。左指针左移、右指针右移,继续往外扩。
- 11以下标 1、2 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 12左 s[1]='b' 与右 s[2]='a' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 13以单字符 'a'(下标 2)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 14左右指针落在同一个字符 'a' 上,它自己就是回文,计数变成 4,再往外扩一格。
- 15左 s[1]='b' 与右 s[3]='c' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 16以下标 2、3 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 17左 s[2]='a' 与右 s[3]='c' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 18以单字符 'c'(下标 3)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 19左右指针落在同一个字符 'c' 上,它自己就是回文,计数变成 5,再往外扩一格。
- 20左 s[2]='a' 与右 s[4]='a' 相等,"aca" 是回文,计数加到 6。左指针左移、右指针右移,继续往外扩。
- 21左 s[1]='b' 与右 s[5]='b' 相等,"bacab" 是回文,计数加到 7。左指针左移、右指针右移,继续往外扩。
- 22左 s[0]='a' 与右 s[6]='a' 相等,"abacaba" 是回文,计数加到 8。左指针左移、右指针右移,继续往外扩。
- 23以下标 3、4 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 24左 s[3]='c' 与右 s[4]='a' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 25以单字符 'a'(下标 4)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 26左右指针落在同一个字符 'a' 上,它自己就是回文,计数变成 9,再往外扩一格。
- 27左 s[3]='c' 与右 s[5]='b' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 28以下标 4、5 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 29左 s[4]='a' 与右 s[5]='b' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 30以单字符 'b'(下标 5)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 31左右指针落在同一个字符 'b' 上,它自己就是回文,计数变成 10,再往外扩一格。
- 32左 s[4]='a' 与右 s[6]='a' 相等,"aba" 是回文,计数加到 11。左指针左移、右指针右移,继续往外扩。
- 33以下标 5、6 之间的缝隙为中心。偶数长度回文从这条缝隙往外扩。从这里开始向左右两边扩展。
- 34左 s[5]='b' 与右 s[6]='a' 不相等,无法再对称,这个中心扩完了,去枚举下一个中心。
- 35以单字符 'a'(下标 6)为中心。它本身就是一个回文(长度 1)。从这里开始向左右两边扩展。
- 36左右指针落在同一个字符 'a' 上,它自己就是回文,计数变成 12,再往外扩一格。
- 3713 个中心全部扩展完毕,把每次对称成功累加起来,回文子串一共有 12 个,返回 12。
⚠️ 容易写错的地方
✗ 错:只枚举单字符中心
✓ 对:同时枚举字符中心和缝隙中心,共 2n-1 个
漏了缝隙中心就数不到偶数长度的回文(如 "bb")
✗ 错:把内容相同的回文当成一个
✓ 对:位置不同就分别计数
"aa" 里两个 "a" 是两个不同回文子串
✗ 错:扩展时忘判越界
✓ 对:while 条件先查 l>=0 且 r<n
指针扩出边界会越界访问
完整代码(Python / C++ / Java)
Python
def countSubstrings(s: str) -> int:
n = len(s)
ans = 0
for c in range(2 * n - 1):
l, r = c // 2, c // 2 + c % 2
while l >= 0 and r < n and s[l] == s[r]:
ans += 1
l -= 1
r += 1
return ansC++
int countSubstrings(string s) {
int n = s.size(), ans = 0;
for (int c = 0; c < 2 * n - 1; c++) {
int l = c / 2, r = c / 2 + c % 2;
while (l >= 0 && r < n && s[l] == s[r]) {
ans++;
l--; r++;
}
}
return ans;
}Java
public int countSubstrings(String s) {
int n = s.length(), ans = 0;
for (int c = 0; c < 2 * n - 1; c++) {
int l = c / 2, r = c / 2 + c % 2;
while (l >= 0 && r < n
&& s.charAt(l) == s.charAt(r)) {
ans++;
l--; r++;
}
}
return ans;
}复杂度
时间
O(n²)
2n-1 个中心,每个最多扩 n/2 次
空间
O(1)
只用计数器和两个指针,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 回文子串 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
除了中心扩展还有别的解法吗?+
有动态规划:dp[i][j] 表示子串 s[i..j] 是否回文,由 s[i]==s[j] 且 dp[i+1][j-1] 推出,同样 O(n²) 时间但要 O(n²) 空间。还有 Manacher 算法能做到 O(n)。
中心扩展为什么是 O(n²)?+
中心有 2n-1 个,每个中心向外扩展最多 O(n) 次,相乘就是 O(n²)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 回文子串 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。