673. Number of Longest Increasing Subsequence
300. Longest Increasing Subsequence
334. Increasing Triplet Subsequence
这三道题目基本一样。
Leetcode 300 题目是最基本的DP算法。
Leetcode 673是300题的延伸。
Leetcode 334 也是300的延伸。
首先第三百题, 给了一个数组,数值任意。给出最长的增长子序列。
当然最先想到的是DFS,把所有的子序列都找到。然后看是不是增长的。
第二个想法就是DP。题目给了两个条件: 有序: LSI是递增。 第二个,求的是最长的LSI值。
既然用了DP,就得:
1. DP的状态: 表示的是到当前位置的数,其最大的LSI长度。
2. 初始化: DP[0]就是第一个元素,到他为止的LSI长度为1.他自己。
3. DP转换方程
如果当前数没有办法拼接到之前的LSI子序列,那么其长度就是1.他自己。
a) 当前数比之前的小, 就没有办法拼成一个LSI子序列了。
如果当前数可以拼接到之前的LSI子序列,那么其长度是DP[j]+1
a). 从当前位置往前找,找到数值比i 小的。那么就能和比i小的这个数拼成一个LSI
4. 求结果。
在DP数组里找最大的那个数。
于是代码就
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1);
// dp 代表到当前index位置的 最大LIS
dp[0] = 1;
for(int i = 1; i< n; i++){
//不管当前index的数是大于还是小于index-1的数。都得往前找小于当前数的LIS
// 比如 0, 1, 0, 3, 2, 3
//当遍历到2的时候,必须往前遍历到数值为1的点,因为0, 1, 再加上2 也是一个LIS
// 所以就找之前的比当前点小的num, 然后尝试拼接程LIS。并赋值dp为最长的那个LIS。
for(int j = i-1;j>=0;j--){
if(nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j]+1);
}
}
}
int ret=dp[0];
for(int i = 1;i<n;i++)
ret = max(ret, dp[i]);
return ret;
}
};’
673题也是根据300题变化来的。既然现在最长的LSI都求出来了。
求LSI的个数。
看完题目就觉得不会这么简单把。 LSI的个数不就是DP数组里最大的那个数的个数吗?
第一次提交完傻眼了。number of longest increasing subsequences。
这个求的是最长的增长序列的个数。也就是path的个数。
比如DP数组里7 出现了3次。人家不光要求3,还要求每次最长LSI为3 的序列个数。
这样的话就得再多加个数据,去存储到当前节点位置,最长的LSI所对应的路径个数。
那么怎么求那?
关键在这,DP每次更新的时候。
DP公式的想法是把num[i] 和之前的每个数据num[j]对比。
如果num[i]比num[j] 大,说明可以和 num[j]一起拼接程一个LSI。 那么我们就可以根据num[j]的路径数目,算出来到num[]的路径数目。
当然, 也许有多个i可以拼出来一样长度的路径。那么就得找到所有的长度为x的节点的路径。
for(int j = i-1;j>=0;j--){
if(nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j]+1);
}
}
把之前的代码改一些。
//递推方程是往前寻找能否拼成increasing的子序列。
for(int i=1;i<n;i++){
int count;
for(int j = i-1;j>=0;j--){
if(nums[i]> nums[j]){
if(dp[j]+1 > dp[i]){
count=numPath[j];
dp[i] = dp[j] + 1;
}
else if (dp[i] == dp[j]+1)
count+=numPath[j];
}
numPath[i] = count;
}
到现在为止就能算出来想要的结果了。
代码如下
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
// this question is based on the no. of
// longest increasing subsequence.
// based on the result of LIS, to find the max
//LIS and how many it have.
//本以为不会很难,没想到写起来却无比的艰难。原因是思考的不全面。
// DP的结果里可能会有多个数值是最大的。本以为把他们的个数加起来就是结果了。但是却没有想到每个最大的个数都可能对应多个路径。比如最长路径是3的点有2个。但是这两个点对应的路径数目可能有多个。
// 所以在计算每个点对应的DP数值的时候,还要计算对应的最大LIS所对应的路径数目。
// 最后遍历得到最大的LSI 数值的时候,就要同时遍历最大的LSI的路径数目。并把他们加起来。
int n = nums.size();
vector<int> dp(n, 1);
vector<int> numPath(n, 1);
//递推方程是往前寻找能否拼成increasing的子序列。
for(int i=1;i<n;i++){
int count = 1;
for(int j = i-1;j>=0;j--){
if(nums[i]> nums[j]){
if(dp[j]+1 > dp[i]){
count=numPath[j];
dp[i] = dp[j] + 1;
}
else if (dp[i] == dp[j]+1)
count+=numPath[j];
}
numPath[i] = count;
}
}
int intMax=0;
int ret=0;
for(int j = n-1; j>=0;j--){
if(dp[j] > intMax)
{
intMax = dp[j];
ret =numPath[j];
}
else if(dp[j] == intMax){
ret +=numPath[j];
}
}
return ret;
}
};
334. Increasing Triplet Subsequence
求是否存在LSI长度为3的。
我们大于3的都能求出来。等于3的求不出来了吗?
答案是能求出来,但是超时了。。。。。。。。
代码如下:
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1);
for(int i= 1;i<n;i++){
for(int j = i-1; j>=0;j--){
if(nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
}
if(dp[i] >=3)
return true;
}
return false;
}
};
看起来得优化了。。。还没有想好咋优化。先写道这把!