Update
This commit is contained in:
@@ -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数组的定义,更直观一点。
|
||||
|
||||
## 其他语言版本
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user