题目:
给两个整数数组 nums1
和 nums2
,返回 两个数组中 公共的 、长度最长的子数组的长度 。
解法一(动态规划):
不妨设A数组为[1, 2, 3],B两数组为[1, 2, 4],我们比较计算A[0:]
与 B[0:]
最长公共前缀、 A[1:]
与 B[1:]
最长公共前缀以及 A[2:]
与 B[2:]
最长公共前缀时产生的。我们希望优化这一过程,使得任意一对 A[i] 和 B[j] 都只被比较一次。这样我们自然而然想到利用这一次的比较结果。如果 A[ i ] == B[ j ],那么我们知道 A[ i: ] 与 B[ j: ] 的最长公共前缀为 A[ i + 1: ] 与 B[ j + 1: ] 的最长公共前缀的长度加一,否则我们知道 A[ i: ] 与 B[ j: ] 的最长公共前缀为零。
这样我们就可以提出动态规划的解法:令 dp[ i ][ j ] 表示 A[ i: ] 和 B[ j: ] 的最长公共前缀,那么答案即为所有 dp[ i ][ j ] 中的最大值。如果 A[ i ] == B[ j ],那么 dp[ i ][ j ] = dp[ i + 1][ j + 1] + 1,否则 dp[ i ][ j ] = 0。考虑到这里 dp[ i ][ j ] 的值从 dp[ i + 1][ j + 1] 转移得到,所以我们需要倒过来,首先计算 dp[ len(A) - 1][ len(B) - 1],最后计算 dp[ 0 ][ 0 ]。如下为实现代码:
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int n = A.size(), m = B.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
int ans = 0;
//倒序访问,遍历所有的dp[i,j]的值,计算最大的dp[i,j]值即为最终的结果
//如果A[i]==B[j],则dp[i][j]=dp[i+1][j+1]+1,动态规划转移的思想
for (int i = n - 1; i >= 0; i--) {
for (int j = m - 1; j >= 0; j--) {
dp[i][j] = A[i] == B[j] ? dp[i + 1][j + 1] + 1 : 0;
ans = max(ans, dp[i][j]);
}
}
return ans;
}
};
时间复杂度: O(N×M)。空间复杂度: O(N×M)。
解法二(滑动窗口):
我们注意到之所以两位置会比较多次,是因为重复子数组在两个位置中的位置可能不同。以 A = [3, 6, 1, 2, 4]
,B = [7, 1, 2, 9]
为例,它们的最长重复子数组是 [1, 2]
,在 A
与 B
中的开始位置不同。
如果知道了开始的位置,我们就可以根据它们将A和B进行【对齐】,此时,最长重复子数组在A和B中的开始位置相同,我们就可以对两个数组进行一次遍历,得到子数组的长度。
我们可以枚举A和B所有的对齐方式。对齐的方式有两类:第一类为A不变,B的首元素与A中的某个元素对齐;第二类为B不变,A的首元素与B中的某个元素对齐。对于每一种对齐方式,我们计算它们相对位置相同的重复子数组即可。
class Solution {
public:
int maxLength(vector<int>& A, vector<int>& B, int addA, int addB, int len) {
int ret = 0, k = 0;
for (int i = 0; i < len; i++) {
if (A[addA + i] == B[addB + i]) {
k++;
} else {
k = 0;
}
ret = max(ret, k);
}
return ret;
}
int findLength(vector<int>& A, vector<int>& B) {
int n = A.size(), m = B.size();
int ret = 0;
for (int i = 0; i < n; i++) {
int len = min(m, n - i);
int maxlen = maxLength(A, B, i, 0, len);
ret = max(ret, maxlen);
}
for (int i = 0; i < m; i++) {
int len = min(n, m - i);
int maxlen = maxLength(A, B, 0, i, len);
ret = max(ret, maxlen);
}
return ret;
}
};
时间复杂度:O(( N + M )×min( N , M ))。空间复杂度:O(1)。
笔者小记:
1、暴力枚举方法的时间复杂度为O(n^3)过长,可以通过使用动态规划思想或滑动窗口思想并结合合适的数组结构,以降低代码的时间复杂度,目的是尽可能减少数组元素的重复遍历访问情况。