leetcode-master/problems/0718.最长重复子数组.md

428 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 718. 最长重复子数组
[力扣题目链接](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/)
给两个整数数组 A  B 返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
* A: [1,2,3,2,1]
* B: [3,2,1,4,7]
* 输出3
* 解释:长度最长的公共子数组是 [3, 2, 1] 。
提示:
* 1 <= len(A), len(B) <= 1000
* 0 <= A[i], B[i] < 100
## 思路
注意题目中说的子数组其实就是连续子序列
要求两个数组中最长重复子数组如果是暴力的解法 只需要先两层for循环确定两个数组起始位置然后再来一个循环可以是for或者while来从两个起始位置开始比较取得重复子数组的长度
本题其实是动规解决的经典题目我们只要想到 用二维数组可以记录两个字符串的所有比较情况这样就比较好推 递推公式了
动规五部曲分析如下
1. 确定dp数组dp table以及下标的含义
dp[i][j] 以下标i - 1为结尾的A和以下标j - 1为结尾的B最长重复子数组长度为dp[i][j]。 **特别注意** 以下标i - 1为结尾的A 标明一定是 以A[i-1]为结尾的字符串
此时细心的同学应该发现那dp[0][0]是什么含义呢总不能是以下标-1为结尾的A数组吧
其实dp[i][j]的定义也就决定着我们在遍历dp[i][j]的时候i j都要从1开始
那有同学问了我就定义dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B最长重复子数组长度不行么
行倒是行 但实现起来就麻烦一点需要单独处理初始化部分在本题解下面的拓展内容里我给出了 第二种 dp数组的定义方式所对应的代码和讲解大家比较一下就了解了
2. 确定递推公式
根据dp[i][j]的定义dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来
即当A[i - 1] 和B[j - 1]相等的时候dp[i][j] = dp[i - 1][j - 1] + 1;
根据递推公式可以看出遍历i j 要从1开始
3. dp数组如何初始化
根据dp[i][j]的定义dp[i][0] 和dp[0][j]其实都是没有意义的
但dp[i][0] 和dp[0][j]要初始值因为 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1;
所以dp[i][0] 和dp[0][j]初始化为0
举个例子A[0]如果和B[0]相同的话dp[1][1] = dp[0][0] + 1只有dp[0][0]初始为0正好符合递推公式逐步累加起来
4. 确定遍历顺序
外层for循环遍历A内层for循环遍历B
那又有同学问了外层for循环遍历B内层for循环遍历A不行么
也行一样的我这里就用外层for循环遍历A内层for循环遍历B了
同时题目要求长度最长的子数组的长度所以在遍历的时候顺便把dp[i][j]的最大值记录下来
代码如下
```CPP
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
```
5. 举例推导dp数组
拿示例1中A: [1,2,3,2,1]B: [3,2,1,4,7]为例画一个dp数组的状态变化如下
![718.最长重复子数组](https://img-blog.csdnimg.cn/2021011215282060.jpg)
以上五部曲分析完毕C++代码如下
```CPP
// 版本一
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
```
* 时间复杂度O(n × m)n 为A长度m为B长度
* 空间复杂度O(n × m)
## 滚动数组
在如下图中
![718.最长重复子数组](https://img-blog.csdnimg.cn/2021011215282060.jpg)
我们可以看出dp[i][j]都是由dp[i - 1][j - 1]推出那么压缩为一维数组也就是dp[j]都是由dp[j - 1]推出
也就是相当于可以把上一层dp[i - 1][j]拷贝到下一层dp[i][j]来继续用
**此时遍历B数组的时候就要从后向前遍历这样避免重复覆盖**
```CPP
// 版本二
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
vector<int> dp(vector<int>(B.size() + 1, 0));
int result = 0;
for (int i = 1; i <= A.size(); i++) {
for (int j = B.size(); j > 0; j--) {
if (A[i - 1] == B[j - 1]) {
dp[j] = dp[j - 1] + 1;
} else dp[j] = 0; // 注意这里不相等的时候要有赋0的操作
if (dp[j] > result) result = dp[j];
}
}
return result;
}
};
```
* 时间复杂度$O(n × m)$n 为A长度m为B长度
* 空间复杂度$O(m)$
## 拓展
前面讲了 dp数组为什么定义以下标i - 1为结尾的A和以下标j - 1为结尾的B最长重复子数组长度为dp[i][j]。
我就定义dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B最长重复子数组长度不行么
当然可以就是实现起来麻烦一些
如果定义 dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B那么 第一行和第一列毕竟要进行初始化如果nums1[i] nums2[0] 相同的话对应的 dp[i][0]就要初始为1 因为此时最长重复子数组为1 nums2[j] nums1[0]相同的话同理
所以代码如下
```CPP
// 版本三
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
// 要对第一行,第一列经行初始化
for (int i = 0; i < nums1.size(); i++) if (nums1[i] == nums2[0]) dp[i][0] = 1;
for (int j = 0; j < nums2.size(); j++) if (nums1[0] == nums2[j]) dp[0][j] = 1;
for (int i = 0; i < nums1.size(); i++) {
for (int j = 0; j < nums2.size(); j++) {
if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
```
大家会发现 这种写法 一定要多写一段初始化的过程。
而且为了让 `if (dp[i][j] > result) result = dp[i][j];` 收集到全部结果两层for训练一定从0开始遍历这样需要加上 `&& i > 0 && j > 0`的判断。
相对于版本一来说还是多写了不少代码。而且逻辑上也复杂了一些。 优势就是dp数组的定义更直观一点。
## 其他语言版本
Java
```java
// 版本一
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int result = 0;
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
for (int i = 1; i < nums1.length + 1; i++) {
for (int j = 1; j < nums2.length + 1; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
result = Math.max(result, dp[i][j]);
}
}
}
return result;
}
}
// 版本二: 滚动数组
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int[] dp = new int[nums2.length + 1];
int result = 0;
for (int i = 1; i <= nums1.length; i++) {
for (int j = nums2.length; j > 0; j--) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[j] = dp[j - 1] + 1;
} else {
dp[j] = 0;
}
result = Math.max(result, dp[j]);
}
}
return result;
}
}
```
Python
> 动态规划:
```python
class Solution:
def findLength(self, A: List[int], B: List[int]) -> int:
dp = [[0] * (len(B)+1) for _ in range(len(A)+1)]
result = 0
for i in range(1, len(A)+1):
for j in range(1, len(B)+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
result = max(result, dp[i][j])
return result
```
> 动态规划:滚动数组
```python
class Solution:
def findLength(self, A: List[int], B: List[int]) -> int:
dp = [0] * (len(B) + 1)
result = 0
for i in range(1, len(A)+1):
for j in range(len(B), 0, -1):
if A[i-1] == B[j-1]:
dp[j] = dp[j-1] + 1
else:
dp[j] = 0 #注意这里不相等的时候要有赋0的操作
result = max(result, dp[j])
return result
```
Go
```Go
func findLength(A []int, B []int) int {
m, n := len(A), len(B)
res := 0
dp := make([][]int, m+1)
for i := 0; i <= m; i++ {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if A[i-1] == B[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
}
if dp[i][j] > res {
res = dp[i][j]
}
}
}
return res
}
// 滚动数组
func findLength(nums1 []int, nums2 []int) int {
n, m, res := len(nums1), len(nums2), 0
dp := make([]int, m+1)
for i := 1; i <= n; i++ {
for j := m; j >= 1; j-- {
if nums1[i-1] == nums2[j-1] {
dp[j] = dp[j-1] + 1
} else {
dp[j] = 0 // 注意这里不相等要赋值为0供下一层使用
}
res = max(res, dp[j])
}
}
return res
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
```
JavaScript
> 动态规划
```javascript
const findLength = (A, B) => {
// A、B数组的长度
const [m, n] = [A.length, B.length];
// dp数组初始化都初始化为0
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
// 初始化最大长度为0
let res = 0;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
// 遇到A[i - 1] === B[j - 1]则更新dp数组
if (A[i - 1] === B[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
// 更新res
res = dp[i][j] > res ? dp[i][j] : res;
}
}
// 遍历完成返回res
return res;
};
```
> 滚动数组
```javascript
const findLength = (nums1, nums2) => {
let len1 = nums1.length, len2 = nums2.length;
// dp[i][j]: 以nums1[i-1]、nums2[j-1]为结尾的最长公共子数组的长度
let dp = new Array(len2+1).fill(0);
let res = 0;
for (let i = 1; i <= len1; i++) {
for (let j = len2; j > 0; j--) {
if (nums1[i-1] === nums2[j-1]) {
dp[j] = dp[j-1] + 1;
} else {
dp[j] = 0;
}
res = Math.max(res, dp[j]);
}
}
return res;
}
```
TypeScript
> 动态规划:
```typescript
function findLength(nums1: number[], nums2: number[]): number {
/**
dp[i][j]nums[i-1]和nums[j-1]结尾,最长重复子数组的长度
*/
const length1: number = nums1.length,
length2: number = nums2.length;
const dp: number[][] = new Array(length1 + 1).fill(0)
.map(_ => new Array(length2 + 1).fill(0));
let resMax: number = 0;
for (let i = 1; i <= length1; i++) {
for (let j = 1; j <= length2; j++) {
if (nums1[i - 1] === nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
resMax = Math.max(resMax, dp[i][j]);
}
}
}
return resMax;
};
```
> 滚动数组:
```typescript
function findLength(nums1: number[], nums2: number[]): number {
const length1: number = nums1.length,
length2: number = nums2.length;
const dp: number[] = new Array(length1 + 1).fill(0);
let resMax: number = 0;
for (let i = 1; i <= length1; i++) {
for (let j = length2; j >= 1; j--) {
if (nums1[i - 1] === nums2[j - 1]) {
dp[j] = dp[j - 1] + 1;
resMax = Math.max(resMax, dp[j]);
} else {
dp[j] = 0;
}
}
}
return resMax;
};
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>