[Lintcode] Longest Increasing Subsequence 最長上升序列

Longest Increasing Subsequence

本文最新版本位於 https://yanjia.me/zh/2018/11/...

給定一個整數序列,找到最長上升子序列(LIS),返回LIS的長度。

樣例 給出[5,4,1,2,3],這個LIS是[1,2,3],返回 3數組

給出[4,2,4,5,3,7],這個LIS是[4,4,5,7],返回 4code

挑戰 要求時間複雜度爲O(n^2) 或者O(nlogn)排序

說明 最長上升子序列的定義:ip

最長上升子序列問題是在一個無序的給定序列中找到一個儘量長的由低到高排列的子序列,這種子序列不必定是連續的或者惟一的。
https://en.wikipedia.org/wiki...get

動態規劃法

複雜度

時間 O(N^2) 空間 O(N)it

思路

因爲這個最長上升序列不必定是連續的,對於每個新加入的數,都有可能跟前面的序列構成一個較長的上升序列,或者跟後面的序列構成一個較長的上升序列。好比1,3,5,2,8,4,6,對於6來講,能夠構成1,3,5,6,也能夠構成2,4,6。由於前面那個序列長爲4,後面的長爲3,因此咱們更願意6組成那個長爲4的序列,因此對於6來講,它組成序列的長度,其實是以前最長一個升序序列長度加1,注意這個最長的序列的末尾是要小於6的,否則咱們就把1,3,5,8,6這樣的序列給算進來了。這樣,咱們的遞推關係就隱約出來了,假設dp[i]表明加入第i個數能構成的最長升序序列長度,咱們就是要在dp[0]dp[i-1]中找到一個最長的升序序列長度,又保證序列尾值nums[j]小於nums[i],而後把這個長度加上1就好了。同時,咱們還要及時更新最大長度。io

代碼

public class Solution {
    public int longestIncreasingSubsequence(int[] nums) {
        // write your code here
        if(nums.length == 0){
            return 0;
        }
        // 構建最長升序序列長度的數組
        int[] lis = new int[nums.length];
        lis[0] = 1;
        int max = 0;
        for (int i = 1; i < nums.length; i++){
            // 找到dp[0]到dp[i-1]中最大的升序序列長度且nums[j]<nums[i]
            for (int j = 0; j < i; j++){
                if (nums[j] <= nums[i]){
                    lis[i] = Math.max(lis[i], lis[j]);
                }
            }
            // 加1就是該位置能構成的最長升序序列長度
            lis[i] += 1;
            // 更新全局長度
            max = Math.max(max, lis[i]);
        }
        return max;
    }
}

比較好理解的版本class

public class Solution {
    public int longestIncreasingSubsequence(int[] nums) {
        if(nums.length == 0){
            return 0;
        }
        int[] lis = new int[nums.length];
        int max = 0;
        for (int i = 0; i < nums.length; i++){
            int localMax = 0;
            // 找出當前點以前的最大上升序列長度
            for (int j = 0; j < i; j++){
                if (lis[j] > localMax && nums[j] <= nums[i]){
                    localMax = lis[j];
                }
            }
            // 當前點則是該局部最大上升長度加1
            lis[i] = localMax + 1;
            // 用當前點的長度更新全局最大長度
            max = Math.max(max, lis[i]);
        }
        return max;
    }
}

耐心排序法

複雜度

時間 O(NlogN) 空間 O(N)搜索

思路

1,3,5,2,8,4,6這個例子中,當到6時,咱們一共能夠有四種
(1)不一樣長度
(2)且保證該升序序列在同長度升序序列中末尾最小
的升序序列集合

1
1,2
1,3,4
1,3,5,6

這些序列都是將來有可能成爲最長序列的候選人。這樣,每來一個新的數,咱們便按照如下規則更新這些序列

  • 若是nums[i]比全部序列的末尾都大,或等於最大末尾,說明有一個新的不一樣長度序列產生,咱們把最長的序列複製一個,並加上這個nums[i]
  • 若是nums[i]比全部序列的末尾都小,說明長度爲1的序列能夠更新了,更新爲這個更小的末尾。
  • 若是在中間,則更新那個末尾數字剛剛大於等於本身的那個序列,說明那個長度的序列能夠更新了。

好比這時,若是再來一個9,那就是第三種狀況,更新序列爲

1
1,2
1,3,4
1,3,5,6
1,3,5,6,9

若是再來一個3,那就是第二種狀況,更新序列爲

1
1,2
1,3,3
1,3,5,6

若是再來一個0,那就是第一種狀況,更新序列爲

0
1,2
1,3,3
1,3,5,6

前兩種都很好處理,O(1)就能解決,主要是第三種狀況,實際上咱們觀察直到6以前這四個不一樣長度的升序序列,他們末尾是遞增的,因此能夠用二分搜索來找到適合的更新位置。

注意

  • 二分搜索時若是在tails數組中,找到咱們要插入的數,也直接返回那個結尾的下標,雖然這時候更新這個結尾沒有意義,但少了些判斷簡化了邏輯

代碼

public class Solution {
public int longestIncreasingSubsequence(int[] nums) {
    // write your code here
    if(nums.length == 0){
        return 0;
    }
    // len表示當前最長的升序序列長度(爲了方便操做tails咱們減1)
    int len = 0;
    // tails[i]表示長度爲i的升序序列其末尾的數字
    int[] tails = new int[nums.length];
    tails[0] = nums[0];
    // 根據三種狀況更新不一樣升序序列的集合
    for(int i = 1; i < nums.length; i++){
        if(nums[i] < tails[0]){
            tails[0] = nums[i];
        } else if (nums[i] >= tails[len]){
            tails[++len] = nums[i];
        } else {
        // 若是在中間,則二分搜索
            tails[binarySearch(tails, 0, len, nums[i])] = nums[i];
        }
    }
    return len + 1;
}

private int binarySearch(int[] tails, int min, int max, int target){
    while(min <= max){
        int mid = min + (max - min) / 2;
        if(tails[mid] == target){
            return mid;
        }
        if(tails[mid] < target){
            min = mid + 1;
        }
        if(tails[mid] > target){
            max = mid - 1;
        }
    }
    return min;
}

}

相關文章
相關標籤/搜索