本文已参与「新人创作礼」活动,一起开启掘金创作之路。
今天发现LIS问题还有nlogn的解法,简单记录一下
问题描述
最长上升子序列问题(LIS,Longest Increasing Subsequence) 给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
例如:3 1 2 1 8 5 6 的最长上升子序列长度为4
先来复习一下n^2的解法
解法1:动态规划
很显然,使用暴力是很难去解决这道问题的,所以就使用到了动态规划。
对于这道题目,我们需要一个dp数组,来记录上升子序列的长度。
状态转移方程为:f[i] = max(f[i] , f[j] + 1) {条件:如果i上的数严格大于j上的数的话}
dp[i]=max(dp[k]+1),a[k]<a[i]
ans=max(dp[i])
dp[i]是以i为结尾的上升子序列的长度。最后的结果,也就是最长的上升子序列的长度不一定是以i为结尾的,所以还要遍历dp找到最大值。时间复杂度O(n^2)
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N],dp[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<=i-1;j++){
if(a[i]>a[j]){
dp[i]=max(dp[i],dp[j]+1);
ans=max(ans,dp[i]);
}
}
}
cout<<ans;
return 0;
}
解法2:二分+贪心
dp[i]表示长度为i的最长上升子序列,末尾最小的数字,这个状态表示的思路是反着的,有点神奇,发明者脑洞真大。时间复杂度O(nlogn)
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int arr[N], dp[N];
int main() {
cin >> n;
for (int i = 1 ; i <= n; i++) cin >> arr[i];
int len = 1;
dp[len] = arr[1];
for(int i = 2; i <= n; i++){
if(dp[len] < arr[i])
dp[++len] = arr[i]; //如果比最后一个元素大,那么就添加在最后末尾处
else {
int j=lower_bound(dp + 1, dp + 1 + len, arr[i])-dp;
dp[j]=arr[i];//如果比最后一个元素小,那么就替换该序列第一个比他大的数;
}
}
cout << len << endl;
return 0;
}