dp 数组含义定义二维数组 dp:
注意这里的 i 和 j 表示的是“前几个元素”,不是数组下标。
例如:
表示:
这道题要求的是“子数组”,子数组必须连续。
所以 dp[i][j] 只关心两个数组当前结尾位置能向前连续匹配多长,而不是像最长公共子序列那样可以跳着选。
题目要求整个数组中的最长重复子数组长度,所以最终答案需要在计算过程中维护一个最大值:
计算 dp[i][j] 时,需要比较两个当前结尾元素:
如果:
说明这两个元素可以作为重复子数组的最后一个元素。
此时重复子数组的长度,等于前一个位置的连续匹配长度再加 1:
也就是:
如果:
说明以这两个元素结尾时,无法形成公共的连续子数组。
所以:
这也是“最长重复子数组”和“最长公共子序列”的重要区别。
子数组必须连续,一旦当前结尾元素不相等,连续匹配就断了,不能从 dp[i - 1][j] 或 dp[i][j - 1] 转移。
完整转移方程:
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。
根据状态转移方程:
也就是当前格子只依赖左上方的状态。
所以按行从上到下、每一行从左到右遍历即可:
这样计算 dp[i][j] 时,左上方的 dp[i - 1][j - 1] 一定已经计算完成。
dp 数组以:
为例。
行表示 nums1 的前缀,列表示 nums2 的前缀:
nums1 \ nums2 | [] | 3 | 2 | 1 | 4 | 7 |
|---|---|---|---|---|---|---|
[] | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | |||||
2 | 0 | |||||
3 | 0 | |||||
2 | 0 | |||||
1 | 0 |
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] = 0nums1 \ nums2 | [] | 3 | 2 | 1 | 4 | 7 |
|---|---|---|---|---|---|---|
[] | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 1 | 0 | 0 |
2 | 0 | |||||
3 | 0 | |||||
2 | 0 | |||||
1 | 0 |
最终 dp 数组为:
nums1 \ nums2 | [] | 3 | 2 | 1 | 4 | 7 |
|---|---|---|---|---|---|---|
[] | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 1 | 0 | 0 |
2 | 0 | 0 | 1 | 0 | 0 | 0 |
3 | 0 | 1 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 2 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 3 | 0 | 0 |
其中最大值是:
对应的最长重复子数组是:
所以返回 3。
O(m * n),其中 m 是 nums1 的长度,n 是 nums2 的长度。O(m * n),需要一个二维 dp 数组保存状态。