题目描述
解法一:DP(C++)
我们记状态 d p [ i ] dp[i] dp[i] 表示以第 i i i 个元素结尾的最长上升子序列的长度,那么专一方程就可以定义为 d p [ i ] = m a x ( d p [ j ] ) + 1 ( 0 ≤ j < i a n d n u m s [ j ] < n u m s [ i ] ) dp[i]=max(dp[j]) + 1\ (0 \leq j<i\ and\ nums[j]<nums[i] ) dp[i]=max(dp[j])+1 (0≤j<i and nums[j]<nums[i])
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n<=1) return n;
vector<int> dp(n, 0);
for(int i=0;i<n;i++)
{
dp[i] = 1;
for(int j=0;j<i;j++)
if(nums[j]<nums[i]) dp[i] = max(dp[i], dp[j]+1);
}
return *max_element(dp.begin(), dp.end());
}
};
解法二:贪心+二分(C++)
详细参考 官方题解
讲真,最早最早想到这个算法的人吼,脑回路确实不是一般人。
先来说他是咋想的哈,我们要让上升子序列尽可能长,那么上升子序列一定不能上升得太快,也就是说右端点值不能一上来就很大很大。那么,贪心就体现在每次增长时在子序列最末尾添加的元素要尽可能小。
我们记一个 d [ l e n ] d[len] d[len] 来表示长度为 l e n len len 的上升子序列的末尾元素值。
易知的是, d [ l e n ] d[len] d[len] 关于 l e n len len 单调递增。有单调性我们就知道怎么出来的二分法了。
如果 n u m s [ i ] > d [ l e n ] nums[i]>d[len] nums[i]>d[len],就说明我们这时在 d [ . . . ] d[...] d[...] 的末尾插入 n u m s [ i ] nums[i] nums[i] 是合理的,保证上升性的同时增加了长度。
如果 n u m s [ i ] < d [ l e n ] nums[i]<d[len] nums[i]<d[len]呢 ?
通过上面的描述,我们知道 n u m s [ i ] nums[i] nums[i] 是有用的,它保证了这个子序列不会上升很快。但是我们应该把它放哪儿呢?插入到最后是不合适的,这样违背了上升性。于是,我们对 d [ . . . ] d[...] d[...] 做二分查找,找到一个 n u m s [ i ] nums[i] nums[i] 可以插进去的地方,这个位置必然满足 d [ p o s ] < n u m s [ i ] < d [ p o s + 1 ] d[pos]<nums[i]<d[pos+1] d[pos]<nums[i]<d[pos+1],这样我们就将 n u m s [ i ] nums[i] nums[i]插入到 p o s + 1 pos+1 pos+1 的位置,也就是说我们以 n u m s [ i ] nums[i] nums[i] 结尾的子序列长度为 p o s + 1 pos+1 pos+1,这样做既保证了最长上升子序列的要求,也满足了我们希望它上升得慢一些的要求
以输入序列 [0, 8, 4, 12, 2]
为例:
第一步插入 0,d = [0]
第二步插入 8,d = [0, 8]
第三步插入 4,d = [0, 4]
第四步插入 12,d = [0, 4, 12]
第五步插入 2,d = [0, 2, 12]
最终得到最大递增子序列长度为 3
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size(), len = 1;
if(n<=1) return n;
vector<int> d(n+1, 0);
d[len] = nums[0];
for(int i=1;i<n;i++)
{
if(nums[i]>d[len]) d[++len] = nums[i];
else
{
int left = 1, right = len, pos = 0;
while(left<=right)
{
int mid = left+(right-left)/2;
if(d[mid]<nums[i])
{
pos = mid;
left = mid+1;
}
else right = mid-1;
}
d[pos+1] = nums[i];
}
}
return len;
}
};
解法三:耐心排序
耐心排序求最大上升子序列也是脑回路非一般人, 但是总的和解法二是很相近的,只是在解法二的基础再深了一点点 (@_@ ; )
我们先讲啥是耐心排序
假设数列是一组牌,每次都抽取一张,按规则放置,最后形成几组牌
规则是:
- 每组牌都是从大到小排列,小的牌不能放在大的牌上
- 放置牌时,从左至右查看,将牌放置能最左的能放置的组里,如果所有组都不能放置,那么这张牌在最右形成新的组。
假设数列是[0, 8, 4, 12, 2]
每次放置牌时,组的结果如下:
初始化 [ ]
第一步插入 0, [ [0] ]
第二步插入 8,[ [0], [8] ]
第三步插入 4,[ [0], [8, 4]]
第四步插入 12, [ [0], [8, 4], [12]]
第五步插入 2,[ [0], [8, 4, 2], [12]]
一共 3 组,取每个组的第一个数形成 [0, 8, 12]
,这是该数列的最长上升子序列
编码具体内容就不给出了,耐心排序的编码实现和解法二是一样的,只是在耐心排序中我们选择将每个较大元素单独设置成一个组,而不是直接插入末尾