前言:本人某天在b站刷到有up主讲解这种类型的题,觉得这是一种常用的模型,特此写下本文
给你一个整数数组 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
<= nums[i] <=
emmm我刚开始想暴力来着,看了眼数据范围感觉不妙,毕竟好歹是力扣中等题qwq
方法一:DP
我们假设dp[i]是以nums[i]结尾的长度最长的最长上升子序列,那么好,我们可以从前往后遍历dp[i]
在遍历到dp[i]的时候,我们要明白,dp[0......i-1]我们是知道的,我们从0....i-1用j遍历,当nums[j]<nums[i]的情况下我们找到最大的dp[j],在dp[j]的基础上+1就是dp[i]
状态转移方程:
前提:0<=j<i && num[j]<num[i]
上代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<long long> dp(n,0);
if(n==0){
return 0;
}
for(int i=0;i<n;i++)
{
dp[i]=1;//自身可以是一个长度为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());
}
};
//时间复杂度:o(n^2)
但很遗憾 时间复杂度是0(n^2),我们需要找到时间复杂度更低的做法
方法二:贪心+二分
贪心:考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
基于上面的贪心思路我们可以用d[i] 代表长度为i的最长上升子序列的末尾的最小值
用 len 记录目前最长上升子序列的长度,起始时 len 为 1,d[1]=nums[0]。
我们接着需要证明数组d具有单调性,即证明i<j时,d[i]<d[j]
,使用反证法,假设存在k<j时,d[k]>d[j]
,但在长度为j,末尾元素为d[j]
的子序列A中,将后j-i
个元素减掉,可以得到一个长度为i的子序列B,其末尾元素t1必然小于d[j]
(因为在子序列A中,t1的位置上在d[j]的后面),而我们假设数组d必须符合表示长度为 i 的最长上升子序列的末尾元素的最小值
,此时长度为i的子序列的末尾元素t1<d[j]<d[k]
,即t1<d[k]
,所以d[k]不是最小的,与题设相矛盾,因此可以证明其单调性
最后整个算法流程为:
设当前已求出的最长上升子序列的长度为 len(初始时为 1),从前往后遍历数组 nums,在遍历到 nums[i] 时:
如果 nums[i]>d[len] ,则直接加入到 d 数组末尾,并更新 len=len+1;
否则,在 d 数组中二分查找,找到第一个比 nums[i] 小的数 d[k] ,并更新 d[k+1]=nums[i]。
在这里我们用pos记录,pos记录d数组中小于num[i]的最大值的下标
- 这一步的意义,在于记录最小序列,代表了一种“最可能性”
- 例如[0,4,12,2,3,5]中,当判断到 nums[i] = 2时,d={0,4,12},此时根据这个原则,会将4替换为2,即d更新为d={0,2,12},虽然此时不会影响最长升序子序列的长度,但是这一步保存了之后有数字可以和{0,2}组成更长的升序子序列的可能性。例如,该数组的最长升序子序列为 {0,2,3,5},刚好0,2}是这个子序列的前缀,也正是因为我们将 4 替换为了2,所以才保留了这种可能性,否则,遇到3时,将忽略这个数,从而计算错误。
class Solution {
public:
int lengthOfLIS(vector<int>& num) {
int n = num.size();
if(n==0){
return 0;
}
vector<int> d(n+5,0);
int len = 1;
d[len]=num[0];
for(int i=1;i<n;i++)
{
if(num[i]>d[len]){
d[++len]=num[i];
}
else{
int l = 0, r = len+1,pos = 0;
while(l+1!=r){
int mid = (l+r)>>1;
if(d[mid]<num[i]){
pos = mid;
l = mid;
}
else r = mid;
}
d[pos+1]=num[i];
}
}
return len;
}
};
介绍一下我的blog sun's blog – 下次你路过,人间已无我。