LeetCode Hot100(57/100)——5. 最长回文子串

文章目录

题目链接
LeetCode - Longest Palindromic Substring


一、题目描述

给定一个字符串 s,请你找出其中的 最长回文子串

回文子串指的是正着读和反着读都相同的字符串片段。

示例:

复制代码
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是有效答案

输入: s = "cbbd"
输出: "bb"

二、问题分析

回文的特征是 对称,因此求解过程的核心就是如何找到字符串中的对称中心并向两边扩展。

下图从思维层面展示了几种常见算法思路:
最长回文子串
暴力枚举
检查所有子串是否回文
时间复杂度 O(n³)
动态规划
状态定义 dpij
转移方程依据 si==sj
时间复杂度 O(n²)
中心扩展
每个字符为中心
向两边扩展计算长度
时间复杂度 O(n²)
Manacher算法
利用镜像特性
线性时间求解 O(n)


三、解法一:暴力枚举

原理

  • 枚举所有可能的子串。
  • 对每个子串判断是否为回文。
  • 记录最大长度的回文子串。

流程图



输入字符串 s
枚举所有子串 si:j
判断是否为回文
是回文?
更新最长长度与起止下标
继续下一子串
输出最长回文子串

时间复杂度

  • 枚举子串次数约为 O(n²)
  • 判定回文 O(n)
  • 总时间复杂度:O(n³)
  • 空间复杂度:O(1)

属于理论上最简单但效率最低的方式,适合初学者理解。


四、解法二:动态规划(DP)

原理

使用二维数组 dp[i][j] 表示 si...j 是否为回文字符串

  • s[i] == s[j] 时,若 dp[i+1][j-1] 为真,则 dp[i][j] 也为真。
  • 边界情况:单个字符必为回文 (dp[i][i] = true)

状态转移方程:

d p i j = ( s i = = s j ) & & ( j − i < 3 ∥ d p i + 1 j − 1 ) dpij = (si == sj) \ \&\& \ (j - i < 3 \ \| \ dpi+1j-1) dpij=(si==sj) && (j−i<3 ∥ dpi+1j−1)

伪逻辑时序

状态表 dpij 内层循环 j ← i 到 n-1 外层循环 i ← n-1 到 0 状态表 dpij 内层循环 j ← i 到 n-1 外层循环 i ← n-1 到 0 逐步扩大区间 i...j 判断 si==sj 且区间内部为回文 若成立则更新最大长度

时间复杂度

  • 状态数量 O(n²)
  • 每次转移 O(1)
  • 总复杂度:O(n²)
  • 空间复杂度:O(n²)

Java 实现

java 复制代码
public class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        String res = "";
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                }
                if (dp[i][j] && (j - i + 1) > res.length()) {
                    res = s.substring(i, j + 1);
                }
            }
        }
        return res;
    }
}

五、解法三:中心扩展法

原理

回文子串的"中心"可以是:

  • 一个字符(奇数长度回文)
  • 两个相邻字符(偶数长度回文)

从每个中心向两侧扩展,直到不再匹配。

示意图

中心 i/i+1
左边扩展 →
← 右边扩展
判断 sleft==sright
更新最长回文长度

时间复杂度

  • 中心共有 2n-1 个。
  • 每次扩展最多 O(n)
  • 总复杂度:O(n²)
  • 空间复杂度:O(1)

Java 实现

java 复制代码
public class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() < 2) return s;
        int start = 0, maxLen = 1;
        for (int i = 0; i < s.length(); i++) {
            int len1 = expandFromCenter(s, i, i);
            int len2 = expandFromCenter(s, i, i + 1);
            int len = Math.max(len1, len2);
            if (len > maxLen) {
                maxLen = len;
                start = i - (len - 1) / 2;
            }
        }
        return s.substring(start, start + maxLen);
    }

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

该方法是实际工程中最常见的实现,兼顾可读性与性能。


不同算法对比总结

方法 时间复杂度 空间复杂度 思想 优势 劣势
暴力枚举 O(n³) O(1) 完全枚举 易理解 极慢
动态规划 O(n²) O(n²) 状态转移 稳定性高 占用空间多
中心扩展 O(n²) O(1) 对称扩展 简洁高效 理论非最优

如果是初学者,推荐使用中心扩展法,代码简洁、性能足够。

相关推荐
不正经学生4 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
毕竟是shy哥6 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲6 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
圣保罗的大教堂7 小时前
leetcode 1406. 石子游戏 III 困难
leetcode
晊晌_h8 小时前
嵌入式从0到精通——数据结构总结[特殊字符]
数据结构·算法·排序算法
我找到地球的支点啦8 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
罗西的思考9 小时前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
浪里镖客9 小时前
位姿转换矩阵写法-个人习惯(计算机理解其实是相反的)
线性代数·算法·矩阵
丰锋ff10 小时前
面试问题合集
面试·职场和发展
颜挺锐10 小时前
如何轻松通过性能测试之第四篇:性能测试项目怎么交付?一文讲透九步实施流程(附面试话术 + 实战清单)
面试·职场和发展