LeetCode5最长回文子串
回文串起始位置公式 start = i - (maxLen-1)/2 详解
1. 问题背景
在最长回文子串算法中,我们使用中心扩展法找到回文串后,需要确定其在原字符串中的起始位置。已知:
i
:扩展中心位置maxLen
:找到的最长回文串长度
核心问题:如何根据中心位置和长度,推导出起始位置公式 start = i - (maxLen-1)/2
?
2. 关键变量含义详解
2.1 变量定义
i
:当前扩展的中心位置索引- 对于奇数长度回文串:
i
就是回文串的中心字符位置 - 对于偶数长度回文串:
i
是左侧中心位置(两个中心字符中较左的那个)
- 对于奇数长度回文串:
maxLen
:当前找到的最长回文串的长度start
:最长回文串在原字符串中的起始位置exp(s, l, r)
:中心扩展函数,返回以(l,r)
为中心的最长回文串长度
2.2 扩展函数解析
int exp(String s, int l, int r) {int n = s.length();while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {--l; // 向左扩展++r; // 向右扩展}return r - l - 1; // 返回回文串长度
}
为什么返回 r - l - 1
?
- 循环结束时,
s[l] ≠ s[r]
或越界 - 此时
l
和r
指向回文串外侧 - 回文串实际范围是
[l+1, r-1]
- 长度 =
(r-1) - (l+1) + 1 = r - l - 1
3. 公式推导过程
3.1 核心思想
回文串具有轴对称特性:
回文串: [左半部分] [中心] [右半部分]←------ 对称轴 ------→
3.2 数学推导
步骤1:分析回文串结构
- 总长度:
maxLen
- 中心位置:
i
- 问题:从中心位置如何找到起始位置?
步骤2:计算中心左侧字符数
对于长度为 maxLen
的回文串:
- 奇数长度(如长度5):
a b c b a
- 中心字符:1个
- 左侧字符数:
(5-1)/2 = 2
- 偶数长度(如长度4):
a b b a
- 中心字符:0个(中心在两字符间)
- 左侧字符数:
(4-1)/2 = 1
(整数除法)
统一公式:中心左侧字符数 = (maxLen-1)/2
步骤3:计算起始位置
起始位置 = 中心位置 - 中心左侧字符数
start = i - (maxLen-1)/2
4. 可视化演示
4.1 测试用例设计
我们选择字符串 "babac"
进行完整演示,这个用例能覆盖算法的所有分支:
字符串: "babac"
索引: 0 1 2 3 4
字符: b a b a c
4.2 完整执行过程
初始状态
maxLen = 0, start = 0
i = 0 时的处理
len1 = exp(s, 0, 0); // 奇数中心扩展
len2 = exp(s, 0, 1); // 偶数中心扩展
奇数扩展 exp(0, 0):
初始: l=0, r=0, s[0]='b'
检查: s[0] == s[0] ✓
扩展: l=-1, r=1
检查: l<0 ✗
返回: r-l-1 = 1-(-1)-1 = 1
偶数扩展 exp(0, 1):
初始: l=0, r=1, s[0]='b', s[1]='a'
检查: s[0] != s[1] ✗
返回: r-l-1 = 1-0-1 = 0
更新状态:
len = max(1, 0) = 1
maxLen < len? 0 < 1 ✓
maxLen = 1
start = 0 - (1-1)/2 = 0 - 0 = 0
i = 1 时的处理
奇数扩展 exp(1, 1):
初始: l=1, r=1, s[1]='a'
检查: s[1] == s[1] ✓
扩展: l=0, r=2
检查: s[0]='b', s[2]='b', s[0] == s[2] ✓
扩展: l=-1, r=3
检查: l<0 ✗
返回: r-l-1 = 3-(-1)-1 = 3
偶数扩展 exp(1, 2):
初始: l=1, r=2, s[1]='a', s[2]='b'
检查: s[1] != s[2] ✗
返回: r-l-1 = 2-1-1 = 0
更新状态:
len = max(3, 0) = 3
maxLen < len? 1 < 3 ✓
maxLen = 3
start = 1 - (3-1)/2 = 1 - 1 = 0
验证结果:
回文串 = s.substring(0, 0+3) = "bab" ✓
i = 2, 3, 4 的处理
继续处理后续位置,但都不会找到更长的回文串,所以最终结果保持不变。
4.3 关键步骤可视化
当 i=1, maxLen=3 时的推导:
原字符串: b a b a c
索引: 0 1 2 3 4↑ ↑ start i 回文串 "bab":
- 中心位置: i = 1
- 长度: maxLen = 3
- 中心左侧字符数: (3-1)/2 = 1
- 起始位置: start = 1 - 1 = 0验证:
字符串: b a b a c
索引: 0 1 2 3 4[---]start=0, length=3提取: s.substring(0, 3) = "bab"
5. 算法分支覆盖分析
5.1 代码分支识别
for (int i = 0; i < n; i++) {int len1 = exp(s, i, i); // 分支1: 奇数长度扩展int len2 = exp(s, i, i + 1); // 分支2: 偶数长度扩展 int len = Math.max(len1, len2);if (maxLen < len) { // 分支3: 更新最长回文串maxLen = len;start = i - (maxLen-1)/2; // 关键公式}
}
5.2 用例 “babac” 的分支覆盖
i | 奇数扩展(len1) | 偶数扩展(len2) | max(len1,len2) | 是否更新 | 覆盖分支 |
---|---|---|---|---|---|
0 | 1 | 0 | 1 | ✓ | 1,2,3 |
1 | 3 | 0 | 3 | ✓ | 1,2,3 |
2 | 1 | 0 | 1 | ✗ | 1,2 |
3 | 1 | 0 | 1 | ✗ | 1,2 |
4 | 1 | 0 | 1 | ✗ | 1,2 |
结论:用例 “babac” 成功覆盖了所有代码分支。
6. 记忆技巧
6.1 视觉记忆法
回文串想象成一个天平:左边 |中心| 右边↑ ↑ ↑(len-1)/2 支点 (len-1)/2从支点往左数 (len-1)/2 步 = 起始位置
中心位置是i,占据了一个位置,想象代码计算回文串expandAroundCenter(i,i),expandAroundCenter(i,i+1)
(len-1)/2的1是中心占据的位置,所以i-左边长度就是start,因此有
start=i-(maxLen-1)/2
6.2 公式记忆口诀
- 回文对称:回文串关于中心轴对称
- 左右等分:除去中心,左右各有
(len-1)/2
个字符 - 向左偏移:从中心向左偏移
(len-1)/2
就是起始位置
6.3 快速验证法
给定 start
和 maxLen
:
- 提取字符串:
s.substring(start, start + maxLen)
- 检查长度:应等于
maxLen
- 检查回文:提取的字符串应是回文串
7. 常见错误分析
7.1 错误公式1:start = i - maxLen/2
错误原因:没有考虑中心字符
- 对于奇数长度,中心字符不应计入左侧
- 正确应该是
(maxLen-1)/2
7.2 错误公式2:start = i - len1/2
或 start = i - len2/2
错误原因:混淆了扩展长度和最终长度
- 应该使用
maxLen
(最长回文串长度) - 而不是单次扩展的长度
7.3 边界错误
问题:start
可能为负数
解决:在实际应用中需要检查边界
start = Math.max(0, i - (maxLen-1)/2);
8. 完整算法总结
public String longestPalindrome(String s) {int maxLen = 0; // 最长回文串长度int n = s.length();int start = 0; // 最长回文串起始位置for (int i = 0; i < n; i++) {// 尝试奇数长度回文串(以i为中心)int len1 = expandAroundCenter(s, i, i);// 尝试偶数长度回文串(以i,i+1为中心) int len2 = expandAroundCenter(s, i, i + 1);int len = Math.max(len1, len2);if (len > maxLen) {maxLen = len;// 核心公式:中心位置减去左侧字符数start = i - (maxLen - 1) / 2;}}return s.substring(start, start + maxLen);
}private int expandAroundCenter(String s, int left, int right) {while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {left--;right++;}return right - left - 1;
}
9. 总结
公式 start = i - (maxLen-1)/2
的本质:
i
是回文串的中心位置(maxLen-1)/2
是中心左侧的字符数量- 从中心向左偏移左侧字符数量,就得到起始位置
记忆要点:
- 回文串是轴对称的
- 知道中心和半径,就能确定起始位置
(maxLen-1)/2
就是回文串的"半径"
这个公式适用于所有情况(奇数/偶数长度),是一个优雅而实用的数学推导结果。