LeetCode 5. 最长回文子串

题目描述

给定一个字符串 s,找到 s 中最长的回文子串。

回文串从左向右读和从右向左读的结果相同,例如 "aba" 和 "abba"。

最初思路

最开始采用暴力枚举:

  1. 用两重循环枚举子串的左右端点 i、j。
  2. 当首尾字符相同时,再调用 isHuiWen 检查整个区间。
  3. 如果该区间是回文串,并且长度超过当前答案,就更新最长回文串。

这个方法逻辑直接,也能够通过,但存在大量重复检查。

问题出在哪里

两重循环需要枚举 O(n^2) 个子串,每次调用 isHuiWen 最多还要扫描 O(n) 个字符,因此最坏时间复杂度为 O(n^3)。

优化的关键不是加快一次回文判断,而是改变枚举方式:不再枚举左右端点,而是枚举回文串的中心。

核心洞察

每个回文串都关于中心对称,但中心有两种形式:

  • 奇数长度回文:中心是一个字符,例如 "aba",中心可以表示为 (i, i)。
  • 偶数长度回文:中心是两个字符之间的空隙,例如 "abba",中心可以表示为 (i, i + 1)。

遍历每个位置 i,分别尝试这两种中心。只要左右字符相同,就让左指针向左、右指针向右继续扩展。

扩展过程

以 "abba" 为例,偶数中心位于两个 'b' 之间:

复制代码
初始:l = 1,r = 2
第一次匹配:s[1] == s[2],继续扩展
第二次匹配:s[0] == s[3],继续扩展
结束:l = -1,r = 4

循环结束时,左右指针已经各多走了一步,所以:

  • 实际回文起点:l + 1
  • 实际回文终点:r - 1
  • 实际回文长度:r - l - 1

辅助方法只返回回文长度后,可以根据中心下标 i 和长度 len,用 i - (len - 1) / 2 统一计算奇数、偶数回文的起点。

伪代码

下面的伪代码和 Java 实现一一对应:

复制代码
函数 longestPalindrome(s):
    如果 s 的长度小于 2:
        返回 s

    start <- 0
    maxLen <- 1

    对每个下标 i:
        oddLen <- expand(s, i, i)
        evenLen <- expand(s, i, i + 1)
        currentLen <- oddLen 和 evenLen 中的较大值

        如果 currentLen > maxLen:
            maxLen <- currentLen
            start <- i - (currentLen - 1) / 2

    返回 s 从 start 开始、长度为 maxLen 的子串

函数 expand(s, left, right):
    当 left、right 没有越界,并且 s[left] == s[right]:
        left <- left - 1
        right <- right + 1

    返回 right - left - 1

Java 代码

复制代码
class Solution {
    public String longestPalindrome(String s) {
        if (s.length() < 2) {
            return s;
        }

        int start = 0;
        int maxLen = 1;

        for (int i = 0; i < s.length(); i++) {
            int oddLen = expand(s, i, i);
            int evenLen = expand(s, i, i + 1);
            int currentLen = Math.max(oddLen, evenLen);

            if (currentLen > maxLen) {
                maxLen = currentLen;
                start = i - (currentLen - 1) / 2;
            }
        }

        return s.substring(start, start + maxLen);
    }

    private int expand(String s, int left, int right) {
        while (left >= 0
                && right < s.length()
                && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }
}

易错点

1. 只处理奇数长度回文

如果只从 (i, i) 开始扩展,会漏掉 "abba" 这类偶数长度回文。每个位置都要检查 (i, i) 和 (i, i + 1)。

2. 扩展结束后的长度多算两个字符

循环退出时,left 和 right 已经指向回文区间之外,因此长度不是 right - left + 1,而是 right - left - 1。

3. 子串结束下标不包含在结果中

Java 的 substring(start, end) 不包含 end,所以应返回 s.substring(start, start + maxLen)。

4. 最短答案的初始化

非空字符串至少存在一个长度为 1 的回文串,因此可以初始化 start = 0、maxLen = 1。

建议测试用例

复制代码
s = "babad" -> "bab" 或 "aba"
s = "cbbd"  -> "bb"
s = "a"     -> "a"
s = "ac"    -> "a" 或 "c"
s = "abba"  -> "abba"
s = "aaaa"  -> "aaaa"

复杂度分析

  • 时间复杂度:O(n^2)。共有 O(n) 个位置,每个位置进行两次中心扩展,每次最多扩展 O(n)。
  • 空间复杂度:O(1)。除返回的子串外,只使用常数个变量。

总结

这题的优化关键是利用回文串的中心对称性,将"枚举子串并完整检查"改成"枚举中心并向两边扩展"。

实现时需要同时处理奇数、偶数两种中心,并牢记扩展结束后指针已经多走一步,实际长度为 right - left - 1。

相关推荐
罗西的思考3 小时前
【Agentic RL】Dressage Staleness 控制深度分析
人工智能·算法·机器学习
quaer3 小时前
弗洛伊德算法--判圈法寻找环的入口O(n) O(1)
数据结构·算法
高晶5 小时前
无乘法器实现LC二阶滤波器:基于移位运算的快速算法与四种精度推导
算法·设计模式·程序员
ysbfsy5 小时前
大数计算思路
算法
高晶5 小时前
主从自动分配算法:平均值乘以设备数一半的选举基准值解析
算法·设计模式·程序员
学电子她就能回来吗5 小时前
LeetCode Hot 100|普通数组专题学习笔记(零基础版)
笔记·算法·leetcode
用户老李5 小时前
反向传播计算梯度举例
算法
niyongsheng5 小时前
多智能体连续时间内路径规划:AA-SIPP(m) 的 Rust 实现与算法
算法·rust·机器人
302wanger6 小时前
DeepSeek-V4.1-Flash-1017 免费用到1024,速来
算法
lisw057 小时前
微分与积分的本质区别解析
算法·数学建模·极限编程