链接:300.最长递增子序列
思路:
寻找最长递增子序列,子序列可以是不连续的,所以当在数组中找递增子序列时,被小于等于之前的数“中断”,这个时候需要跳过当前数,然后往后寻找符合条件的数,以维持子序列的递增性。这个时候问题就来了,如果之后有多组递增且符合要求的子序列,那么应该跳到哪一个子序列上呢?如下面这个数组:
123156456 对应下标为 012345678
数组前三位123是递增的,直到遇到1,这个时候需要跳过1,到达下一个递增的子序列56,组成12356,也可以到达另一个递增的子序列456,组成123456,很显然这个时候应该选后者。所以问题就是如何确定选取哪一个连续子序列才能组成最长的递增子序列,这个时候就需要动态规划的帮助了。
按照之前的思路,我们需要从数组中的第三个元素,也就是3,跳到下一个符合条件的数上,这个数只要比3大就行,所以可以理解为以数字3为“跳板”,寻找下一个符合条件的数。定义下标:dp[j]表示i之前包括i的以nums[j]结尾的最长严格递增子序列,注意这里指的是必须以nums[j]结尾,这才体现了“跳板”的的含义,找到符合条件的下一个数后,比如这个数是nums[i],那么dp[i]就可以表示成dp[j] + 1。
整个dp数组需要初始化为1,因为单个最短的递增子序列就是一个数,长度为1。还是以上面的数组为例,通过这种方式,我们可以算出,dp[2] = 3,因为有3个连续递增的数。当从3跳到5的时候,dp[4] = dp[2] + 1,当从3跳到4的时候,dp[6] = dp[2] + 1,此时不论跳到哪个数上都是一致的。但是往后我们可以发现,dp[8] = dp[7] + 1,dp[5] = dp[4] + 1,dp[8]可以取到比dp[5]更大的值,也就是说数组123456的长度要大于12356的长度。所以我们要固定i,然后遍历到达nums[i]的所有可能的“跳板”,也就是[0,i-1]之间的数j,并且nums[j] < nums[i],然后寻找能够令dp[i]最大的值是多少。递推公式为: dp[i] = max(dp[i], dp[j] + 1),遍历顺序为先用i遍历整个数组,再固定i后用j遍历[0, i-1],然后寻找能够让i最大化的j,并且记录下dp[i]最大的值是多少。在如上例子中,dp[i]最大时取的就是dp[8],即子序列123456。
代码:
class Solution {
public:
int lengthOfLIS(vector& nums) {
// 定义下标:dp[i]表示i之前包括i的以nums[i]结尾的最长严格递增子序列
vector dp(nums.size(), 1);
int ans = 1;
for (int i = 1; i < nums.size(); i++)
{
for (int j = 0; j < i; j++) // j的范围为[0, i-1]
// 固定i时,寻找能够令i最大化的j
if (nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
// 记录下当前dp数组的最大值
ans = max(ans, dp[i]);
}
return ans;
}
};
链接:674. 最长连续递增序列
思路:
这道题目要比前一道容易很多,它增加了一个限制条件:子序列必须是连续的。既然是连续的,就不存在所谓的“跳板”了,也就不需要遍历到达i的所有可能的“跳板”,可以直接省略一个循环。将遍历跳板的内循环[0, i-1]删除后,其他代码保持一致,递推公式也是一样的,然后记录下当前dp数组的最大值即可。
代码:
class Solution {
public:
int findLengthOfLCIS(vector& nums) {
// 定义下标:dp[i]表示i之前包括i的以nums[i]结尾的最长严格递增子序列
vector dp(nums.size(), 1);
int ans = 1;
for (int i = 1; i < nums.size(); i++)
{
if (nums[i] > nums[i-1])
dp[i] = dp[i-1] + 1;
// 记录下当前dp数组的最大值
ans = max(ans, dp[i]);
}
return ans;
}
};
通过观察可以发现,dp数组中只运用了上一个状态的值,所以可以只用一个变量来代替整个dp数组。
代码:
class Solution {
public:
int findLengthOfLCIS(vector& nums) {
int ans = 1;
int count = 1;
for (int i = 1; i < nums.size(); i++)
{
if (nums[i] > nums[i-1])
count++;
else
count = 1;
ans = max(ans, count);
}
return ans;
}
};
链接:718. 最长重复子数组
思路:
本题相较于前两题又难了一些,现在有两个数组了,还是用动态规划的做法,首先定义下标,dp[i][j]表示以长度为[0,i]的以nums[i-1]结尾的nums1和长度为[0,j]的以num2[j-1]结尾的nums2的最长公共子数组的长度,注意这里的下标代表的是公共子数组的长度,所以dp数组应该初始化为nums1和nums2的长度再加1,不加1的话就会越界了,这样的做法是为了方便初始化dp数组,当i为0或者j为0的时候,代表有一个数组的长度为0,这样不论另一个数组怎么样,它们的最长公共子数组一定为0。
接下来要确定递推公式,为了推导出dp[i][j],我们知道nums1和nums2在长度分别为i-1,j-1的时候,它们的最长公共子数组为dp[i-1][j-1],那么如果它们在长度i和j的时候,nums1的第i-1个数字和nums2的第j-1个数字也相同,也就是 nums1[i-1] == nums[j-1]的时候,dp[i][j]就等于dp[i-1][j-1] + 1,表示比dp[i-1][j-1]时又多了一个相同的数字。最后,只要记录下来遍历数组时最大的dp[i][j]就可以了。
代码:
class Solution {
public:
int findLength(vector& nums1, vector& nums2) {
// 定义下标:dp[i][j]表示以长度为i的nums1和长度为j的nums2的最长公共子数组的长度
vector> dp(nums1.size() + 1, vector(nums2.size() + 1));
int ans = 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;
ans = max(ans, dp[i][j]);
}
return ans;
}
};