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 扩展函数解析

java 复制代码
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] 或越界
  • 此时 lr 指向回文串外侧
  • 回文串实际范围是 [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 完整执行过程

初始状态
java 复制代码
maxLen = 0, start = 0
i = 0 时的处理
java 复制代码
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

更新状态

java 复制代码
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  

更新状态

java 复制代码
len = max(3, 0) = 3
maxLen < len? 1 < 3 ✓  
maxLen = 3
start = 1 - (3-1)/2 = 1 - 1 = 0

验证结果

java 复制代码
回文串 = 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 代码分支识别

java 复制代码
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 公式记忆口诀

  1. 回文对称:回文串关于中心轴对称
  2. 左右等分 :除去中心,左右各有 (len-1)/2 个字符
  3. 向左偏移 :从中心向左偏移 (len-1)/2 就是起始位置

6.3 快速验证法

给定 startmaxLen

  1. 提取字符串:s.substring(start, start + maxLen)
  2. 检查长度:应等于 maxLen
  3. 检查回文:提取的字符串应是回文串

7. 常见错误分析

7.1 错误公式1:start = i - maxLen/2

错误原因:没有考虑中心字符

  • 对于奇数长度,中心字符不应计入左侧
  • 正确应该是 (maxLen-1)/2

7.2 错误公式2:start = i - len1/2start = i - len2/2

错误原因:混淆了扩展长度和最终长度

  • 应该使用 maxLen(最长回文串长度)
  • 而不是单次扩展的长度

7.3 边界错误

问题start 可能为负数
解决:在实际应用中需要检查边界

java 复制代码
start = Math.max(0, i - (maxLen-1)/2);

8. 完整算法总结

java 复制代码
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 的本质

  1. i 是回文串的中心位置
  2. (maxLen-1)/2 是中心左侧的字符数量
  3. 从中心向左偏移左侧字符数量,就得到起始位置

记忆要点

  • 回文串是轴对称的
  • 知道中心和半径,就能确定起始位置
  • (maxLen-1)/2 就是回文串的"半径"

这个公式适用于所有情况(奇数/偶数长度),是一个优雅而实用的数学推导结果。

相关推荐
smj2302_796826528 小时前
解决leetcode第3911题.移除子数组元素后第k小偶数
数据结构·python·算法·leetcode
_深海凉_12 小时前
LeetCode热题100-寻找两个正序数组的中位数
算法·leetcode·职场和发展
踩坑记录13 小时前
leetcode hot100 寻找两个正序数组的中位数 hard 二分查找 双指针
leetcode
superior tigre16 小时前
78 子集
算法·leetcode·深度优先·回溯
superior tigre18 小时前
739 每日温度
算法·leetcode·职场和发展
6Hzlia18 小时前
【Hot 100 刷题计划】 LeetCode 15. 三数之和 | C++ 排序+双指针
c++·算法·leetcode
北顾笙98019 小时前
day37-数据结构力扣
数据结构·算法·leetcode
6Hzlia1 天前
【Hot 100 刷题计划】 LeetCode 189. 轮转数组 | C++ 三次反转经典魔法 (O(1) 空间)
c++·算法·leetcode
m0_629494731 天前
LeetCode 热题 100-----13.最大子数组和
数据结构·算法·leetcode
田梓燊1 天前
力扣:94.二叉树的中序遍历
数据结构·算法·leetcode