300.最长递增子序列 ★
文档讲解 : 代码随想录 - 300.最长递增子序列
状态:再次回顾。(★:需要多次回顾并重点回顾。)
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i]
表示i
之前包括i
的以nums[i]
结尾的最长递增子序列的长度 -
确定递推公式
位置i
的最长升序子序列等于j
从0
到i-1
各个位置的最长升序子序列+1
的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
-
dp数组如何初始化
一个i
,对应的dp[i]
(即最长递增子序列)起始大小至少都是1
。 -
确定遍历顺序
dp[i]
是有0
到i-1
各个位置的最长递增子序列 推导而来,那么遍历i
一定是从前向后遍历。
j
其实就是遍历0
到i-1
,那么是从前到后,还是从后到前遍历都无所谓,只要把0
到i-1
的元素都遍历,所以默认习惯从前向后遍历。for (int i = 1; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); } if (dp[i] > result) result = dp[i]; // 取长的子序列 }
-
举例推导dp数组:
输入:[0,1,0,3,2]
,dp数组的变化如下:
本题代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int result = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
return result;
}
};
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
674. 最长连续递增序列
文档讲解 : 代码随想录 - 674. 最长连续递增序列
状态:再次回顾。
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i]
表示以下标i
为结尾的连续递增的子序列长度为dp[i]
-
确定递推公式
如果nums[i] > nums[i - 1]
,那么以i
为结尾的连续递增的子序列长度 一定等于 以i - 1
为结尾的连续递增的子序列长度+1
。
即:dp[i] = dp[i - 1] + 1;
-
dp数组如何初始化
以下标i
为结尾的连续递增的子序列长度最少也应该是1
,即就是nums[i]
这一个元素,所以dp[i]
应该初始1
; -
确定遍历顺序
从递推公式上可以看出,dp[i + 1]
依赖dp[i]
,所以一定是从前向后遍历。for (int i = 1; i < nums.size(); i++) { if (nums[i] > nums[i - 1]) { // 连续记录 dp[i] = dp[i - 1] + 1; } }
-
举例推导dp数组:
输入:nums = [1,3,5,4,7]
,dp数组的变化如下:
本题代码:
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
int result = 1;
vector<int> dp(nums.size() ,1);
for (int i = 1; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) { // 连续记录
dp[i] = dp[i - 1] + 1;
}
if (dp[i] > result) result = dp[i];
}
return result;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
718. 最长重复子数组 ★
文档讲解 : 代码随想录 - 718. 最长重复子数组
状态:再次回顾。(★:需要多次回顾并重点回顾。)
重复子数组,是连续子序列!
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i]
表示以下标i - 1
为结尾的A,和以下标j - 1
为结尾的B,最长重复子数组长度为dp[i][j]
。 -
确定递推公式
根据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;
-
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
。 -
确定遍历顺序
外层for循环遍历A,内层for循环遍历B。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]; } }
-
举例推导dp数组:
拿示例1中,A: [1,2,3,2,1],B: [3,2,1,4,7]
为例,画一个dp数组的状态变化,如下:
本题代码:
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
)
O(n × m)
O(n×m),
n
为A长度,m
为B长度 - 空间复杂度: O ( n × m ) O(n × m) O(n×m)