算法-動態規劃-最長上升子序列

算法-動態規劃-最長上升子序列

1 題目概述

1.1 題目出處

https://leetcode-cn.com/problems/longest-increasing-subsequence/

1.2 題目描述

給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:

輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。
說明:

可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
你算法的時間複雜度應該爲 O(n2) 。

進階: 你能將算法的時間複雜度降低到 O(n log n) 嗎?

2 動態規劃

2.1 思路

如果設dp[i]表示前i個的最長上升子序列長度,那麼很難找到他和dp[i-1]等的關係。

而考慮設dp[i]表示以下標i的數字結尾的上升子序列最大長度, 則:

dp[i] = max(dp[0],dp[1],dp[2],...dp[j]...,dp[i-1]) + 1
且 nums[j] < nums[i]

2.2 代碼

class Solution {
    public int lengthOfLIS(int[] nums) {
        // 全局最長上升子序列長度
        int result = 0;
        if(null == nums || nums.length == 0){
            return 0;
        }

        // 如果設dp[i]表示前i個的最長上升子序列長度
        // 那麼很難找到他和dp[i-1]等的關係
        
        // 考慮設dp[i]表示以下標i的數字結尾的上升子序列最大長度
        // 則dp[i] = max(dp[0],dp[1],dp[2],...dp[j]...,dp[i-1]) + 1,且 nums[j] < nums[i]
        int[] dp = new int[nums.length];
        
        for(int i = 0; i < nums.length; i++){
            // 以nums[i]結尾的最長上升子序列長度
            int tmpMax = 0;
            for(int j = i - 1; j >= 0; j--){
                if(nums[j] < nums[i]){
                    tmpMax = Math.max(tmpMax, dp[j]);
                }
            }
            // 最小長度是1,也就是說前面的數都不小於nums[i],則nums[i]本身組成一個長度爲1的子序列
            dp[i] = tmpMax + 1;
            // 如果比全局更長就更新
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}

2.3 時間複雜度

在這裏插入圖片描述
O(N^2)

2.4 空間複雜度

O(N)

3 有序數組+二分查找

3.1 思路

目標將算法的時間複雜度降低到 O(n log n) ,那麼能想到的是二分或者歸併之類的。

但是數組本身無需,要找最長上升子序列,不可能先排序再找吧?

而dp[i]也是無序的,也無法使用二分查找。

有一種思路,使用一個有序數組。當遍歷到一個元素大於該數組的尾元素(最大的),就放置在末尾;否則就使用二分查找,如果找到就不動,找不到就替換比目標元素大的右邊那個元素。這樣替換的依據是,已經用末尾最大元素限制了長度,更小的元素只能替換而不能增加該序列長度!

這樣的好處是,該有序數組長度就表示了最長上升子序列長度,而且複雜度優化到O(NlogN)!

3.2 代碼

3.2.1 遞歸版本

class Solution {
    private List<Integer> resultList = new ArrayList<>();
    public int lengthOfLIS(int[] nums) {
        // 全局最長上升子序列長度
        int result = 0;
        if(null == nums || nums.length == 0){
            return 0;
        }

        
        resultList.add(nums[0]);
        for(int i = 1; i < nums.length; i++){
            if(nums[i] > resultList.get(resultList.size() - 1)){
                resultList.add(nums[i]);
            }else{
                binaryInsert(nums[i], 0, resultList.size() - 1);
            }
        }
        return resultList.size();
    }

    private void binaryInsert(int target, int start, int end){
        int mid = (start + end) / 2;
        if(resultList.get(mid) == target){
            return;
        } else if(resultList.get(mid) < target){
            if(mid == end){
                // 因爲我們已經提前判斷過target大於數組尾元素情況,
                // 所以這裏不會出現end+1不存在的情況
                resultList.set(end + 1, target);
            }else{
                binaryInsert(target, mid + 1, end);
            }
        }else{
            // resultList.get(mid) > target
            if(mid == start){
                resultList.set(mid, target);
            }else{
                binaryInsert(target, start, mid - 1);
            }
        }
    }
}

3.2.2 循環版本

class Solution {
    private List<Integer> resultList = new ArrayList<>();
    public int lengthOfLIS(int[] nums) {
        // 全局最長上升子序列長度
        int result = 0;
        if(null == nums || nums.length == 0){
            return 0;
        }

        resultList.add(nums[0]);
        for(int i = 1; i < nums.length; i++){
            if(nums[i] > resultList.get(resultList.size() - 1)){
                resultList.add(nums[i]);
            }else{
                int left = 0;
                int right = resultList.size() - 1;
                while(left <= right){
                    int mid = left + (right - left) / 2;
                    if(resultList.get(mid) == nums[i]){
                        // 相同值的忽略
                        break;
                    } else if (resultList.get(mid) < nums[i]){
                        left = mid + 1;
                    } else if (resultList.get(mid) > nums[i]){
                        right = mid - 1;
                    }
                }
                // right == left時,resultList.get(mid) > nums[i]
                // 不可能resultList.get(mid) < nums[i]
                // 因爲我們提前判斷了nums[i] > resultList.get(resultList.size() - 1)
                // 所以這裏我們將目標數字放在left位置即可
                if(right < left){
                    resultList.set(left, nums[i]);
                }
            }
        }
        return resultList.size();
    }
}

3.3 時間複雜度

3.3.1 遞歸版本

在這裏插入圖片描述
O(NlogN)

3.3.2 循環版本

在這裏插入圖片描述
O(NlogN)

3.4 空間複雜度

O(K)

  • 取決於最長子序列長度K
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章