動態規劃解最長上升子序列(全)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章