📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待
文章目录
- 最长递增子序列
- 最长连续递增序列
- 最长重复子数组
- 总结:
本期是求子序列的新的一期,题目前两道有一些相似之处,思路差不多,第三道有一点难度,但并不意味着第一道没有难度,没有做过该类型题的选手,并不容易解出题解。
最长递增子序列
300. 最长递增子序列 - 力扣(LeetCode)
题目大意是给一个数组,要求返回最长的递增的序列,题目要求是删除中间一些元素,或者不删除也可以,返回最长的递增的数组长度,又是一道这种题目,如果你是之前做过类似题目大意的朋友应该知道,通常来说要求可以删除中间元素的,通常我们都不是真的删除数据,而是通过遍历来间接的实现删除中间的数据的过程,换句话来说,不是真正删除数据,而是要删除的数据我们不处理它。
dp数组的含义:dp【i】代表到该位置为止,i之前包括i的最长的递增子序列是多大。但是需要注意的是,整体的最长的递增子序列,可不一定是dp数组的最后一个数据,因为很可能,我们要找的最长递增序列结尾在前面就已经出现了,而遍历到数组最后一个位置,可能只是不同的递增子序列路线,不一定是长的。
递推公式:由于递增子序列,很有可能不是连续的,需要删除部分数据,那么我们如何实现这一部分的代码呢?答案是用两个循环,一个是外层循环控制遍历到数组中的哪一个位置了,另一个内层循环是重复遍历从起始位置到i这个位置,如果出现前面的0-i的数据比当前遍历到的数小,那么递增子序列长度+1,这也就是相当于不符合条件的数据我们直接略过去,符合条件的再使长度+1,间接的删除了多余数据
也就是
if(nums【j】< nums【i】)dp【i】=max(dp【i】,dp【j】+1)
取最大值,看看它原本的大还是遍历完的最长递增子序列长度大。每次j都会从头开始遍历一次,看看这回多出来的那个数据是否能使子序列长度增加。
dp数组初始化:dp数组的初始化都是初始化为1,这是因为如果只有一个数据的话,那么最长递增子序列肯定是1,不可能返回0,而其他的为什么也初始化为1呢?因为如果出现大于1的递增子序列长度那么就一定会被递推公式所覆盖,所以我们都初始化为1。
遍历顺序:都是从前向后遍历,不用多说。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>dp(nums.size(),1);
int result=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(result<dp[i])result=dp[i];
}
return result;
}
};
代码中的result的用处就是,记录最长的递增子序列有多长,然后最后返回它。
最长连续递增序列
674. 最长连续递增序列 - 力扣(LeetCode)
这道题和上一道题很像,只不过这道题我们要求的是连续的递增子序列了,连续的递增子序列,我个人认为要比非连续的要好写,或者说好想一点。
dp数组含义:dp数组的含义和上一道题一样,也是到该位置为止,i之前包括i的最长的连续递增子序列是多长,但是我觉得这种含义太抽象了,不利于初学者理解,我认为可以将这句话翻译为以i结尾的这条递增序列有多长,这其实也就间接的证明了我们之前所说的含义,即最后一个下标的dp数组所代表的数据,并不一定是最大的。因为它代表的应该是以i为结尾的这条递增序列最长有多长。
递推公式:由于是连续的最长递增序列,所以我们只看是否是连续递增就可以了,那么连续就体现在上一个数字和这一个数字的比较
if(nums【i】==nums【i-1】)dp【i】=dp【i-1】+1;
dp数组初始化和遍历顺序都和上一道题一样,不做赘述。
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int>dp(nums.size(),1);
int result=1;
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1])
dp[i]=dp[i-1]+1;
result=max(result,dp[i]);
}
return result;
}
};
最长重复子数组
718. 最长重复子数组 - 力扣(LeetCode)
这道题是略有难度的一道题,为什么说它略有难度呢?并不是递推公式有多难理解,而是dp数组的的表示,要清楚的想到有一些难度。
dp数组的含义:这是给我们两个数组,然后让我们求重复的连续部分最长有多长,这是我们就需要使用二维数组来表示了,其中一维表示一个数组,另一维表示另一数组,而dp【i】【j】表示的是第一个数组的i-1位置,下标对应的第二个数组的j-1下标位置,最长的重复子数组有多长,那么为什么我们要表示i-1和j-1呢?直接表示i和j不好吗?这样定义是为了方便dp数组的初始化,在下面还有对此处的详解。
递推公式:从哪个方向上能够推出dp【i】【j】呢?当数组1的i-1下标位置的数字和数组2下标为j-1的位置数字相等的时候,不就是说明两数组有一数字重复吗,此时就是长度+1,那么递推公式就理所当然的是
if(nums1【i-1】==nums2【j-1】)dp【i】【j】=dp【i-1】【j-1】+1;
就是上一个长度再加上这回匹配成功的长度1。
dp数组的初始化:看到这里相信大家可能已经带有一些疑问了,这样定义dp数组那么当遍历到i和j等于0的时候,不就出问题了吗?这也正是初始化所要解决的问题,正是因为我们这样定义dp数组导致了当i和j其中一个为0时候,是非法的,所以我们初始化第一行和第一列时候全部初始化为0,其余部分因为有递推公式的存在,每个位置是依靠前一个位置的值,所以i和j非0部分初始化为什么都可以。那么为什么我们要这样定义数组呢?因为如果dp【i】【j】就是表示它处在i和j的位置上时候有多长,这会导致初始化i和j其一等于0时,也就是二维数组的第一行和第一列可能不全为0,这会增大初始化的代码强度,可能第一个数组i==0的时候第二个数组的j对应某位置两数组有数据相等,也就是第一行某处有位置应该初始化为1,讲到这里大家反复揣摩一下,用笔画一下,可能就明白了。
遍历顺序:遍历顺序还是从前向后,两个for循环,分别遍历第一个数组和第二个数组,从前向后查看是否有相等的元素出现。
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;
result=max(result,dp[i][j]);
}
}
return result;
}
};
以上就是本章的全部内容,子序列问题起初做都没有什么思路,要勤加练习思维,才能够想出用动态规划解决问题的思路。
总结:
今天我们完成了最长递增子序列、最长连续递增序列、最长重复子数组三道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~