• 最长上升子序列 (LIS)


    1.LIS的定义:
            最长上升子序列(longest  increasing subsequence),也可以叫最长非降序子序列,简称LIS,不是太难。即一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N,但必须按照一定。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8)。

            是不是觉得好抽象~没事我给你解释~

            首先需要知道,子串和子序列的概念,我们以字符子串和字符子序列为例,更为形象,也能顺带着理解字符的子串和子序列:

         (1)字符子串指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。

         (2)字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。

           知道了这个,数值的子序列就很好明白了,即用数组成的子序列。这样的话,最长上升子序列也很容易明白了,归根结底还是子序列,然后子序列中,按照上升顺序排列的最长的就是我们最长上升子序列了,这样听来是不是就很容易明白啦~

           还有一个非常重要的问题:请大家用集合的观点来理解这些概念,子序列、公共子序列以及最长公共子序列都不唯一,但很显然,对于固定的两个数组,虽然最LIS不一定唯一,但LIS的长度是一定的。

    解法1:动态规划:
      我们都知道,动态规划的一个特点就是当前解可以由上一个阶段的解推出, 由此,把我们要求的问题简化成一个更小的子问题。子问题具有相同的求解方式,只不过是规模小了而已。最长上升子序列就符合这一特性。我们要求n个数的最长上升子序列,可以求前n-1个数的最长上升子序列,再跟第n个数进行判断。求前n-1个数的最长上升子序列,可以通过求前n-2个数的最长上升子序列……直到求前1个数的最长上升子序列,此时LIS当然为1。

      让我们举个例子:求 2 7 1 5 6 4 3 8 9 的最长上升子序列。我们定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度。

      前1个数 d(1)=1 子序列为2;

      前2个数 7前面有2小于7 d(2)=d(1)+1=2 子序列为2 7

      前3个数 在1前面没有比1更小的,1自身组成长度为1的子序列 d(3)=1 子序列为1

      前4个数 5前面有2小于5 d(4)=d(1)+1=2 子序列为2 5

      前5个数 6前面有2 5小于6 d(5)=d(4)+1=3 子序列为2 5 6

      前6个数 4前面有2小于4 d(6)=d(1)+1=2 子序列为2 4

      前7个数 3前面有2小于3 d(3)=d(1)+1=2 子序列为2 3

      前8个数 8前面有2 5 6小于8 d(8)=d(5)+1=4 子序列为2 5 6 8

      前9个数 9前面有2 5 6 8小于9 d(9)=d(8)+1=5 子序列为2 5 6 8 9

      d(i)=max{d(1),d(2),……,d(i)} 我们可以看出这9个数的LIS为d(9)=5

      总结一下,d(i)就是找以A[i]结尾的,在A[i]之前的最长上升子序列+1,当A[i]之前没有比A[i]更小的数时,d(i)=1。所有的d(i)里面最大的那个就是最长上升子序列。其实说的通俗点,就是每次都向前找比它小的和比它大的位置,将第一个比它大的替换掉,这样操作虽然LIS序列的具体数字可能会变,但是很明显LIS长度还是不变的,因为是替换掉了,并没有改变增加或者减少长度。但是我们通过这种方式是无法求出最长上升子序列具体是什么的,

    状态设计:F[ i ]代表以A[ i ]结尾的LIS的长度

    状态转移:F[ i ]=max{ F[ j ]+1 ,F[ i ] }(1<=j< i,A[j]< A[i])

    边界处理:F[ i ]=1(1<=i<=n)

    时间复杂度:O(n^2)

    代码实现:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    using namespace std;
    const int N = 1e4+10;
    int main() 
    {
    	int a[N],b[N],i,j,n;
    	while(cin>>n){
    		for(i = 0; i < n; i++){
    			cin>>a[i];
    			b[i] = 1;
    		}
    		for(i = 0; i < n; i++){
    			for(j = 0; j < i; j++){
    				if(a[j]<a[i]){
    					b[i] = max(b[j]+1,b[i]);
    				}
    			}
    		}
    		sort(b,b+n);
    		cout<<b[n-1]<<endl;
    	}
    	return 0;
    }
    

      这个算法的时间复杂度为〇(n²),并不是最优的算法。一般来说都可以通过,但是在限制条件苛刻的情况下,这种方法行不通。

  • 相关阅读:
    多线程系列教材 (四)- 常见的线程安全相关的面试题
    多线程系列教材 (三)- Java 多线程同步 synchronized 详解
    多线程系列教材 (二)- Java 常见的线程方法
    UPC-最优分解问题(贪心)
    【数论】【模板】
    UPC-人品指数(模拟)
    UPC-趾压板矩阵(强行找规律)
    UPC-排队出发+AcWing-耍杂技的牛(推公式的贪心)
    UPC-购买巧克力(贪心)
    前端——多行三列模式页面布局
  • 原文地址:https://www.cnblogs.com/clb123/p/10431221.html
Copyright © 2020-2023  润新知