LeetCode 5. 最长回文子串

题目描述

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

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

最初思路

最开始采用暴力枚举:

  1. 用两重循环枚举子串的左右端点 ij
  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. 扩展结束后的长度多算两个字符

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

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

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

4. 最短答案的初始化

非空字符串至少存在一个长度为 1 的回文串,因此可以初始化 start = 0maxLen = 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

相关推荐
hanlin032 小时前
刷题笔记:力扣第84题-柱状图中最大的矩形
笔记·算法·leetcode
青少儿编程课堂3 小时前
威佐夫博弈(双堆取子游戏)解析
c++·python·算法·bfs·信息学竞赛
辰烨chenye7 小时前
LeetCode Hot 100 题解 · 普通数组篇
算法·leetcode·职场和发展
hqyjzsb10 小时前
零 AI 项目经验,学 Python 转型 AI 的正确顺序是什么?
开发语言·人工智能·python·算法·职场和发展·数据挖掘·数据分析
辰烨chenye10 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
微功夫信息技术10 小时前
分层多智能体强化学习驱动的非急救转运公平 - 效率统一调度系统研究与实践
人工智能·学习·算法·动态规划
sanjiaomao33311 小时前
从论文公式到Python实现:用单元测试校验滑动平均算法
python·算法·单元测试
乐迪信息11 小时前
如何通过AI防爆摄像机精准判断船舶超速?
大数据·人工智能·算法·安全·目标跟踪
大圣编蚕12 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法