【动态规划】最长上升子序列LIS

295 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

今天发现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;
}