This commit is contained in:
programmercarl
2022-12-20 15:33:57 +08:00
parent 5fc31873aa
commit 77f1c528b7
15 changed files with 290 additions and 292 deletions

View File

@@ -13,11 +13,10 @@
示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
* A: [1,2,3,2,1]
* B: [3,2,1,4,7]
* 输出3
* 解释:长度最长的公共子数组是 [3, 2, 1] 。
提示:
@@ -27,7 +26,12 @@ B: [3,2,1,4,7]
## 思路
注意题目中说的子数组其实就是连续子序列这种问题动规最拿手动规五部曲分析如下
注意题目中说的子数组其实就是连续子序列
要求两个数组中最长重复子数组如果是暴力的解法 只要需要先两层for循环确定两个数组起始位置然后在来一个循环可以是for或者while来从两个起始位置开始比较取得重复子数组的长度
本题其实是动规解决的经典题目我们只要想到 用二维数组可以记录两个字符串的所有比较情况这样就比较好推 递推公式了
动规五部曲分析如下
1. 确定dp数组dp table以及下标的含义
@@ -39,7 +43,7 @@ dp[i][j] 以下标i - 1为结尾的A和以下标j - 1为结尾的B
那有同学问了我就定义dp[i][j] 以下标i为结尾的A和以下标j 为结尾的B最长重复子数组长度不行么
行倒是行 但实现起来就麻烦一点大家看下面的dp数组状态图就明白
行倒是行 但实现起来就麻烦一点需要单独处理初始化部分在本题解下面的拓展内容里我给出了 第二种 dp数组的定义方式所对应的代码和讲解大家比较一下就了解
2. 确定递推公式
@@ -73,14 +77,15 @@ dp[i][j] 以下标i - 1为结尾的A和以下标j - 1为结尾的B
代码如下
```CPP
for (int i = 1; i <= A.size(); i++) {
for (int j = 1; j <= B.size(); j++) {
if (A[i - 1] == B[j - 1]) {
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];
}
}
```
@@ -93,14 +98,15 @@ for (int i = 1; i <= A.size(); i++) {
以上五部曲分析完毕C++代码如下
```CPP
// 版本一
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
vector<vector<int>> dp (A.size() + 1, vector<int>(B.size() + 1, 0));
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 <= A.size(); i++) {
for (int j = 1; j <= B.size(); j++) {
if (A[i - 1] == B[j - 1]) {
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];
@@ -111,8 +117,8 @@ public:
};
```
* 时间复杂度$O(n × m)$n 为A长度m为B长度
* 空间复杂度$O(n × m)$
* 时间复杂度O(n × m)n 为A长度m为B长度
* 空间复杂度O(n × m)
## 滚动数组
@@ -127,6 +133,7 @@ public:
**此时遍历B数组的时候就要从后向前遍历这样避免重复覆盖**
```CPP
// 版本二
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
@@ -148,6 +155,49 @@ public:
* 时间复杂度$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数组的定义更直观一点。
## 其他语言版本