【LeetCode 热题 100】1143. 最长公共子序列——(解法二)递推
Problem: 1143. 最长公共子序列
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(m * n)
- 空间复杂度:O(m * n)
整体思路
这段代码同样旨在解决 “最长公共子序列” (LCS) 问题,但它采用的是一种 自底向上(Bottom-Up)的动态规划 方法,也称为 迭代法 或 制表法 (Tabulation)。这种方法通过构建一个DP表(dp
二维数组),从最小的子问题开始,逐步计算出最终的解,避免了递归的开销。
该实现是解决LCS问题的标准教科书式DP模型。
-
状态定义与索引映射
- 算法定义了一个二维DP数组
dp
,大小为(m+1) x (n+1)
。 dp[i][j]
的含义是:text1
的前i
个字符(text1.substring(0, i)
)与text2
的前j
个字符(text2.substring(0, j)
)的最长公共子序列的长度。- 这是一个常见的索引偏移技巧。
dp
数组的索引i
和j
分别对应了text1
和text2
的长度,而字符串的索引是i-1
和j-1
。这样做的好处是,dp
数组的第0行和第0列可以天然地代表空字符串的情况,简化了边界处理。
- 算法定义了一个二维DP数组
-
基础情况 (Base Case)
- 在Java中,
new int[m + 1][n + 1]
数组的所有元素默认初始化为0。 dp[0][j]
和dp[i][0]
始终为0。这恰好满足我们的基础情况:任何字符串与一个空字符串的最长公共子序列长度都为0。
- 在Java中,
-
状态转移 (State Transition)
- 算法使用两层嵌套循环来填充DP表。
i
和j
在循环中对应的是字符串的索引。 - 在循环的每一步,计算
dp[i+1][j+1]
的值,它代表text1[0...i]
和text2[0...j]
的LCS长度。 - 状态转移逻辑基于对
text1.charAt(i)
和text2.charAt(j)
的比较:
a. 如果text1.charAt(i) == text2.charAt(j)
:- 这意味着这两个相同的字符可以被包含在LCS中。
- 因此,当前LCS的长度等于
text1[0...i-1]
和text2[0...j-1]
的LCS长度再加1。 - 状态转移方程为:
dp[i + 1][j + 1] = dp[i][j] + 1
。
b. 如果text1.charAt(i) != text2.charAt(j)
: - 这意味着这两个不同的字符不可能同时被包含在LCS中。
- LCS的长度取决于两种可能中较长的那一个:
text1[0...i-1]
和text2[0...j]
的LCS,其长度为dp[i][j+1]
。text1[0...i]
和text2[0...j-1]
的LCS,其长度为dp[i+1][j]
。
- 状态转移方程为:
dp[i + 1][j + 1] = Math.max(dp[i][j + 1], dp[i + 1][j])
。
- 算法使用两层嵌套循环来填充DP表。
-
返回结果
- 当所有循环结束后,
dp
数组已经完全填充。我们需要的最终答案,即text1
和text2
整个字符串的LCS长度,就存储在dp[m][n]
中。
- 当所有循环结束后,
完整代码
class Solution {/*** 计算两个字符串的最长公共子序列的长度。* @param text1 第一个字符串* @param text2 第二个字符串* @return 最长公共子序列的长度*/public int longestCommonSubsequence(String text1, String text2) {int m = text1.length();int n = text2.length();// dp[i][j]: 表示 text1 的前 i 个字符与 text2 的前 j 个字符的 LCS 长度。// 使用 m+1, n+1 的大小是为了用第0行和第0列代表空字符串,简化边界处理。int[][] dp = new int[m + 1][n + 1];// i 和 j 是 text1 和 text2 的 0-based 索引for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前比较的两个字符相同if (text1.charAt(i) == text2.charAt(j)) {// LCS 长度等于它们各自去掉末尾字符后的 LCS 长度 + 1。dp[i + 1][j + 1] = dp[i][j] + 1;} else {// 如果字符不同,LCS 长度等于两种情况中的较大值:// 1. text1 去掉末尾 vs text2 (dp[i][j+1])// 2. text1 vs text2 去掉末尾 (dp[i+1][j])dp[i + 1][j + 1] = Math.max(dp[i][j + 1], dp[i + 1][j]);}}}// 最终答案存储在 dp[m][n] 中,代表整个 text1 和 text2 的 LCS 长度。return dp[m][n];}
}
时空复杂度
时间复杂度:O(m * n)
m
是text1
的长度,n
是text2
的长度。
- 循环结构:算法使用了两层嵌套的
for
循环。- 外层循环从
i = 0
运行到m-1
,执行m
次。 - 内层循环从
j = 0
运行到n-1
,执行n
次。
- 外层循环从
- 计算依据:
- 总的操作次数(主要是比较、加法和
Math.max
)是内外层循环次数的乘积,即m * n
。
- 总的操作次数(主要是比较、加法和
综合分析:
算法的时间复杂度为 O(m * n)。
空间复杂度:O(m * n)
- 主要存储开销:算法创建了一个名为
dp
的二维整型数组来存储动态规划的所有中间状态。 - 空间大小:该数组的大小是
(m + 1) * (n + 1)
。
综合分析:
算法所需的额外空间主要由 dp
数组决定,其空间复杂度为 O(m * n)。