动态规划解最长上升子序列(全)

1、动态规划问题导览:

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

2、常见面试题:

题目描述:

给定一组整数序列,求最长升序子序列的长度。

如序列(1,7,3,5,9,4,8)中包含(1,7),(3,4,8)等多个升序子序列,其中最长的是(1,3,5,8),因此最长升序子序列长度为4.

   输入描述:

输入为2行,第一行是一个正整数n(1<=n<=10000),表示序列长度,第二行包含n个int类型证书的序列,空格分隔。

   输出描述:

输出最长升序子序列的长度

②LeetCode中题目:

Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

For example,

Given[10, 9, 2, 5, 3, 7, 101, 18],

The longest increasing subsequence is[2, 3, 7, 101], therefore the length is4. Note that there may be more than one LIS combination, it is only necessary for you to return the length.

Your algorithm should run in O(n2) complexity.

Follow up:Could you improve it to O(nlogn) time complexity?

Credits:

Special thanks to@pbrotherfor adding this problem and creating all test cases.

Subscribeto see which companies asked this question.

翻译:

给出一个无序的整形数组,找出它的最大升序子序列。
举个栗子,
示例数组 arr[] = {10, 9, 2, 5, 3, 7, 101, 18},
数组arr的最长升序子序列是 {2, 3, 7, 101},因此长度是4。
请注意,可能有一个以上的LIS(最长上升子序列)的组合,只需要返回长度就好。
时间复杂度O(n²)前提下实现。
进阶:时间复杂度O(nlogn)前提下实现。

原题链接

3、针对用动态规划解最长上升子序列问题,我们下面举个栗子深入理解一下:

       求 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,即最长升序子序列长度为5.

4、接下来我们用代码实现一下:(编程语言为:java)

package test;

import java.util.Scanner;
/**
 * 
 * @author FHY
 * time complicity : O(n^2)
 * spatial complicity : O(1)
 *
 */

public class DynamicTest1{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int len = sc.nextInt();   //insert array's length
        int[] arr = new int[len];   
        sc.nextLine();
        String s = sc.nextLine();  //insert array
        String[] arrs = s.split(" ");
        for(int i=0;i< len;i++){   //transform array format
            arr[i] = Integer.parseInt(arrs[i]);
        }
        System.out.println(getMax(len,arr));
    }

    public static int getMax(int len,int[] nums) {
        if(len < 2 )
            return len;
        int dp[] = new int[len];  //define array to store smaller than this element
        dp[0] = 1;
        int maxlength = 1;  //the max length of ascending array
        for(int i=1 ; i<len ;i++){
            int thismax = 0;
            for(int j=0 ; j<i ; j++){
                if(nums[j] <= nums[i]){
                    thismax = Math.max(dp[j],thismax);
                }
            }
            dp[i] = thismax + 1;
            maxlength = Math.max(maxlength,dp[i]);
        }
        return maxlength;
    }
}

5、性能分析:39ms

6、对该算法进行简单分析及进阶:

这个算法的时间复杂度为〇(n²),并不是最优的算法。在限制条件苛刻的情况下,这种方法行不通。那么怎么办呢!有没有时间复杂度更小的算法呢?说到这里了,当然是有的啦!还有一种时间复杂度为〇(nlogn)的算法,下面就来看看。

 我们再举一个例子:有以下序列A[]=3 1 2 6 4 5 10 7,求LIS长度。

    我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[i]有序地放进B[i]里。(为了方便,i的范围就从1~n表示第i个数)

  A[1]=3,把3放进B[1],此时B[1]=3,此时len=1,最小末尾是3

  A[2]=1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1

  A[3]=2,2大于1,就把2放进B[2]=2,此时B[]={1,2},len=2

  同理,A[4]=6,把6放进B[3]=6,B[]={1,2,6},len=3

  A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[]={1,2,4},len=3

  A[6]=5,B[4]=5,B[]={1,2,4,5},len=4 

  A[7]=10,B[5]=10,B[]={1,2,4,5,10},len=5

  A[8]=7,7在5和10之间,比10小,可以把B[5]替换为7,B[]={1,2,4,5,7},len=5

  最终我们得出LIS长度为5。但是,但是!!这里的1 2 4 5 7很明显并不是正确的最长上升子序列。是的,B序列并不表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步7替换10并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”。假如后面还有两个数据8和9,那么B[6]将更新为8,B[7]将更新为9,len就变为7。读者可以自行体会它的作用。

7、对以上进阶算法的代码实现 :(java语言实现)

package test;

import java.util.Scanner;

/**
 * 
 * @author FHY
 * time complicity : O(nlog(n))
 * spatial complicity : O(n)
 * 
 */
public class DynamicTest2 {
	public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int len = sc.nextInt();   //insert array's length
            int[] arr = new int[len];   
            sc.nextLine();
            String s = sc.nextLine();  //insert array
            String[] arrs = s.split(" ");
            for(int i=0;i< len;i++){   //transform array format
                arr[i] = Integer.parseInt(arrs[i]);
            }
            System.out.println(getMaxLength(arr, len));      
	}


	public static int getMaxLength(int[] array, int numsize){
            if(numsize<2){
		return numsize;
	    }
            int[] store = new int[numsize];  //define new array to store sorted array
            store[0] = array[0];
            int length = 1;
            int position; // the result of halfSearch
	    for(int i=1; i<numsize; i++){
	    	if(array[i]>store[length-1]){
	    		store[length++] = array[i];	    		
	    	}else{
	    		position = halfSearch(store, array[i], length); //find the position to store this element
	    		System.out.println(position);
	    		store[position] = array[i];
	    	}
	    }
	    return length;
	}


	public static int halfSearch(int[] array, int key, int length){
		int left = 0;
		int right = length-1;
		int middle = 0;
		while(left<=right){
			middle = left + (right-left)/2;
			if(array[middle]>key){
				right = middle-1;
			}else if(array[middle]<key){
				left = middle+1;
			}else{
				return middle;
			}	
		}
		return left;
	}

}

8、性能上的巨大提升:1ms

以上内容分析部分参考博客:https://www.cnblogs.com/GodA/p/5180560.html

代码思想参考:https://www.jianshu.com/p/6fc5f3c04968

猜你喜欢

转载自blog.csdn.net/fhy569039351/article/details/82970711