题目描述
给两个整数数组 nums1
和 nums2
,返回 两个数组中 公共的 、长度最长的子数组的长度。
样例
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1]。
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5
提示
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 100
算法
(动态规划) O(n2)
- 设 f(i,j) 表示以
A[i]
和B[j]
结尾的最长公共子数组的长度,有效下标从 1 开始。 - 初始化 f(i,0)=0,f(0,j)=0,其余待定。
- 转移时,如果
A
数组的第 i 个字符等于B
数字的第 j 个字符,则转移 f(i,j)=f(i−1,j−1)+1;否则转移 f(i,j)=0。 - 最终答案为 max。
- 可以通过转移顺序调整优化掉第一维的状态存储。
时间复杂度
- 状态数为 O(nm),每次转移的时间复杂度为 O(1),故总时间复杂度为 O(nm)。
空间复杂度
- 优化后,空间复杂度为 O(m)。
C++ 代码(空间优化前)
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
const int n = nums1.size(), m = nums2.size();
vector<vector<int>> f(n + 1, vector<int>(m + 1, 0));
int ans = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (nums1[i - 1] == nums2[j - 1])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = 0;
ans = max(ans, f[i][j]);
}
return ans;
}
};
C++ 代码(空间优化后)
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
const int n = nums1.size(), m = nums2.size();
vector<int> f(m + 1, 0);
int ans = 0;
for (int i = 1; i <= n; i++)
for (int j = m; j >= 1; j--) { // 注意转移顺序
if (nums1[i - 1] == nums2[j - 1])
f[j] = f[j - 1] + 1;
else
f[j] = 0;
ans = max(ans, f[j]);
}
return ans;
}
};
感觉把f[i][j]定义成以A[i]结尾和B[j]结尾的公共子数组的最大长度比较好
否则定义成考察了A数组的前i个数字和 B 数组的前j个数字所能构成的最长公共子数组的长度,即便A[i] != B[j],但是f[i][j] = 0不就表示A的前i个数字和B的前j个数字中没有公共的子数组么,感觉有点歧义
已修正
那个空间优化是怎么优化的?感觉有点不太理解
(i, j) 只依赖于 (i - 1, j - 1),可以通过倒序遍历第二维优化掉第一维的存储
请教一下大佬 为什么要倒序遍历呢 这个地方很迷
可以参考学习下01背包空间优化倒序枚举的原因。假如采取了正向枚举,那更新 f(i, j) 时 会依赖 f(i, j - 1),但需要依赖的是 f(i - 1, j - 1)。倒序枚举保证被依赖的值都是上一轮更新的结果,不是本轮更新的结果。
感谢!
这里,二维的话,可以不加
else f[i][j] = 0
,但是空间优化后,必须加else f[j] = 0
,这个是为什么?因为需要设置下一轮 i 所对应的
f[j]
为 0,二维的不加f[i][j] = 0
是因为默认已经是 0 了。周二地平线清华宣讲会原题
这道题其实可以用后缀数组搞