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。

相关推荐
lzhdim3 小时前
C#不为人知的10个魔法特性:资深开发者也会震惊的底层奥秘
java·前端·javascript·算法·c#
weixin_307779133 小时前
C++代码实现MATLAB中的pade函数功能
开发语言·c++·算法·matlab
smj2302_796826523 小时前
解决leetcode第4064题至多一次取反能被k整除的最长子数组II
python·算法·leetcode
AIGC小尼3 小时前
ACE-Step-1.5 本地 AI 音乐模型实战|text2music 歌词转人声,部署脚本、参数调优与排坑全记录
人工智能·算法·comfyui·ai漫剧
YOLO数据集集合3 小时前
树木检测数据集 | 树木检测 树种分类 航拍林业 森林监测9131期
人工智能·算法·机器学习·分类·数据挖掘·林业·树种分类
青山木3 小时前
Hot 100 --- 下一个排列
java·数据结构·算法·leetcode
垆边人似月.3 小时前
字符串重排后最大字典序
算法·leetcode·职场和发展
码流子3 小时前
AI稽核精灵-Agent落地
大数据·人工智能·物联网·算法·系统架构
微三云生态系统架构师-彭丹3 小时前
排队免单资金池专户存管与返本算法:队列模型与熔断保护
算法·熔断机制·排队免单·消费增值·资金池·队列系统·返本算法
wzdark4 小时前
矩阵快速幂算法在图路径计算中的应用4
线性代数·算法·矩阵