二刷 LeetCode:5. 最长回文子串 & 1143. 最长公共子序列 复盘笔记

目录

[一、5. 最长回文子串](#一、5. 最长回文子串)

题目回顾

思路复盘

[方法 1:中心扩展法(最直观、推荐)](#方法 1:中心扩展法(最直观、推荐))

[方法 2:动态规划(进阶理解)](#方法 2:动态规划(进阶理解))

[易错点 & 二刷心得](#易错点 & 二刷心得)

[二、1143. 最长公共子序列](#二、1143. 最长公共子序列)

题目回顾

思路复盘

[空间优化版(一维 DP)](#空间优化版(一维 DP))

[易错点 & 二刷心得](#易错点 & 二刷心得)

[三、两道题的共性总结 & 二刷收获](#三、两道题的共性总结 & 二刷收获)


这两道题都是动态规划的经典代表,也是面试高频考点,二刷时我们重点拆解思路、优化写法,顺便把易错点和通用模板总结清楚。


一、5. 最长回文子串

题目回顾

给你一个字符串 s,找到 s 中最长的回文子串。回文子串是指正读和反读都相同的字符串,子串是连续的字符序列。

思路复盘

方法 1:中心扩展法(最直观、推荐)

核心思想:回文串是对称的,所以可以以每个字符(或字符间隙)为中心,向两边扩展,找到以该中心为轴的最长回文串

  • 回文串有两种形式:
    1. 奇数长度 :中心是一个字符(如 aba,中心是 b
    2. 偶数长度 :中心是两个字符的间隙(如 abba,中心在两个 b 之间)
  • 实现步骤:
    1. 遍历字符串的每个位置 i,分别以 i(奇数长度)和 i+1(偶数长度)为中心扩展
    2. 每次扩展时,判断左右边界的字符是否相等,相等则继续扩展,否则停止
    3. 记录扩展过程中得到的最长回文串的起始和结束位置

Java 代码实现

java

运行

复制代码
public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);       // 奇数长度
        int len2 = expandAroundCenter(s, i, i + 1);   // 偶数长度
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

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;
}
方法 2:动态规划(进阶理解)

核心思想:用 dp[i][j] 表示 s[i..j] 是否为回文子串。

  • 状态转移方程:
    • 如果 s[i] == s[j]dp[i+1][j-1] 为 true,则 dp[i][j] = true
    • 边界条件:dp[i][i] = true(单个字符是回文),dp[i][i+1] = (s[i] == s[i+1])(两个字符相等则为回文)
  • 遍历顺序:从短到长枚举子串长度,保证计算 dp[i][j] 时,dp[i+1][j-1] 已经计算完成

Java 代码实现

java

运行

复制代码
public String longestPalindrome(String s) {
    int n = s.length();
    if (n < 2) return s;
    boolean[][] dp = new boolean[n][n];
    int maxLen = 1;
    int start = 0;
    // 单个字符都是回文
    for (int i = 0; i < n; i++) {
        dp[i][i] = true;
    }
    // 枚举子串长度
    for (int len = 2; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            if (s.charAt(i) != s.charAt(j)) {
                dp[i][j] = false;
            } else {
                if (len == 2) {
                    dp[i][j] = true;
                } else {
                    dp[i][j] = dp[i+1][j-1];
                }
            }
            if (dp[i][j] && len > maxLen) {
                maxLen = len;
                start = i;
            }
        }
    }
    return s.substring(start, start + maxLen);
}

易错点 & 二刷心得

  1. 中心扩展的边界处理expandAroundCenter 函数中,退出循环时 leftright 已经越界,所以长度计算要减 1:right - left - 1
  2. DP 方法的遍历顺序 :必须先枚举子串长度,再枚举起点,否则会出现 dp[i+1][j-1] 未计算的情况。
  3. 两种方法的选择:中心扩展法时间复杂度 O (n²),空间复杂度 O (1),是面试中最推荐的解法;DP 方法适合理解回文子串的动态规划思路,空间复杂度 O (n²),可优化到 O (n)。

二、1143. 最长公共子序列

题目回顾

给定两个字符串 text1text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列,返回 0。子序列是由字符串派生而来的序列,删除(或不删除)字符串中的某些字符而不改变其余元素的顺序。

思路复盘

这是二维动态规划的经典题,核心是定义状态和状态转移方程。

  1. 状态定义dp[i][j] 表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列长度(为了方便处理边界,i 和 j 从 1 开始)。
  2. 状态转移
    • 如果 text1[i-1] == text2[j-1],说明当前字符可以作为公共子序列的一部分,所以 dp[i][j] = dp[i-1][j-1] + 1
    • 如果不相等,那么最长公共子序列只能来自去掉 text1[i-1] 或去掉 text2[j-1] 的情况,所以 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  3. 初始状态dp[0][j] = 0dp[i][0] = 0(空字符串和任何字符串的公共子序列长度都是 0)
  4. 结果dp[m][n],其中 mn 分别是 text1text2 的长度

Java 代码实现(二维 DP)

java

运行

复制代码
public int longestCommonSubsequence(String text1, String text2) {
    int m = text1.length();
    int n = text2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}
空间优化版(一维 DP)

因为 dp[i][j] 只依赖 dp[i-1][j-1]dp[i-1][j]dp[i][j-1],所以可以用一维数组优化空间复杂度到 O (n):

java

运行

复制代码
public int longestCommonSubsequence(String text1, String text2) {
    int m = text1.length();
    int n = text2.length();
    int[] dp = new int[n + 1];
    for (int i = 1; i <= m; i++) {
        int prev = 0; // 保存dp[i-1][j-1]的值
        for (int j = 1; j <= n; j++) {
            int temp = dp[j];
            if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                dp[j] = prev + 1;
            } else {
                dp[j] = Math.max(dp[j], dp[j - 1]);
            }
            prev = temp;
        }
    }
    return dp[n];
}

易错点 & 二刷心得

  1. 状态定义的边界处理dp[i][j] 定义为前 i 个和前 j 个字符的公共子序列,这样可以避免处理 i=0j=0 时的边界问题。
  2. 一维优化的关键 :必须用 prev 变量保存 dp[i-1][j-1] 的值,否则在更新 dp[j] 时会覆盖之前的值。
  3. 和最长递增子序列的区别:最长递增子序列是单字符串的问题,最长公共子序列是双字符串的问题,两者的 DP 思路有相似之处,但状态定义和转移方程不同。

三、两道题的共性总结 & 二刷收获

  1. 动态规划的核心
    • 明确状态定义:把问题分解成子问题,定义 dp[i][j] 表示什么
    • 找到状态转移方程:根据当前字符是否相等,推导状态转移的逻辑
  2. 优化意识
    • 最长回文子串:从暴力法到中心扩展法,把时间复杂度从 O (n³) 降到 O (n²),空间复杂度降到 O (1)
    • 最长公共子序列:从二维 DP 到一维 DP,把空间复杂度从 O (mn) 降到 O (min (m,n))
  3. 面试重点
    • 最长回文子串:中心扩展法是必须掌握的,要能讲清楚两种中心的扩展逻辑
    • 最长公共子序列:二维 DP 的思路是基础,一维优化是加分项,要理解优化的原理
相关推荐
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B AI算法开发流程
人工智能·算法·机器学习
Rabitebla2 小时前
【C++】string 类:原理、踩坑与对象语义
linux·c语言·数据结构·c++·算法·github·学习方法
小雅痞3 小时前
[Java][Leetcode middle] 167. 两数之和 II - 输入有序数组
java·算法·leetcode
CN-Dust3 小时前
【C++】输入cin例题专题
java·c++·算法
数模竞赛Paid answer4 小时前
2025年MathorCup数学建模A题汽车风阻预测解题文档与程序
算法·数学建模·mathorcup
xin_nai4 小时前
LeetCode热题100(Java)(6)矩阵
java·leetcode·矩阵
三品吉他手会点灯10 小时前
C语言学习笔记 - 20.C编程预备计算机专业知识 - 变量为什么必须的初始化【重点】
c语言·笔记·学习
kobesdu10 小时前
【ROS2实战笔记-12】rosshow:终端里的盲文可视化与无头机器人的现场调试
笔记·机器人·ros·移动机器人
sakiko_10 小时前
UIKit学习笔记1-创建项目(使用UIKit)、使用组件
笔记·学习