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 就是回文串的"半径"

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

相关推荐
天选之女wow4 小时前
【代码随想录算法训练营——Day4】链表——24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07.链表相交、142.环形链表II
数据结构·算法·leetcode·链表
Miraitowa_cheems10 小时前
LeetCode算法日记 - Day 34: 二进制求和、字符串相乘
java·算法·leetcode·链表·职场和发展
程序员Xu13 小时前
【LeetCode热题100道笔记】二叉树的直径
笔记·算法·leetcode
阿维的博客日记14 小时前
LeetCode 209 - 长度最小的子数组算法详解
数据结构·算法·leetcode
珍珠是蚌的眼泪15 小时前
LeetCode_数学
leetcode·计数质数·3的幂·七进制数·十六进制数·三个数的最大乘积
Lris-KK1 天前
【Leetcode】高频SQL基础题--1731.每位经理的下属员工数量
sql·leetcode
野犬寒鸦1 天前
力扣hot100:搜索二维矩阵 II(常见误区与高效解法详解)(240)
java·数据结构·算法·leetcode·面试
菜鸟得菜1 天前
leecode kadane算法 解决数组中子数组的最大和,以及环形数组连续子数组的最大和问题
数据结构·算法·leetcode