题目
给两个整数数组 A
和 B
,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
提示:
1 <= len(A), len(B) <= 1000
0 <= A[i], B[i] < 100
解题思路
解法一:动态规划
dp[i+1][j+1] 表示以 A[i]、B[j] 结尾长度分别为 i+1、j+1 的序列的公共子数组长度,若 A[i] == B[j],则 dp[i+1][j+1] = dp[i][j] + 1;
若 A[i] !=B[j],则 dp[i+1][j+1] = 0。
初始化 dp[0][0] = 0,表示长度为0的序列的公共子数组长度为0。
复杂度分析:
时间复杂度:O(N×M)。
空间复杂度:O(N×M)。
解法二:滑动窗口
如果知道了重复子数组在两个数组中的开始位置,我们就可以根据它们将 A 和 B 进行「对齐」,然后对这两个数组进行一次遍历,得到子数组的长度,最后取全局最大值即可。
我们可以枚举 A 和 B 所有的对齐方式。对齐的方式有两类:第一类为 A 不变,B 的首元素与 A 中的某个元素对齐;第二类为 B 不变,A 的首元素与 B 中的某个元素对齐。对于每一种对齐方式,我们计算它们相对位置相同的重复子数组即可。
复杂度分析:
时间复杂度:O((M+N)×min(M,N))。
空间复杂度:O(1)。
代码
解法一:动态规划
class Solution {
public int findLength(int[] A, int[] B) {
int m = A.length;
int n = B.length;
int[][] dp = new int[m+1][n+1]; // 初始化全为0
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(A[i] == B[j]){
dp[i+1][j+1] = dp[i][j] + 1;
}else{
dp[i+1][j+1] = 0; // 这步不写也可以
}
}
}
int res = 0;
for(int i=0; i<=m; i++){
for(int j=0; j<=n; j++){
res = Math.max(res, dp[i][j]);
}
}
return res;
}
}
解法二:滑动窗口
class Solution {
public int findLength(int[] A, int[] B) {
int m = A.length;
int n = B.length;
int res = 0;
// 保持 A 不动,B 的首元素与 A 中的某个元素对齐
for(int i=0; i<m; i++){
// 可以滑动的距离
int len = Math.min(m-i, n);
int maxLen = maxLength(A, B, i, 0, len);
res = Math.max(maxLen, res);
}
// 保持 B 不动,A 的首元素与 B 中的某个元素对齐
for(int i=0; i<n; i++){
int len = Math.min(m, n-i);
int maxLen = maxLength(A, B, 0, i, len);
res = Math.max(maxLen, res);
}
return res;
}
private int maxLength(int[] A, int[] B, int startA, int startB, int len){
int res = 0;
int cnt = 0;
for(int i=0; i<len; i++){
if(A[startA+i] == B[startB+i]){
cnt++;
}else{
cnt = 0;
}
res = Math.max(res, cnt);
}
return res;
}
}