300. 最长递增子序列(中等)
题目链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
你可以设计时间复杂度为 O(n2) 的解决方案吗?
你能将算法的时间复杂度降低到 O(n log(n)) 吗?
本题有两种解题方案,第一种使用动态规划,时间复杂度O(n^2),第二种使用贪心思路加二分查找复杂度O(nlogn)
解法一:
整个过程不断枚举上升序列结尾元素,每枚举一个结尾元素就从前往后再遍历一遍,找到比当前元素小的元素j,查询该元素f[j]值,将f[j]+1
f[i]作比较,最终取得以第i个元素为结尾元素时的最大值,最终遍历f数组,找到最大值。
定义f[i]表示考虑前i个元素的情况下,以第i个元素结尾的最长上升子序列长度,则边界条件有,对于每个元素,有f[i]=1;
接下来考虑状态转移方程:在考虑f[i]时,需要枚举以第j个数字结尾的最长上升子序列的长度,其中,0<=j<i,枚举过程中,若
有nums[j]<nums[i],则最长上升子序列长度应为max(f[i],f[j]+1),并在从第一个元素枚举到最后一个元素时找出最大的f[i]即为答案
AC代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int f[2505],res=-1;
for(int i=0;i<nums.size();i++){
f[i]=1;
for(int j=0;j<i;j++)
if(nums[j]<nums[i]) f[i]=max(f[i],f[j]+1);
res=max(res,f[i]);
}
return res;
}
};
方法二:
采取贪心思想,在保证子序列上升且长度最大的情况下让结尾元素尽可能选取数组中最小元素从而使得子序列尽可能长
例如:假设nums数组为:5 1 2 4 3 4
第一步:选取5,序列为:5,长度为1;
第二步:1比5小,将5替换掉,保证序列尾部尽可能小,序列变为:1,长度为1;
第三步:2比序列尾部大,直接加入,序列变为:1 2,长度为2;
第四步:序列变为:1 2 4,长度为3;
第五步:3比序列尾部的4小,在序列中找到第一个大于3的数(不一定是最后一个元素,可以想想为什么不是结尾元素也需要更新),用3替换,得到:1 2 3,长度为3
第六步:4比3大,直接插入,变为:1 2 3 4,长度为4
4为正解
由于序列是上升序列,故上述的查找过程可以利用二分查找,总体复杂度就为nlogn
AC代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int res=1;
int f[2505]={0};
f[res]=nums[0];
for(int i=1;i<nums.size();i++){
if(nums[i]>f[res]){
res++;
f[res]=nums[i];
}
else{
int t=lower_bound(f+1,f+res,nums[i])-f;
if(t<=res)
f[t]=nums[i];
else
f[1]=nums[i];
}
}
return res;
}
};