LIS-最长上升子序列

LIS(最长上升子序列)是一个经典的问题。

首先我们来介绍子序列的概念:

子序列指的是一个序列中,按照原顺序选出若干个不一定连续的元素所组成的序列。

LIS有两种算法模型:一种是复杂度为O(n^{^{2}})的dp模型,另外一种是复杂度为O(nlogn),利用二分实现的模型。

模型一:复杂度为O(n^{^{2}})的dp模型

我们首先定义状态:
我们定义dp[i]为以a[i]结尾的最长上升子序列长度。
设置 j为小于 i 的某一点,那么当 a[j] < a[i] 时,必然有,以 a[j]结尾的最长上升子序列,现在能以 a[i]结尾,并且长度+1
因为j < ia[j]< a[i],满足上升子序列的要求。
所以 dp[i]的一条转移路径为 dp[i]=dp[j]+1
但是  j是比i小的某一个值,所以转移到 dp[i]这一状态的值很多,我们要选择最优状态。所以 dp[i]的状态转移:
dp[i]=max(dp[j]+1,dp[i]);
那么当 a[j] > a[i]
此时肯定不满足上升子序列,所以 dp[i]dp[j]毫无关联。
由此我们可以写出 LIS 的算法:

#include<bits/stdc++.h>
using namespace std;

using ll = long long;
const int N=1e3+10;
int n;
ll a[N],dp[N];
int main()
{
  cin>>n;
  for(int i = 1;i <= n;i++) {
    cin >> a[i];
    dp[i]=1;
  }
  
  for(int i = 1;i <= n;i++)
  {
    for(int j = 1;j < i;j++)
      {
      if(a[i] > a[j]) dp[i]=max(dp[i],dp[j]+1);
      }
  }
  ll ans = 0;
  for(int i = 1; i <= n; i++){
    ans = max(ans,dp[i]);
  }  
  cout<<ans<<endl;
  return 0;
 }

模型二:复杂度为O(nlogn),利用二分实现的模型。

当数据量较大,使用常规的O(n^2)的LIS算法会超时
故采用二分+贪心的算法求解: 
维护一个low数组,low[i]表示长度为 i 的最长上升子序列的末尾元素
使用贪心思想来对low数组进行更新,核心思想是末尾元素越小,越容易凑出最长的子序列
遍历每一个元素,若当前元素比low[i]更大,则直接加入low数组的末尾 
若当前元素小于low[i],则从low数组中找到一个大于等于它的最小值将其替换
由于low数组是递增的,故使用二分算法进行搜索和替换
最终输出low数组的长度即为答案 。

注意此算法的缺陷:low数组只有长度是有意义的,其保存的元素是无意义的。

由此我们可以写出 LIS 的算法:

#include<bits/stdc++.h>
using namespace std;

using ll = long long;
const int N = 3e5 + 10;
ll a[N],low[N];

int main(){
  int n,length = 1;
  cin >> n;
  for(int i = 1; i <= n; i++){
    cin >> a[i];
  }
  low[1] = a[1];  //初始化,长度为1的子序列初始化为第一个元素 
  for(int i = 2; i <= n; i++){
    if(a[i] > low[length]){  //若当前元素大于low数组末尾元素,直接插入 
      low[++length] = a[i];  
    }else{   //若小于,则用low数组中刚好大于等于它的元素替换之 
      int index = lower_bound(low + 1, low + length + 1,a[i]) - low;  //获取插入位置下标 
      low[index] = a[i];  //替换 
    }
  }
  cout << length <<endl;  //输出low数组长度即为答案 
  return 0;
}
  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值