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

一、题目速览

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

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

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

示例

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

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

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

很多人第一眼会想:

"能不能用双指针?"

❌ 不行,因为:

  • 子序列 不要求连续

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

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


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

关键特征全部命中:

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

👉 LCS 是 DP 教科书级例题


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

定义状态

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

注意:

i、j表示 前缀长度,不是下标


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

情况 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]表示 text1前 i个字符和 text2前 j个字符的最长公共子序列长度。

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

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

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


十二、一句话总结

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

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

相关推荐
HPtiger6 小时前
数据结构——选择排序
数据结构·算法·排序算法
康实训6 小时前
老年评估仿真实训平台架构设计与老年人能力评估实训室教学落地实践
大数据·人工智能·算法·实训室·实训室建设
DongQiShanRen7 小时前
六根硬件映射(四):归属澄清、两处不一致与卷十一总表
开发语言·人工智能·算法·rust·哈希算法·集成学习
CCblandy7 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
丑梨数据7 小时前
信创环境里跑文档工具:格式兼容与本地合规的几个坑
数据结构·人工智能·算法·决策树·排序算法
erzong2319 小时前
Linux操作系统:线程概念与控制
linux·算法
Nil2089 小时前
leetcode 5最长回文子串
算法·leetcode·职场和发展
xxwl5859 小时前
区域赛分类题解
c++·笔记·学习·算法
m0_674571979 小时前
力扣 hot 100 python
python·算法·leetcode
青少儿编程课堂10 小时前
蓝桥杯+全国青少年信息素养大赛+电子学会PythonC/C++考级:最长公共子序列LCS解析
c++·python·算法·bfs·信息学竞赛