718. 最长重复子数组
考点
- LCS
思路
一、问题概述
给定两个整数数组 \[ \text{nums}_1 = [a_1, a_2, \dots, a_{n_1}], \quad \text{nums}_2 = [b_1, b_2, \dots, b_{n_2}] \] 要求找出 同时出现在两个数组中的最长连续子数组 的长度。
注意关键词是 连续(subarray),而不是子序列(subsequence)。
二、动态规划建模思想
2.1 与 LCS 的本质区别
- 最长公共子序列(LCS):允许跳过元素
- 最长重复子数组:必须连续,一旦断开,长度直接归零
因此,本题的 DP 状态 不允许继承横向或纵向的最大值,只能从 左上角对角线 转移。
三、状态定义(State Definition)
定义二维 DP 数组: \[ f[i][j] \] 表示:
以 \[ \text{nums}_1[i-1] \quad \text{和} \quad \text{nums}_2[j-1] \] 作为结尾元素的最长公共连续子数组长度
换言之,f[i][j] 描述的是:
“必须以这两个位置结尾”的最长长度。
四、初始值设计(Initialization)
4.1 边界状态
当任意一个数组长度为 0 时,不存在公共子数组: \[ f[0][j] = 0,\quad f[i][0] = 0 \]
4.2 实现方式说明
在代码中通过:
1 | memset(f, 0, sizeof(f)); |
直接将整个 DP 数组初始化为 0,从而隐式完成所有边界条件。
五、状态转移方程(Transition)
5.1 元素不相等
若: \[ \text{nums}_1[i-1] \ne \text{nums}_2[j-1] \] 则连续性被破坏: \[ f[i][j] = 0 \]
5.2 元素相等(核心转移)
若: \[ \text{nums}_1[i-1] = \text{nums}_2[j-1] \] 则可以在前一个连续匹配的基础上延长: \[ \boxed{ f[i][j] = f[i-1][j-1] + 1 } \] 这是本题 唯一有效的状态转移路径。
六、答案的获取方式(Answer)
最长重复子数组 不一定以数组末尾结束,因此答案为: \[ \text{ans} = \max_{1 \le i \le n_1,\; 1 \le j \le n_2} f[i][j] \] 实现中需在 DP 过程中维护一个全局最大值。
七、二维 DP 完整逻辑总结
- 初始化所有状态为 0
- 双重循环枚举
(i, j) - 仅在元素相等时进行对角线转移
- 实时更新全局最大值
时间复杂度: \[ O(n_1 \times n_2) \] 空间复杂度: \[ O(n_1 \times n_2) \]
八、滚动数组优化(空间压缩)
8.1 优化依据
状态转移只依赖: \[ f[i][j] \leftarrow f[i-1][j-1] \] 因此可以将二维数组压缩为一维。
8.2 一维状态定义
令: \[ f[j] = \text{当前处理到 } i \text{ 时的 } f[i][j] \]
8.3 反向遍历的必要性
为保证 f[j-1] 仍表示上一行的 f[i-1][j-1],
j 必须 从大到小遍历。
8.4 一维状态转移
\[ f[j] = \begin{cases} f[j-1] + 1, & \text{nums}_1[i-1] = \text{nums}_2[j-1] \\ 0, & \text{otherwise} \end{cases} \]
九、复杂度对比
| 实现方式 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 二维 DP | \(O(n_1 n_2)\) | \(O(n_1 n_2)\) |
| 滚动数组 | \(O(n_1 n_2)\) | \(O(n_2)\) |
十、AC代码
10.1 二维 DP 版本
1 | class Solution { |
10.2 滚动数组(一维 DP)版本
1 | class Solution { |