718.最长重复子数组
1. dp 数组含义
定义二维数组 dp:
注意这里的 i 和 j 表示的是“前几个元素”,不是数组下标。
例如:
表示:
这道题要求的是“子数组”,子数组必须连续。
所以 dp[i][j] 只关心两个数组当前结尾位置能向前连续匹配多长,而不是像最长公共子序列那样可以跳着选。
题目要求整个数组中的最长重复子数组长度,所以最终答案需要在计算过程中维护一个最大值:
2. 确定状态转移方程
计算 dp[i][j] 时,需要比较两个当前结尾元素:
当前元素相同
如果:
说明这两个元素可以作为重复子数组的最后一个元素。
此时重复子数组的长度,等于前一个位置的连续匹配长度再加 1:
也就是:
当前元素不同
如果:
说明以这两个元素结尾时,无法形成公共的连续子数组。
所以:
这也是“最长重复子数组”和“最长公共子序列”的重要区别。
子数组必须连续,一旦当前结尾元素不相等,连续匹配就断了,不能从 dp[i - 1][j] 或 dp[i][j - 1] 转移。
完整转移方程:
3. dp 数组如何初始化
因为 dp[i][j] 表示以某两个元素结尾的最长重复子数组长度,所以需要处理空前缀。
dp[0][j] = 0:
dp[i][0] = 0:
因此可以创建一个 (nums1.length + 1) * (nums2.length + 1) 的二维数组,并全部初始化为 0:
这样后续计算 dp[i][j] 时,可以安全访问 dp[i - 1][j - 1]。
如果 nums1 或 nums2 是空数组,最终答案自然就是 0。
4. 确定遍历顺序
根据状态转移方程:
也就是当前格子只依赖左上方的状态。
所以按行从上到下、每一行从左到右遍历即可:
这样计算 dp[i][j] 时,左上方的 dp[i - 1][j - 1] 一定已经计算完成。
5. 举例打印 dp 数组
以:
为例。
行表示 nums1 的前缀,列表示 nums2 的前缀:
填第 1 行:nums1[0] = 1
和 nums2 中每个元素比较:
1 !== 3,dp[1][1] = 01 !== 2,dp[1][2] = 01 === 1,dp[1][3] = dp[0][2] + 1 = 11 !== 4,dp[1][4] = 01 !== 7,dp[1][5] = 0
填完整张表
最终 dp 数组为:
其中最大值是:
对应的最长重复子数组是:
所以返回 3。
6. 代码实现
7. 易错点:误用最长公共子序列的状态转移
当两个当前元素不相等时,容易错误地写成:
这个写法会保留之前的匹配结果,相当于允许跳过 nums1[i - 1] 或 nums2[j - 1],因此求出的是“最长公共子序列”,而不是“最长重复子数组”。
两者的区别是:
- 子数组必须连续;
- 子序列可以跳过中间的元素。
例如:
两个数组中都能按顺序找到 1 和 3,所以公共子序列 [1, 3] 的长度是 2。但是 1 和 3 在两个数组中都不连续,因此它们不能组成重复子数组,最长重复子数组只能是 [1] 或 [3],答案是 1。
本题中 dp[i][j] 的准确含义是:
因此,当前两个元素不相等时,以它们为结尾的连续匹配已经中断,必须归零:
可以用一句话记忆:
另外,创建 dp 时已经用 fill(0) 将所有位置初始化为 0,所以不需要再单独遍历并初始化第一行和第一列。
复杂度分析
- 时间复杂度:
O(m * n),其中m是nums1的长度,n是nums2的长度。 - 空间复杂度:
O(m * n),需要一个二维dp数组保存状态。


