leetcode-718. 最长重复子数组
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
动态规划
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int lenA = A.size(), lenB = B.size();
vector<vector<int>> dp(lenA + 1, vector<int>(lenB + 1, 0));
int ret = 0;
// 从后往前,dp[lenA][x] = 0 dp[x][lenB] = 0,初始状态在最后一行和最后一列
for (int i = lenA - 1; i >= 0; i--) {
for (int j = lenB - 1; j >= 0; j--) {
dp[i][j] = (A[i] == B[j] ? dp[i + 1][j + 1] + 1 : 0);
ret = max(ret, dp[i][j]);
}
}
return ret;
}
};
时间复杂度: O(N×M)
空间复杂度: O(N×M)
【我自己实现的】
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
int ret = 0;
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
for(int i = 1; i <= len1; i++) {
for(int j = 1; j <= len2; j++) {
if(nums1[i-1] == nums2[j-1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
ret = max(ret, dp[i][j]);
} else {
dp[i][j] = 0;
}
}
}
return ret;
}
};
【我自己的优化】
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
int ret = 0;
vector<int> dp(len2 + 1, 0);
for(int i = 1; i <= len1; i++) {
// 二维的时候不管遍历顺序怎么变,都能通过 dp[i][j] 找到 dp[i - 1][j - 1]
// 一维的时候,j 从后往前遍历时,对于当前次的dp[j] (即dp[i][j]),
// 才能找到前一次for循环的dp[j-1],(即dp[i-1][j-1])
for(int j = len2; j > 0; j--) {
if(nums1[i-1] == nums2[j-1]) {
dp[j] = dp[j - 1] + 1;
ret = max(ret, dp[j]);
} else {
dp[j] = 0;
}
}
}
return ret;
}
};
滑窗
class Solution {
public:
// A 从 addA 开始,B 从 addB 开始时,查找长度为 len 的两个数对的最长连续数组
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;
// B的头不变,A的头往前走
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);
}
// A的头不变,B的头往前走
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)
N 表示数组 A 的长度,M 表示数组 B 的长度。
字符串哈希
class Solution {
public:
const int mod = 1e9+7; // 1 <= nums1.length, nums2.length <= 1000
const int base = 113; // 0 <= nums1[i], nums2[i] <= 100,涵盖所有元素的质数即可
// 使用快速幂计算 x^n % mod 的值
long long qPow(long long x, long long n) {
long long ret = 1;
while (n) {
if (n & 1) {
ret = ret * x % mod;
}
x = x * x % mod;
n >>= 1;
}
return ret;
}
bool check(vector<int>& A, vector<int>& B, int len) {
long long hashA = 0;
for (int i = 0; i < len; i++) {
hashA = (hashA * base + A[i]) % mod;
}
unordered_set<long long> bucketA;
bucketA.insert(hashA);
long long mult = qPow(base, len);
for (int i = len; i < A.size(); i++) {
// 此处推动见最下部分
hashA = ((base*hashA)%mod - (A[i - len]*mult)%mod + A[i]%mod + mod)%mod;
// hashA = ((hashA - A[i - len] * mult % mod + mod) % mod * base + A[i]) % mod;
bucketA.insert(hashA);
}
long long hashB = 0;
for (int i = 0; i < len; i++) {
hashB = (hashB * base + B[i]) % mod;
}
if (bucketA.count(hashB)) {
return true;
}
for (int i = len; i < B.size(); i++) {
hashB = ((base*hashB)%mod - (B[i - len]*mult)%mod + B[i]%mod + mod)%mod;
// hashB = ((hashB - B[i - len] * mult % mod + mod) % mod * base + B[i]) % mod;
if (bucketA.count(hashB)) {
return true;
}
}
return false;
}
int findLength(vector<int>& A, vector<int>& B) {
int left = 1, right = min(A.size(), B.size());
while (left <= right) {
int mid = (left + right) >> 1;
// 不断检查 A 和 B 中长度为 mid 的子串是否相同
if (check(A, B, mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left - 1;
}
};
二分查找的每一步中,使用哈希表分别存储这两个数组的所有长度为 len 的子数组的哈希值,
将它们的哈希值进行比对,如果两序列存在相同的哈希值,则认为两序列存在相同的子数组。
防止哈希碰撞,可以在发现两个子数组的哈希值相等时,校验它们本身是否相同。
或者采用双hash。
A串的0~len-1个子串的 hash
hash(A[0,len)) = {A[0]*base^(len-1) + A[1]*base^(len-2) + … + A[len-1]*base^0}%mod
A串的1~len个子串的 hash
hash(A[1,len+1)) = {A[1]*base^(len-1) + A[2]*base^(len-2) + … + A[len]*base^0}%mod
如何从 hash(A[0,len)) 推导出 hash(A[1,len+1))
hash(A[1,len+1)) - {A[len]*base^0}%mod
= {A[1]*base^(len-1) + A[2]*base^(len-2) + … + A[len-1]base^1}%mod
= {base(A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]base^0)}%mod
= {base(A[0]*base^(len-1) + A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]*base^0 - A[0]base^(len-1))}%mod
= {base(A[0]*base^(len-1) + A[1]*base^(len-2) + A[2]*base^(len-3) + … + A[len-1]*base^0)}%mod - {A[0]base^len}%mod
= {basehash(A[0,len))}%mod - {A[0]base^len}%mod
∴ hash(A[1,len+1))
= {basehash(A[0,len))}%mod - {A[0]*base^len}%mod + {A[len]base^0}%mod
= {basehash(A[0,len))}%mod - {A[0]*base^len}%mod + A[len]%mod