LeetCode題解 300 最長上升子序列

1. 題目描述

給定一個數字序列,求其最長上升子序列java

1.1. 測試用例

測試用例
int[] nums = {4,2,4,5,3,7};

預期結果
4, 序列是{2,4,5,7}

1.2. 函數簽名

public int lengthOfLIS(int[] nums){

}

2. 題解

2.1. 動態規劃解法

時間複雜度爲O(N^2)數組

2.1.1. 分析

  • 肯定狀態:dp[i]以nums[i]結尾的最長子序列的長度
  • 轉移方程: $dp[i] = max { 1, dp[j] + 1 } \quad 0 \le j < i 且 nums[i] \ge nums[j]$
    • 以nums[i]結尾的LIS,序列中nums[i]的上一個數字多是nums[i]以前的任何一個比它小的數, 假設是上一個數字nums[j], 則有j < i 且 nums[i] $\le$ ge nums[j], 此時的序列的長度爲dp[j] + 1
    • 也有可能nums[i]以前的數字沒有比它小的,那麼以它結尾的LIS長度就是1
  • 返回值。注意要返回dp數組中的最大值,而不是dp[n]

2.1.2. Java實現

public int longestIncreasingSubsequence(int[] nums) {
    if(nums == null || nums.length == 0){
        return 0;
    }
    //dp[i] : 以nums[i]結尾的LIS的長度
    int n = nums.length;
    int[] dp = new int[n] ;
    //初始化
    Arrays.fill(dp, 1);
    for(int i = 0; i < n; i++){
        //nums[i]以前的每個數字
        for(int j = 0; j < i; j++){
            if(nums[i] > nums[j]){
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    //dp中的最大值
    int res = dp[0];
    for (int i = 1; i < n; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

2.2. 二分解法

時間複雜度爲O(N*logN),單獨去理解比較難,在動態規劃的解法上進一步思考會更好理解函數

2.2.1. 分析

數組{4, 2, 4, 5, 2,7}, 用DP解法能夠獲得,dp = {1, 1, 2, 3, 2,7}。假設計算dp[3], DP解法在獲得dp[3]時,須要順序查找3以前的dp值中可用的(序列尾元素小於nums[3]的)最大值,其實可使用二分查找。但想使用二分查找前,先要證實一些結論。測試

經過dp數組咱們知道dp[3]前面的dp有:設計

  • dp[0],dp[1]對應長度爲1的序列{4}、{2},它們中最小的尾元素是2
  • dp[2]對應長度爲2的序列{*, 4}, 它們中最小的尾元素是4

結論1 : 數值相等的那些dp只須要查找最小的尾元素是否可用code

  • 分析:若是尾元素大的可用,尾元素小的必定可用,可是反過來不必定,因此最後的採用的必定會是dp相同的尾元素最小的其中一個

結論2 : dp值遞增時,它們中最小的尾元素確定也是遞增的class

  • 反證法,dp = len1的最小尾元素是t1,dp = len2的最小尾元素是t2, len1 < len2。若是t1 > t2 , 由於長爲len2的序列爲...t2, 其中確定有長爲len1的序列 ...t3...t2, t3<=t2, 和長爲len1的序列的最小尾元素t1 > t2矛盾

有了這兩個結論,能夠用tail[j]記錄dp值爲j的序列們的最小尾元素,dp[i]的值能夠經過對tail進行二分查找可用的尾元素值來獲得動態規劃

2.2.2. Java實現

public int longestIncreasingSubsequence(int[] nums) {
    int n = nums.length;
    //minTail[i] 全部長度爲i的子序列中最小的尾元素的值
    int[] minTail = new int[n + 1];
    minTail[0] = Integer.MIN_VALUE;
    //最後一個記錄的位置
    int maxLen = 0;
    for (int i = 0; i < n; i++) {
        int prePlace = 0;
        int start = 0, end = maxLen, mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            //nums[i]會放到最後一個比它小的堆的右邊
            if (nums[i] > minTail[mid]) {
                prePlace = mid;
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
        //更新nums[i]放的堆的最小值
        minTail[prePlace + 1] = nums[i];
        //全部的堆的最小值都比它小,另起一堆
        if (prePlace + 1 > maxLen) {
            maxLen = prePlace + 1;
        }
    }
    return maxLen;
}
相關文章
相關標籤/搜索