贪心+二分查找求解LIS问题

求解最长上升子序列通常做法都是直接dp,很容易得到dp状态转移方程:

d p [ i ] = m a x ( d p [ j ] ) + 1 dp[i]=max(dp[j])+1 dp[i]=max(dp[j])+1,其中 0 ≤ j < i 且 n u m [ j ] < n u m [ i ] 0≤j<i且num[j]<num[i] 0j<inum[j]<num[i]

用两个循环遍历原数组,维护dp数组,遍历结束之后找到dp数组中的最大值就是最长上升子序列。代码如下:

int lengthOfLIS(vector<int>& nums) {
    int n=nums.size();
    vector<int>dp(n,1);
    dp[0]=1;
    for(int i=0;i<n;i++){
        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²),在n较大时容易超出时间限制,因此我们考虑另一种方法求解LIS问题


贪心+二分查找求解LIS问题
如果我们想要上升的子序列尽可能的长,那么我们需要让序列上升的尽可能慢,即我们希望每次在上升子序列中加上的那个数尽可能的小。
基于上面的贪心思路,我们维护一个数组d[i],他表示长度为i的最长上升子序列的末尾元素的最小值,用length记录当前最长上升子序列的长度,起始时length=1,d[1]=nums[0];
同时我们可以注意到:d数组是单调递增的。我们依次遍历数组nums中的每个元素,并更新数组d和length的长度,如果nums[i]>d[len],,则更新length=length+1,否则在d[1…length]中找到满足d[i-1]<nums[j]<d[i]的下标i,并更新d[i]=nums[j];
因此整个算法的流程为:设当前已经求出的最长上升子序列的长度为length,从前往后遍历数组,在遍历到nums[i]时:

  • 如果nums[i]>d[length],则直接加入d末尾,并length++;
  • 否则,查找d中第一个大于nums[i]的元素,并将其替换。

以输入序列[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];
    代码如下:
vector<int> d;
int length = 0;
for (int j = 0; j < n; j++) {
	++length;
	auto it = upper_bound(d.begin(), d.end(), arr[j]); //不递减子序列,若是严格递增则用lower
	if (it == d.end()) {
    	d.push_back(arr[j]);
    }
    else {
    	*it = arr[j];
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值