容易想到一个暴力解法,即枚举数组 A 中的起始位置 i 与数组 B 中的起始位置 j,然后计算 A[i:] 与 B[j:] 的最长公共前缀 k。最终答案即为所有的最长公共前缀的最大值。
这里借用了 Python 表示数组的方法,A[i:] 表示数组 A 中索引 i 到数组末尾的范围对应的子数组。
这个过程的伪代码如下:
1 2 3 4 5 6 7 8 9 10
ans = 0 for i in [0 .. A.length - 1] for j in [0 .. B.length - 1] k = 0 while (A[i+k] == B[j+k]) do # and i+k < A.length etc. k += 1 end while ans = max(ans, k) end for end for
funcfindLength(A []int, B []int)int { n, m := len(A), len(B) dp := make([][]int, n + 1) for i := 0; i < len(dp); i++ { dp[i] = make([]int, m + 1) } ans := 0 for i := n - 1; i >= 0; i-- { for j := m - 1; j >= 0; j-- { if A[i] == B[j] { dp[i][j] = dp[i + 1][j + 1] + 1 } else { dp[i][j] = 0 } if ans < dp[i][j] { ans = dp[i][j] } } } return ans }
[sol1-C]
1 2 3 4 5 6 7 8 9 10 11 12
intfindLength(int* A, int ASize, int* B, int BSize) { int dp[ASize + 1][BSize + 1]; memset(dp, 0, sizeof(dp)); int ans = 0; for (int i = ASize - 1; i >= 0; i--) { for (int j = BSize - 1; j >= 0; j--) { dp[i][j] = A[i] == B[j] ? dp[i + 1][j + 1] + 1 : 0; ans = fmax(ans, dp[i][j]); } } return ans; }
复杂度分析
时间复杂度: O(N \times M)。
空间复杂度: O(N \times M)。
N 表示数组 A 的长度,M 表示数组 B 的长度。
空间复杂度还可以再优化,利用滚动数组可以优化到 O(\min(N,M))。
方法二:滑动窗口
思路及算法
我们注意到之所以两位置会比较多次,是因为重复子数组在两个数组中的位置可能不同。以 A = [3, 6, 1, 2, 4], B = [7, 1, 2, 9] 为例,它们的最长重复子数组是 [1, 2],在 A 与 B 中的开始位置不同。
但如果我们知道了开始位置,我们就可以根据它们将 A 和 B 进行「对齐」,即:
1 2 3
A = [3, 6, 1, 2, 4] B = [7, 1, 2, 9] ↑ ↑
此时,最长重复子数组在 A 和 B 中的开始位置相同,我们就可以对这两个数组进行一次遍历,得到子数组的长度,伪代码如下:
1 2 3 4 5 6 7 8 9 10 11
ans = 0 len = min(A.length, B.length) k = 0 for i in [0 .. len - 1] do if (A[i] == B[i]) then k = k + 1 else k = 0 end if ans = max(ans, k) end for
classSolution { public: intmaxLength(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; } intfindLength(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; } };
classSolution { publicintfindLength(int[] A, int[] B) { intn= A.length, m = B.length; intret=0; for (inti=0; i < n; i++) { intlen= Math.min(m, n - i); intmaxlen= maxLength(A, B, i, 0, len); ret = Math.max(ret, maxlen); } for (inti=0; i < m; i++) { intlen= Math.min(n, m - i); intmaxlen= maxLength(A, B, 0, i, len); ret = Math.max(ret, maxlen); } return ret; }
publicintmaxLength(int[] A, int[] B, int addA, int addB, int len) { intret=0, k = 0; for (inti=0; i < len; i++) { if (A[addA + i] == B[addB + i]) { k++; } else { k = 0; } ret = Math.max(ret, k); } return ret; } }
classSolution: deffindLength(self, A: List[int], B: List[int]) -> int: defmaxLength(addA: int, addB: int, length: int) -> int: ret = k = 0 for i inrange(length): if A[addA + i] == B[addB + i]: k += 1 ret = max(ret, k) else: k = 0 return ret n, m = len(A), len(B) ret = 0 for i inrange(n): length = min(m, n - i) ret = max(ret, maxLength(i, 0, length)) for i inrange(m): length = min(n, m - i) ret = max(ret, maxLength(0, i, length)) return ret
funcfindLength(A []int, B []int)int { n, m := len(A), len(B) ret := 0 for i := 0; i < n; i++ { len := min(m, n - i) maxLen := maxLength(A, B, i, 0, len) ret = max(ret, maxLen) } for i := 0; i < m; i++ { len := min(n, m - i) maxLen := maxLength(A, B, 0, i, len) ret = max(ret, maxLen) } return ret }
funcmaxLength(A, B []int, addA, addB, lenint)int { ret, k := 0, 0 for i := 0; i < len; i++ { if A[addA + i] == B[addB + i] { k++ } else { k = 0 } ret = max(ret, k) } return ret }
funcmax(x, y int)int { if x > y { return x } return y }
funcmin(x, y int)int { if x < y { return x } return y }
intmaxLength(int* A, 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 = fmax(ret, k); } return ret; }
intfindLength(int* A, int ASize, int* B, int BSize) { int ret = 0; for (int i = 0; i < ASize; i++) { int len = fmin(BSize, ASize - i); int maxlen = maxLength(A, B, i, 0, len); ret = fmax(ret, maxlen); } for (int i = 0; i < BSize; i++) { int len = fmin(ASize, BSize - i); int maxlen = maxLength(A, B, 0, i, len); ret = fmax(ret, maxlen); } return ret; }
复杂度分析
时间复杂度: O((N + M) \times \min(N, M))。
空间复杂度: O(1)。
N 表示数组 A 的长度,M 表示数组 B 的长度。
方法三:二分查找 + 哈希
思路及算法
如果数组 A 和 B 有一个长度为 k 的公共子数组,那么它们一定有长度为 j <= k 的公共子数组。这样我们可以通过二分查找的方法找到最大的 k。
而为了优化时间复杂度,在二分查找的每一步中,我们可以考虑使用哈希的方法来判断数组 A 和 B 中是否存在相同特定长度的子数组。
注意到序列内元素值小于 100 ,我们使用 Rabin-Karp 算法来对序列进行哈希。具体地,我们制定一个素数 base,那么序列 S 的哈希值为:
这里借用了 Python 表示数组的方法,A[i:j] 表示数组 A 中索引 i 到索引 j - 1 的范围对应的子数组。
公式的含义为,删去最高位 S[0],其余位自动进一,并补上最低位 S[len]。
在二分查找的每一步中,我们使用哈希表分别存储这两个数组的所有长度为 len 的子数组的哈希值,将它们的哈希值进行比对,如果两序列存在相同的哈希值,则认为两序列存在相同的子数组。为了防止哈希碰撞,我们也可以在发现两个子数组的哈希值相等时,进一步校验它们本身是否确实相同,以确保正确性。但该方法在本题中很难发生哈希碰撞,因此略去进一步校验的部分。
classSolution { public: constint mod = 1000000009; constint base = 113; // 使用快速幂计算 x^n % mod 的值 longlongqPow(longlong x, longlong n){ longlong ret = 1; while (n) { if (n & 1) { ret = ret * x % mod; } x = x * x % mod; n >>= 1; } return ret; }
boolcheck(vector<int>& A, vector<int>& B, int len){ longlong hashA = 0; for (int i = 0; i < len; i++) { hashA = (hashA * base + A[i]) % mod; } unordered_set<longlong> bucketA; bucketA.insert(hashA); longlong mult = qPow(base, len - 1); for (int i = len; i < A.size(); i++) { hashA = ((hashA - A[i - len] * mult % mod + mod) % mod * base + A[i]) % mod; bucketA.insert(hashA); } longlong hashB = 0; for (int i = 0; i < len; i++) { hashB = (hashB * base + B[i]) % mod; } if (bucketA.count(hashB)) { returntrue; } for (int i = len; i < B.size(); i++) { hashB = ((hashB - B[i - len] * mult % mod + mod) % mod * base + B[i]) % mod; if (bucketA.count(hashB)) { returntrue; } } returnfalse; }
intfindLength(vector<int>& A, vector<int>& B){ int left = 1, right = min(A.size(), B.size()) + 1; while (left < right) { int mid = (left + right) >> 1; if (check(A, B, mid)) { left = mid + 1; } else { right = mid; } } return left - 1; } };
defcheck(length: int) -> bool: hashA = 0 for i inrange(length): hashA = (hashA * base + A[i]) % mod bucketA = {hashA} mult = pow(base, length - 1, mod) for i inrange(length, len(A)): hashA = ((hashA - A[i - length] * mult) * base + A[i]) % mod bucketA.add(hashA) hashB = 0 for i inrange(length): hashB = (hashB * base + B[i]) % mod if hashB in bucketA: returnTrue for i inrange(length, len(B)): hashB = ((hashB - B[i - length] * mult) * base + B[i]) % mod if hashB in bucketA: returnTrue
returnFalse
left, right = 0, min(len(A), len(B)) ans = 0 while left <= right: mid = (left + right) // 2 if check(mid): ans = mid left = mid + 1 else: right = mid - 1 return ans
funcfindLength(A []int, B []int)int { check := func(length int)bool { hashA := 0 for i := 0; i < length; i++ { hashA = (hashA * base + A[i]) % mod } bucketA := map[int]bool{hashA: true} mult := qPow(base, length - 1) for i := length; i < len(A); i++ { hashA = ((hashA - A[i - length] * mult % mod + mod) % mod * base + A[i]) % mod bucketA[hashA] = true }
hashB := 0 for i := 0; i < length; i++ { hashB = (hashB * base + B[i]) % mod } if bucketA[hashB] { returntrue } for i := length; i < len(B); i++ { hashB = ((hashB - B[i - length] * mult % mod + mod) % mod * base + B[i]) % mod if bucketA[hashB] { returntrue } } returnfalse }
left, right := 1, min(len(A), len(B)) + 1 for left < right { mid := (left + right) >> 1 if check(mid) { left = mid + 1 } else { right = mid } } return left - 1 } funcqPow(x, n int)int { ret := 1 for n != 0 { if n & 1 != 0 { ret = ret * x % mod } x = x * x % mod n >>= 1 } return ret }
funcmin(x, y int)int { if x < y { return x } return y }
复杂度分析
时间复杂度:O\big((M+N) \log{(\min(M, N))}\big)。
空间复杂度:O(N)。
N 表示数组 A 的长度,M 表示数组 B 的长度。二分查找为对数时间复杂度,计算哈希值的时间复杂度为 O(M+N),哈希检测的时间复杂度为 O(1)。