1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Solution { public int longestCommonSubsequence(String text1, String text2) { int[][] memo = new int[text1.length() + 1][text2.length() + 1]; for (int i = 0; i < text1.length(); i++) { for (int j = 0; j < text2.length(); j++) { memo[i][j] = -1; } } return helper(text1, text2, 0, 0, memo); }
private int helper(String text1, String text2, int pos1, int pos2, int[][] memo) { if (memo[pos1][pos2] != -1) { return memo[pos1][pos2]; } int option1 = helper(text1, text2, pos1 + 1, pos2, memo); int option2 = 0; int firstOccurence = text2.indexOf(text1.charAt(pos1), pos2); if (firstOccurence != -1) { option2 = helper(text1, text2, pos1 + 1, firstOccurence + 1, memo) + 1; } memo[pos1][pos2] = Math.max(option1, option2); return memo[pos1][pos2]; } }
|
思路就是我们看text1的每个char. 选项1就是不考虑这个char, 那么我们就需要找该char之后组成的string和text2的LCS. 选项2就是考虑这个char, 那么此时我们就需要找text2最左边的第一次出现的这个char. 然后从这里往后截取substring, 然后求text1刨去这个char后的substring和text2第一次出现的char后面的substring的LCS, 得到后 + 1即可.
这里如果pos1或者pos2到达text1.length()或者text2.length()的时候, 我们直接返回0, 这说明二者没有LCS. 比较聪明的做法是我们的memo的长和宽分别是text1.length() + 1和text2.length() + 1. 这样完美cover到了base case.
时间复杂度: O(M * N^2) 因为有个indexOf因此每个subproblem是O(N) 有多少个subProblem? 因为pos1有M种可能, pos2有N种可能, 因此是M * N, 合在一起就是O(M * N^2)
空间复杂度: O(M * N)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Solution { public int longestCommonSubsequence(String text1, String text2) { int[][] memo = new int[text1.length() + 1][text2.length() + 1]; for (int i = 0; i < text1.length(); i++) { for (int j = 0; j < text2.length(); j++) { memo[i][j] = -1; } } return helper(text1, text2, 0, 0, memo); }
private int helper(String text1, String text2, int pos1, int pos2, int[][] memo) { if (memo[pos1][pos2] != -1) { return memo[pos1][pos2]; } int ans = 0; if (text1.charAt(pos1) == text2.charAt(pos2)) { ans = helper(text1, text2, pos1 + 1, pos2 + 1, memo) + 1; } else { ans = Math.max(helper(text1, text2, pos1 + 1, pos2, memo), helper(text1, text2, pos1, pos2 + 1, memo)); } memo[pos1][pos2] = ans; return memo[pos1][pos2]; } }
|
一点点优化. 如果pos1和pos2指向的char不相等, 那这两个位置的char一定不会在最后的答案里面. 于是我们要更新pos1和pos2, 使得他俩指向的char相等, 那么答案才可能从这里开始. 此时的话我们先更新pos1, 看能取得多长的LCS, 再更新pos2, 看能取多长的LCS, 取最大即可. 如果二者相等, 那么同时更新这两个.
时间复杂度: O(M * N)
空间复杂度: O(M * N)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public int longestCommonSubsequence(String text1, String text2) { int[][] memo = new int[text1.length() + 1][text2.length() + 1]; for (int i = text1.length() - 1; i >= 0; i--) { for (int j = text2.length() - 1; j >= 0; j--) { if (text1.charAt(i) == text2.charAt(j)) { memo[i][j] = memo[i + 1][j + 1] + 1; } else { memo[i][j] = Math.max(memo[i + 1][j], memo[i][j + 1]); } } } return memo[0][0]; } }
|
从helper的signature来判断是是二维数组(pos1和pos2标记一种subproblem), 从helper的内容来确定状态转移方程.
时间复杂度: O(M * N)
空间复杂度: O(M * N)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public int longestCommonSubsequence(String text1, String text2) { if (text1.length() < text2.length()) { return longestCommonSubsequence(text2, text1); } int[] memo = new int[text2.length() + 1]; for (int i = text1.length() - 1; i >= 0; i--) { int currDiagnol = memo[memo.length - 1]; for (int j = text2.length() - 1; j >= 0; j--) { int nextDiagnol = memo[j]; if (text1.charAt(i) == text2.charAt(j)) { memo[j] = currDiagnol + 1; } else { memo[j] = Math.max(memo[j], memo[j + 1]); } currDiagnol = nextDiagnol; } } return memo[0]; } }
|
进一步压缩, 因为我们要用到对角线, 那么在更新某个位置之前, 我们来存这个位置的值, 因为这个位置的值是下一个位置可能要用到的对角线的值. 这个模板也要记得.
时间复杂度: O(M * N)
空间复杂度: O(min(M, N))