有两个序列 A 和 B, 问最长的公共序列的长度是?
(如果问子串就是换成字符串做,一样的做法就行)
DP
时间复杂度: O ( N × M ) O(N \times M) O(N×M)
空间复杂度: O ( N × M ) O(N \times M) O(N×M)
一维数组可以将空间复杂度降至 O ( m i n ( N , M ) ) O(min(N,M)) O(min(N,M))
定义 same(i,j):A[ i ] = B[ j ] 时取1,否则取0
所以两个序列可以用same矩阵表示:
A = [1,2,3,2,1] B = [3,2,1,4,7]
则,最长公共序列长度 = 最长斜线长度
所以递推公式:dp [ i ] [ j ]:A[ i ] 和 B [ j ] 的最长公共序列长度
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
−
1
]
+
1
A
[
i
]
=
B
[
j
]
0
A
[
i
]
≠
B
[
j
]
dp [ i ] [ j ]=\left\{ \begin{aligned} dp [ i-1 ] [ j-1 ] +1 & & \ A[ i ] = B [ j ] \\ 0 & & \ A[ i ] ≠B [ j ]\\ \end{aligned} \right.
dp[i][j]={dp[i−1][j−1]+10 A[i]=B[j] A[i]=B[j]
代码从1开始是为了避免数组越界和初始化0下标,所以代码中的 dp[ i ][ j ] 实际扩展了一个单位
public int findLength(int[] A, int[] B) {
int ans = 0;
int[][] dp = new int[A.length + 1][B.length + 1];
for (int i = 1; i <= A.length; ++i) {
for (int j = 1; j <= B.length; ++j) {
if (A[i - 1] == B[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = 0;
ans = Math.max(ans, dp[i][j]);
}
}
return ans;
}
一维数组优化:
dp是从前往后的过程,所以第二重循环倒序才能不影响结果
public int findLength(int[] A, int[] B) {
int ans = 0;
int[] dp = new int[B.length + 1];
for (int i = 1; i <= A.length; i++) {
for (int j = B.length; j >= 1; j--) {
if (A[i - 1] == B[j - 1])
dp[j] = dp[j - 1] + 1;
else
dp[j] = 0;
ans = Math.max(ans, dp[j]);
}
}
return ans;
}
滑动窗口
时间复杂度:
O
(
(
N
+
M
)
×
min
(
N
,
M
)
)
O((N + M) \times \min(N, M))
O((N+M)×min(N,M))
空间复杂度:
O
(
1
)
O(1)
O(1)
参考 LeetCode:
想象两把尺子,错开之后比较相同的部分
public int findLength(int[] A, int[] B) {
return A.length < B.length ? findMaxLength(A, B) : findMaxLength(B, A);
}
public int findMaxLength(int[] A, int[] B) {
int ans = 0;
int n = A.length, m = B.length;
//B一步步滑动至A和B右对齐
for (int len = 1; len <= n; ++len)
ans = Math.max(ans, maxLength(A, 0, B, m - len, len));
//B比A长,继续滑动至A和B左对齐
for (int j = m - n; j >= 0; j--)
ans = Math.max(ans, maxLength(A, 0, B, j, n));
//从对齐位置开始继续滑动至结束
for (int i = 1; i < n; ++i)
ans = Math.max(ans, maxLength(A, i, B, 0, n - i));
return ans;
}
public int maxLength(int[] A, int i, int[] B, int j, int len) {
/*
* A[i~i+len-1]和 B[j~j+len-1]的最长公共长度
*/
int cnt = 0, ans = 0;
for (int k = 0; k < len; ++k) {
if (A[i + k] == B[j + k]) cnt++;
else if (cnt > 0) {
ans = Math.max(ans, cnt);
cnt = 0;
}
}
return Math.max(ans, cnt);
}