从“看起来像双指针”到真正的动态规划 —— 最长公共子序列

一、题目速览

LeetCode 1143. 最长公共子序列(Medium)

给定两个字符串 text1text2,返回它们的 最长公共子序列的长度

若不存在公共子序列,返回 0

示例

复制代码
text1 = "abcde"
text2 = "ace"

输出:3
解释:最长公共子序列是 "ace",长度为 3。

二、第一反应为什么容易错?

很多人第一眼会想:

"能不能用双指针?"

不行,因为:

  • 子序列 不要求连续

  • 双指针只能处理"连续匹配"

  • 一旦跳字符,顺序就乱了


三、为什么一定是动态规划?

关键特征全部命中:

DP 特征 本题表现
有重叠子问题 同一个前缀对会被反复计算
有最优子结构 整体最优由局部最优构成
无后效性 后面的选择不影响前面的结果

👉 LCS 是 DP 教科书级例题


四、状态设计(最重要的一步)

定义状态

复制代码
dp[i][j] = text1 前 i 个字符 与 text2 前 j 个字符 的最长公共子序列长度

注意:

ij表示 前缀长度,不是下标


五、状态转移(核心逻辑)

情况 1:当前字符相同

复制代码
text1[i-1] == text2[j-1]
dp[i][j] = dp[i-1][j-1] + 1

✅ 把这个字符加入公共子序列


情况 2:当前字符不同

复制代码
text1[i-1] != text2[j-1]
dp[i][j] = max(dp[i-1][j], dp[i][j-1])

✅ 至少丢掉其中一个字符,取更优的结果


合并写法

复制代码
if text1[i-1] == text2[j-1]:
    dp[i][j] = dp[i-1][j-1] + 1
else:
    dp[i][j] = max(dp[i-1][j], dp[i][j-1])

六、基础边界

复制代码
dp[0][*] = 0
dp[*][0] = 0

空字符串与任何字符串的公共子序列长度都是 0。


七、执行过程示例

text1 = "abcde"

text2 = "ace"

DP 表(简化):

Ø a c e
Ø 0 0 0 0
a 0 1 1 1
b 0 1 1 1
c 0 1 2 2
d 0 1 2 2
e 0 1 2 **3 ✅**​

八、Java 实现(面试版)

二维 DP(最清晰)

复制代码
class Solution {
    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[i][j]只依赖:

  • 上一行

  • 当前行前一个

👉 可用一维数组优化:

复制代码
class Solution {
    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;
            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];
    }
}

十、复杂度分析

版本 时间复杂度 空间复杂度
二维 DP O(m × n) O(m × n)
一维 DP O(m × n) O(n)

十一、面试答题模板(直接背)

"这是一道经典的二维动态规划问题。

我们用 dp[i][j]表示 text1i个字符和 text2j个字符的最长公共子序列长度。

如果两个字符相同,就继承左上角的值并加一;

如果不同,就取上方和左方的最大值。

时间复杂度 O(m×n),空间复杂度可以优化到 O(n)。"


十二、一句话总结

**LCS 的本质不是"找字符串",而是"数状态"。**​

你不是在比较两个字符串,而是在比较它们所有可能的"前缀对"。

相关推荐
kisshyshy1 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷9 小时前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络10 小时前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络10 小时前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao40010 小时前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao40010 小时前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2122 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2123 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack203 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树3 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色