买卖股票问题结束,进入动态规划的最长子序列问题。想想之前的章节还落下不少,得找时间补呀。
任务日期:7.18
题目一链接:300. 最长递增子序列 - 力扣(LeetCode)
思路:设置一个一维dp数组dp[i],代表以nums[i]为结尾的最长递增子序列的长度,而次长度并非最大长度。
代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 1) return 1;
int result = 0;
int len = nums.size();
vector<int> dp(len,1); //定义dp[i]:以nums[i]为结尾的最长递增子序列的长度 每一个数都相当于以他开始的递增子序列的第一个数,所以初始化为1
for(int i = 1;i < len;i ++) { //从dp数组的第二个位置开始遍历,因为第一个数肯定是1,因为从左往右的顺序不能变,只能进行删减
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;
}
};
难点:1.边界问题,当nums的大小是1时,直接返回1.
2.定义dp数组:以nums[i]为结尾的最长递增子序列的长度
3.dp数组初始化:每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1,而dp数组的一个元素值一定是1.
4.确定遍历顺序时:i从1开始是因为j只能到i - 1.
5.由于dp[i]的值并不表示子序列长度最大值,因此在遍历过程中应该不断更新result,让result代表dp数组里的长度最大值。
题目二链接:674. 最长连续递增序列 - 力扣(LeetCode)
思路:本题较上一题的区别在于本体需要递增子序列连续,严格按照递归五部曲可以解决本题。其中关键在于确定dp数组,dp[i]代表以nums[i]为结尾的最长连续递增子序列的长度。因此可知递推公式,只需要比较相邻的两个nums[i]元素即可。
代码:
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if(nums.size() == 1) return 1;
int len = nums.size();
int result = 0;
//确定dp数组 并且初始化
vector<int> dp(len,1); //以nums[i]为结尾的最长连续递增子序列的长度
//确定递推公式
//if(nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
//确定遍历顺序
for(int i = 1;i < len;i ++) {
if(nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
if(dp[i] > result) result = dp[i];
}
return result;
}
};
难点:1.由于本题只需要比较相邻的元素,数组下标用i - 1就能表示,因此一层for循环即可,而上一题不连续,所以必须需要遍历i下标之前的元素,因此需要两个for循环
题目三链接:718. 最长重复子数组 - 力扣(LeetCode)
思路:本题跟最长连续递增子序列的区别在于本题在此基础上多了一个数组,比较两个数组。因此本题定义的dp[i][j]:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。然后确定递推公式:dp[i][j] = dp[i - 1][j - 1] + 1,可知dp[i][j]由左上角的dp[][]求得。
代码:
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int result = 0;
//确定dp[i][j]及其初始化:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。;dp[i][0],dp[0][j],dp[0][0]都初始化为0
vector<vector<int>> dp(nums1.size() + 1,vector<int>(nums2.size() + 1,0));
//确定递推公式
//if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
for(int i = 1;i <= nums1.size();i ++) { //小于等于的原因在于dp数组的定义.
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;
}
};
难点:1.dp数组的定义dp[i][j]为什么是下标为i - 1而不是i:如果是i的话,在初始化时如果nums1[i] 与 nums2[0] 相同的话,对应的 dp[i][0]就要初始为1,因此会多出两行for循环的初始化代码,不如当前写法:第一行和第一列都是零简洁。
2.dp数组定义中的以nums1[i - 1]为结尾指的是原数组而不是dp数组
3.在确定遍历顺序中i小于等于nums1.size()而不是小于的原因在于dp数组的定义:以下标为i - 1的元素为结尾的数组。
解释细节1:因为dp[i][j]代表下标以i - 1结尾的A和下标以j - 1结尾的B的最长重复子数组的大小,因此要求第一行dp[0][j]和第一列dp[i][0]都是零,所以省去了初始化的代码。