把这两个字符串分别叫做 s 和 t,长度设为 n 和 m,和背包问题一样,子序列也相当于是考虑每个字母 选 还是 不选,从最后一个字母开始考虑的话,两两组合就有四种情况:不选 x 不选 y,不选 x 选 y,选 x 不选 y,选 x 选 y。
把问题一般化,考虑 s[i] 和 t[j] 选 或者 不选,表示的子问题就是 s 的前 i 个字母和 t 的前 j 个字母的最长公共子序列长度,根据选 还是 不选,可以得到以下三个子问题:
s 的前 i - 1 个字母和 t 的前 j - 1 个字母的最长公共子序列长度
s 的前 i - 1 个字母和 t 的前 j 个字母的最长公共子序列长度
s 的前 i 个字母和 t 的前 j - 1 个字母的最长公共子序列长度
做法
记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: deflongestCommonSubsequence(self, text1: str, text2: str) -> int: n = len(text1) m = len(text2)
@cache defdfs(i, j): if i < 0or j < 0: return0 if text1[i] == text2[j]: return dfs(i - 1, j - 1) + 1 returnmax(dfs(i - 1, j), dfs(i, j - 1)) return dfs(n - 1, m - 1)
时间复杂度和空间复杂度都是 O(mn)
二维数组
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: deflongestCommonSubsequence(self, text1: str, text2: str) -> int: n = len(text1) m = len(text2)
f = [[0] * (m + 1) for _ inrange(n + 1)] for i, x inenumerate(text1): for j, y inenumerate(text2): if x == y: f[i + 1][j + 1] = f[i][j] + 1 else: f[i + 1][j + 1] = max(f[i + 1][j], f[i][j + 1]) return f[n][m]
一个数组
递推公式:f[i + 1][j + 1] = f[i][j] + 1 if x == y else max(f[i][j + 1], f[i + 1][j])
从递推公式中可以看出,对于每个状态,只需要知道它左边,上面和左上这三个相邻方向的状态,如果只有一个一维数组,那么在算当前行的时候,它的左上这个状态就会被之间的计算给覆盖掉。可以用一个临时变量 pre 把它记录下来。
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: deflongestCommonSubsequence(self, text1: str, text2: str) -> int: n = len(text1) m = len(text2)
f = [0] * (m + 1) for i, x inenumerate(text1): pre = 0 for j, y inenumerate(text2): tmp = f[j + 1] f[j + 1] = pre + 1if x == y elsemax(f[j], f[j + 1]) pre = tmp return f[m]