最长上升子序列(序列长度+序列输出)

一、LIS的定义:
最长上升子序列(Longest Increasing Subsequence),简称LIS,也有些情况求的是最长非降序子序列二者区别就是序列中是否可以有相等的数。假设我们有一个序列 b i b_i bi,当 b 1 < b 2 < … < b S b_1 < b_2 < … < b_S b1<b2<<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们也可以从中得到一些上升的子序列 ( a i 1 , a i 2 , … , a i K ) (a_{i_1}, a_{i_2}, …, a_{i_K}) (ai1,ai2,,aiK),这里 1 < = i 1 < i 2 < … < i K < = N 1 <= i_1 < i_2 < … < i_K <= N 1<=i1<i2<<iK<=N,但必须按照从前到后的顺序。比如,对于序列(1, 7, 3, 5, 9, 4, 8),我们就会得到一些上升的子序列,如(1, 7, 9), (3, 4, 8), (1, 3, 5, 8)等等,而这些子序列中最长的(如子序列(1, 3, 5, 8) ),它的长度为4,因此该序列的最长上升子序列长度为4。
首先需要知道,子串和子序列的概念,我们以字符子串和字符子序列为例,更为形象,也能顺带着理解字符的子串和子序列:
(1)字符子串指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的子串。
(2)字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。
知道了这个,数值的子序列就很好明白了,即用数字组成的子序列。这样的话,最长上升子序列也很容易明白了,归根结底还是子序列,然后子序列中,按照上升顺序排列的最长的就是我们最长上升子序列了,这样听来是不是就很容易明白啦~
还有一个非常重要的问题:请大家用集合的观点来理解这些概念,子序列、公共子序列以及最长公共子序列都不唯一,但很显然,对于固定的数组,虽然LIS序列不一定唯一,但LIS的长度是唯一的。再拿我们刚刚举的栗子来讲,给出序列 ( 1, 7, 3, 5, 9, 4, 8),易得最长上升子序列长度为4,这是确定的,但序列可以为 ( 1, 3, 5, 8 ), 也可以为 ( 1, 3, 5, 9 )。
二、解法
普通解法:动态规划dp
dp[i]表示以a[i]结尾的最长上升子序列长度
显然有 dp[i]=max(dp[i],dp[j]+1) 满足a[i]>a[j],1<=i<n,j<i
实现过程时间复杂度为O(n^2)
代码:复杂度在O(n^2)

import java.util.Scanner;
public class Main{
    
    
    public static void main(String[] args){
    
    
        Scanner cin=new Scanner(System.in);
        while(cin.hasNext()){
    
    
            int n=cin.nextInt();
            int a[]=new int[n+1];
            int dp[]=new int[n+1];//dp[i]表示前i个数以a[i]结尾的最长上升子序列长度
            int pos=0;//记录最后一次更新的最长上升子序列长度最后位置
            for(int i=0;i<n;i++){
    
    
                a[i]=cin.nextInt();
                dp[i]=1;
            }
            int ans=1;//记录答案
            for(int i=0;i<n;i++){
    
    
                for(int j=0;j<i;j++){
    
    
                    if(a[j]<a[i]) dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            for(int i=0;i<n;i++){
    
    
                if(ans<=dp[i]){
    
     //加个等于记录一下使得最后一个序列数最小
                    ans=dp[i];
                    pos=i;
                }
            }
            System.out.println(ans);//最长上升子序列长度
            int num[]=new int[ans+1];//记录其中一种解决方案,正着记录,倒着输出
            num[0]=a[pos];
            int cnt=0;
            for(int i=pos-1;i>=0;i--){
    
    
                if(dp[i]==ans-1){
    
    
                    num[++cnt]=a[i];
                    ans--;
                }
            }
            for(int i=cnt;i>=0;i--){
    
     //输出答案
                if(i==cnt) System.out.print(num[i]);
                else System.out.print(" "+num[i]);
            }
            System.out.println();
        }
    }
}

贪心+二分: 复杂度在O(nlogn)
思路:用一个low数组记录长度,low[i]表示产后都为i的LIS结尾元素的最小值,这样我们在记录low的时候,当a[i]大于low[++当前LIS最大长度]时候,直接将a[i]接在low中,否则在low中二分查找大于等于当前元素a[i]的第一个位置pos,用a[i]替换掉之前的low[pos],这样记录的low数组其实不一定是一个子序列解,我们采用一个数组pos1[]记录a[]中的元素出现在low中的位置。最后我们找一下最长上升子序列下标满足的解,记录下该子序列即可。需要注意的是,在二分更新下界时候是 a[mid]<x,不能用等于来更新,否则会出错误。
如果是C++则直接用lower_bound即可。文末给出一份C++代码。
这里给出一组卡这个的数据。

样例:
88
317 211 180 187 104 285 63 117 320 35 288 299 235 282 105 231 253 74 143 148 77 249 310 137 191 184 88 8 194 12 117 108 260 313 114 261 60 226 133 61 146 297 291 13 198 286 254 96 135 48 135 307 23 155 203 258 168 42 301 45 164 193 26 290 280 172 94 230 156 36 250 174 47 188 148 138 194 89 71 119 218 325 136 63 271 210 320 309
答案:
17
35 74 77 88 108 114 133 135 155 164 172 174 188 194 218 271 309 

当然可以在letcode中验证一下:最长上升子序列,同时也可以在牛客网中提交测试:Redraiment的走法

import java.util.Scanner;
public class Main{
    
    
    public static final int INF=0x3f3f3f3f;
    public static void main(String[] args){
    
    
        Scanner cin=new Scanner(System.in);
        while(cin.hasNext()){
    
    
            int n=cin.nextInt();
            int a[]=new int[n+1];
            int low[]=new int[n+1];//low[i]表示长度为i的LIS结尾元素的最小值。
            int pos1[]=new int[n+1];//用pos1数组记录a中元素在low中出现位置
            int ans[]=new int[n+1];//用ans数组记录答案
            for(int i=0;i<n;i++){
    
    
                a[i]=cin.nextInt();
                low[i]=INF;
            }
            int cnt=0;
            low[0]=a[0];pos1[0]=0;
            for(int i=1;i<n;i++){
    
    
                if(a[i]>low[cnt]){
    
    
                    low[++cnt]=a[i];//如果a[i]大于了,就接在后面,否则,二分查找位置替换掉;
                    pos1[i]=cnt;
                }
                else{
    
    
                    int pos=binary_search(low,cnt,a[i]);
                    low[pos]=a[i];
                    pos1[i]=pos;
                }
            }
            System.out.println(cnt+1);
            int maxNum=INF,index=cnt;
            for(int i=n-1;i>=0;i--){
    
    
                if(index==-1) break; //说明找完了
                if(pos1[i]==index&&maxNum>a[i]){
    
    //先找第一个在low数组index位置,再找第一个在low数组index-1位置直到index=-1
                    maxNum=a[i];
                    ans[index--]=i;
                }
            }
            for(int i=0;i<=cnt;i++) System.out.print(a[ans[i]]+" ");
            System.out.println();
        }
    }
    public static int binary_search(int a[],int R,int x){
    
    
        int L=0;
        while(L<=R){
    
    
            int mid=(L+R)>>1;
            if(a[mid]<x) L=mid+1;
            else R=mid-1;
        }
        return L;
    }
}

代码在牛客网中进行测试,输出的子序列解未在其他OJ平台进行测试,如有误导,请多指教!

当然,更多解法和例题可参看:最长上升子序列 (LIS) 详解+例题模板 (全)

C++版本LIS长度:

#include<bits/stdc++.h>
using namespace std;
const int maxn =300003, INF = 0x7f7f7f7f;
int low[maxn], a[maxn];
int n, ans;
int binary_search(int *a, int R, int x)
//二分查找,返回a数组中第一个>=x的位置
{
    
    
    int L = 1, mid;
    while(L <= R)
    {
    
    
        mid = (L+R) >> 1;
        if(a[mid] < x)
            L = mid + 1;
        else
            R = mid - 1;
    }
    return L;
}

int main()
{
    
    
    scanf("%d", &n);
    for(int i=1; i<=n; i++)
    {
    
    
        scanf("%d", &a[i]);
        low[i] = INF;   //由于low中存的是最小值,所以low初始化为INF
    }
    low[1] = a[1];
    ans = 1;   //初始时LIS长度为1
    for(int i=2; i<=n; i++)
    {
    
    
        if(a[i] > low[ans])    //若a[i]>=low[ans],直接把a[i]接到后面
            low[++ans] = a[i];
        else
        {
    
    
            int j=lower_bound(low+1,low+1+ans,a[i])-low;
            low[j]=a[i];
        }
    }
    printf("%d\n", ans);   //输出答案
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dl962454/article/details/107434055