同理dp[0][j]也是0。
其他下标都是随着递推公式逐步覆盖,初始值是默认的即可。
总之,全部都是0,即默认的。
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
4.确定遍历顺序
从递推公式,可以看出,有三个方向可以推出dp[i][j],如图:
那么为了在递推的过程中,这三个方向都是经过计算的数值,所以要从前向后,从上到下来遍历这个矩阵。
5.举例推导dp数组
以输入:text1 = “abcde”, text2 = “ace” 为例,dp状态如图:
最后红框dp[text1.length()][text2.length()]为最终结果。
再次重申,dp[i][j]表示 从text1的下标0开始截取长度为i的字符串 与 从text2的下标0开始截取长度为j的字符串 的最长公共子序列的长度。
方法二:方法一的空间优化
进一步地观察,方法一的代码遍历时只需前一行以及目前一行的信息即可。因此,dp数组初始化两行的。
注意,用k ^ 1、k ^= 1来切换 dp[0] (第一行) 与 dp[1] (第二行)。
注意,m % 2 与 m & 1 意同。
方法三:方法一的空间优化Plus
再进一步地观察,方法一的代码遍历时只需前一行以及目前一行的前一元素信息即可。因此,dp数组初始化一行的,另加3个变量进行辅助。
最后,优化后的时空杂度分别为:O(m * n),O(min(m, n))。
参考资料
public class LongestCommonSubsequence {
// 方法一:标准DP
public int longestCommonSubsequence(String text1, String text2) {
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
for (int i = 1; i <= text1.length(); i++) {
for (int j = 1; j <= text2.length(); 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[text1.length()][text2.length()];
}
// 方法二:方法一的空间优化
public int longestCommonSubsequence2(String s1, String s2) {
int m = s1.length(), n = s2.length();
if (m < n)// 本判断语句目的可以减少空间复杂度。可以移除本判断语句。
return longestCommonSubsequence2(s2, s1);
int[][] dp = new int[2][n + 1];
for (int i = 1, k = 1; i <= m; ++i, k ^= 1)
for (int j = 1; j <= n; ++j)
if (s1.charAt(i - 1) == s2.charAt(j - 1))
dp[k][j] = 1 + dp[k ^ 1][j - 1];
else
dp[k][j] = Math.max(dp[k ^ 1][j], dp[k][j - 1]);
return dp[m & 1][n];
}
// 方法三:方法一的空间优化Plus
public int longestCommonSubsequence3(String text1, String text2) {
int m = text1.length(), n = text2.length();
if (m < n) {// 本判断语句目的可以减少空间复杂度。可以移除本判断语句。
return longestCommonSubsequence3(text2, text1);
}
int[] dp = new int[n + 1];
for (int i = 1; i <= text1.length(); ++i) {
for (int j = 1, left = 0, leftUp = 0, newOne = 0; j <= text2.length(); ++j) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
newOne = leftUp + 1;
} else {
newOne = Math.max(left, dp[j]);
}
leftUp = dp[j];
left = dp[j] = newOne;// 为同行下一元素以及下行元素做准备
}
}
return dp[n];
}
}
import static org.junit.Assert.*;
import org.junit.Test;
public class LongestCommonSubsequenceTest {
@Test
public void test() {
LongestCommonSubsequence obj = new LongestCommonSubsequence();
assertEquals(3, obj.longestCommonSubsequence(“abcde”, “ace”));
assertEquals(3, obj.longestCommonSubsequence(“ace”, “abcde”));
assertEquals(3, obj.longestCommonSubsequence(“abc”, “abc”));
assertEquals(0, obj.longestCommonSubsequence(“abc”, “def”));
}
@Test
public void test2() {
LongestCommonSubsequence obj = new LongestCommonSubsequence();
assertEquals(3, obj.longestCommonSubsequence2(“abcde”, “ace”));
assertEquals(3, obj.longestCommonSubsequence2(“ace”, “abcde”));
assertEquals(3, obj.longestCommonSubsequence2(“abc”, “abc”));
assertEquals(0, obj.longestCommonSubsequence2(“abc”, “def”));
}
@Test
public void test3() {
LongestCommonSubsequence obj = new LongestCommonSubsequence();
assertEquals(3, obj.longestCommonSubsequence3(“abcde”, “ace”));
assertEquals(3, obj.longestCommonSubsequence3(“ace”, “abcde”));
assertEquals(3, obj.longestCommonSubsequence3(“abc”, “abc”));
assertEquals(0, obj.longestCommonSubsequence3(“abc”, “def”));
}
@Test
public void testOther() {
面试资料整理汇总
这些面试题是我朋友进阿里前狂刷七遍以上的面试资料,由于面试文档很多,内容更多,没有办法一一为大家展示出来,所以只好为大家节选出来了一部分供大家参考。
面试的本质不是考试,而是告诉面试官你会做什么,所以,这些面试资料中提到的技术也是要学会的,不然稍微改动一下你就凉凉了
在这里祝大家能够拿到心仪的offer!
;
}
@Test
public void testOther() {
面试资料整理汇总
[外链图片转存中…(img-Kq76CHkY-1719165073601)]
[外链图片转存中…(img-Zca6AlMZ-1719165073602)]
这些面试题是我朋友进阿里前狂刷七遍以上的面试资料,由于面试文档很多,内容更多,没有办法一一为大家展示出来,所以只好为大家节选出来了一部分供大家参考。
面试的本质不是考试,而是告诉面试官你会做什么,所以,这些面试资料中提到的技术也是要学会的,不然稍微改动一下你就凉凉了
在这里祝大家能够拿到心仪的offer!