【LeetCode】最長遞增子序列(動態規劃、二分查找)

LeetCode第300題題目地址

題目描述:

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

例子:

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

解法一:動態規劃

動態規劃的核心設計思想是數學歸納法。從第一個元素開始找,你思考的過程,將其歸納總結,轉化爲用代碼描述你思考的過程。

馬士兵老師說過,代碼不是一口氣寫出來的,一點點拆解,最後再組合,再去調試,處理邊界,最終將問題解決。這裏我們拆開解決解決。

1.狀態定義:dp[i] 表示以 nums[i] 這個數結尾的最長遞增子序列的長度。狀態的初始值,根據這個定義,我們就可以推出 base case:dp[i] 初始值爲 1,因爲以 nums[i] 結尾的最長遞增子序列起碼要包含它自己。

最終的返回結果爲res,那麼遍歷數組,找出最大的dp[i],代碼如下:

int res = 0;
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
    res = Math.max(res, dp[i]);
}

接下來看dp[i]應該怎麼計算,也就是狀態方程該如何確定吶?看下面的例子,根據剛纔我們對 dp 數組的定義,現在想求 dp[5] 的值,也就是想求以 nums[5] 爲結尾的最長遞增子序列。

index 0 1 2 3 4 5
nums 10 9 2 5 3 7
dp 1 1 1 2 2

nums[5] = 7,既然是遞增子序列,我們只要找到前面那些結尾比 7 小的子序列,然後把 7接到最後,就可以形成一個新的遞增子序列,而且這個新的子序列長度加一。可能形成很多種新的子序列,但是我們只選擇最長的那一個,把最長子序列的長度作爲 dp[5] 的值即可。代碼描述:

//求第i個元素的最長子序列
for (int j = 0; j < i; j++) {
    if (nums[j] < nums[i]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
    }
}

到這裏,核心部分就寫完了,來看下完整代碼:

public int lengthOfLIS(int[] nums) {
    if (nums.length == 0) return 0;
    int res = 0;
    int[] dp = new int[nums.length];
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
        }
        res = Math.max(res, dp[i]);
    }
    return res;
}

時間複雜度O(n^2),空間複雜度O(n)

解法二:二分查找

解析:參考的jyd大神的解題方法,短而精湛。

1.狀態定義:tails[k] 的值代表長度爲k+1子序列 的尾部元素值。

2.設 res 爲 tails當前長度,代表直到當前的最長上升子序列長度。設 j∈[0,res),考慮每輪遍歷 nums[k] 時,通過二分法遍歷 [0,res)列表區間,找出 nums[k]的大小分界點,會出現兩種情況:

  • 區間中存在 tails[i] > nums[k]: 將第一個滿足 tails[i] > nums[k]執行 tails[i] = nums[k] ;因爲更小的 nums[k]後更可能接一個比它大的數字(前面分析過)。
  • 區間中不存在 tails[i] > nums[k]: 意味着 nums[k]可以接在前面所有長度的子序列之後,因此肯定是接到最長的後面(長度爲 res),新子序列長度爲res + 1。

3.初始狀態:令tails列表所有值=0。

4.返回值:返回res,即最長上升子子序列長度。

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] tails = new int[nums.length];
        int res = 0;
        for(int num : nums) {
            int i = 0, j = res;
            while(i < j) {
                int m = (i + j) / 2;
                if(tails[m] < num) i = m + 1;
                else j = m;
            }
            tails[i] = num;
            if(res == j) res++;
        }
        return res;
    }
}

時間複雜度O(nlogn),空間複雜度O(n)

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