在问 · 算法题解

718. 最长重复子数组

中等动态规划二维动态规划力扣原题 ↗

题目描述

输入两个整数数组 nums1 和 nums2。

子数组指数组中连续的一段。找出同时出现在两个数组中的最长子数组,返回它的长度。没有公共元素时返回 0。

示例 1
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3

解释:两个数组都含有连续片段 [3,2,1],长度为 3。

示例 2
输入: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

解题思路:二维动态规划

dp[i][j] 表示以 nums1[i-1] 和 nums2[j-1] 结尾的最长公共子数组长度。两个结尾元素相等时,可以接在左上角那段的后面:dp[i][j] = dp[i-1][j-1] + 1;不相等时为 0,因为子数组必须连续。答案是整张表的最大值。在表中,一段公共子数组就是一条斜线。

为什么正确

计算 dp[i][j] 时,左上角 dp[i-1][j-1] 已经算好;best 是已算格子中的最大值。

复杂度分析

时间 O(mn),空间 O(mn)(可以压缩到 O(n))。

易错点

这是子数组(连续),不是子序列:不相等时是 0,而不是 max(dp[i-1][j], dp[i][j-1])。答案也不是 dp[m][n],而是全表最大值。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        m, n = len(nums1), len(nums2)
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        best = 0
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if nums1[i - 1] == nums2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                    best = max(best, dp[i][j])
        return best

载入编辑器,自己跑一遍 →

其他解法

动态规划最直观;滑动对齐把空间降到 O(1);二分 + 滚动哈希把时间降到近线性,但依赖哈希不冲突。

解法 2:滑动对齐 时间 O((m+n)·min(m,n)),空间 O(1)

把两个数组错开不同的偏移量对齐。对每种对齐方式,同时扫描重叠部分,统计连续相等元素的最长长度。nums1 相对 nums2 右移和左移的所有偏移量都要枚举。

时间与动态规划同阶,但只用 O(1) 额外空间。代码要处理两个方向的偏移,比动态规划多一些边界细节。

class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        m, n = len(nums1), len(nums2)

        def scan(i, j, length):
            best = cur = 0
            for k in range(length):
                if nums1[i + k] == nums2[j + k]:
                    cur += 1
                    best = max(best, cur)
                else:
                    cur = 0
            return best

        best = 0
        for i in range(m):
            best = max(best, scan(i, 0, min(m - i, n)))
        for j in range(n):
            best = max(best, scan(0, j, min(n - j, m)))
        return best

解法 3:二分 + 滚动哈希 时间 O((m+n)·log min(m,n)),空间 O(m)

如果长度为 L 的公共子数组存在,那么更短的也存在,所以可以二分答案 L。检查时用滚动哈希算出 nums1 所有长度为 L 的子数组的哈希值放入集合,再滚动扫描 nums2,看是否有哈希值命中。

时间复杂度最低,适合数组很长的情况。哈希有极小的冲突概率,需要选好底数和模数。

class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        MOD, BASE = 10**9 + 9, 113

        def hashes(a, L, pw):
            h = 0
            for k in range(L):
                h = (h * BASE + a[k]) % MOD
            out = [h]
            for i in range(L, len(a)):
                h = ((h - a[i - L] * pw) * BASE + a[i]) % MOD
                out.append(h)
            return out

        def check(L):
            if L == 0:
                return True
            pw = pow(BASE, L - 1, MOD)
            seen = set(hashes(nums1, L, pw))
            return any(h in seen for h in hashes(nums2, L, pw))

        lo, hi = 0, min(len(nums1), len(nums2))
        while lo < hi:
            mid = (lo + hi + 1) // 2
            if check(mid):
                lo = mid
            else:
                hi = mid - 1
        return lo

动态规划 专题的其他题目

查看 动态规划 全部题目 →