[LeetCode]Longest Increasing Subsequence

題目鏈接:https://leetcode.com/problems/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 is 4. 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(n^2) complexity.

Follow up: Could you improve it to O(nlogn) time complexity?

題目解法

這是一道典型的動態規劃問題,通常有兩種解法,一種自然的思想時間複雜度爲O(n^2),而另一種巧妙地思路可以利用二分查找把時間複雜度降低到O(nlogn)。下面分別介紹這兩種做法。

首先我們約定nums爲輸入容器,下標從0開始。

  • 解法一

    dp[i]表示以nums[i]結尾的最長遞增子列(LIS)的長度。由於最短的LIS就是自己本身,因此我們先初始化dp[.]=1。接着,從前到後遍歷整個nums容器,按照下面的規則更新dp。

    • dp[i] = max{dp[j]+1|0<=j<i,nums[i] > nums[j]}

    • 通俗來說,也就是第i個位置爲結尾的LIS長度,取決於前面0~i-1位置的LIS長度,如果前面的LIS的最後一個元素比當前位置的元素小(nums[i] > nums[j]),則可以把當前的結尾加入到前面的LIS尾部,構成一個比原來長度+1的LIS。因爲我們是從前到後來更新dp,因此在更新dp[i]時已經得到了所有的dp[j],j<i,我們只需要從中選取最大的。在遍歷結束後,也就得到了以每個數爲結尾的LIS,只要把dp排序,取出最大的元素即爲整個序列的LIS。

    代碼如下:

    class Solution {
    public:
    int lengthOfLIS(vector<int>& nums) {
        int cnt = nums.size();
        int *dp = (int*)malloc(cnt*sizeof(int));
        for(int i = 0; i < cnt; i++) dp[i] = 1;
        for(int i = 1; i < cnt; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i] && dp[i] < dp[j] + 1){
                    dp[i] = dp[j] + 1;
                }
            }
        }
        sort(dp,dp+cnt);
        return dp[cnt - 1];
    }
    };
  • 解法二

    d[len]表示長度爲len的最長子列的最小末尾元素,這裏之所以說是最小,是因爲可能出現多個解,而顯然其中結尾最小的最具有變得更長的潛力,因此我們應該選取結尾最小的作爲最優解。當我們遍歷nums[i]中的每個元素時,都能在d[len]中找到合適的位置插入它,規則爲:

    • nums[i]d[len]還大,說明可以插入到當前LIS的尾部,也就是可以找到一個更長的LIS,這時候我們更新d[++len]=nums[i],這樣不僅更新了LIS的尾部,也更新了長度。

    • nums[i]不比d[len]大時,並不能改變LIS的長度,但是可以對LIS進行優化,比如更新一個長度比len小的LIS的尾部元素爲更小的值,雖然一次這樣的更新可能無法帶來len的增加,但是可以減小d[len],例如序列10,9,2,5,3,7,我們開始會得到d[1]=10,接着9的到來會更新d[1]=92的到來會更新d[1]=2,只有這樣,當5到來時,才能得到5>d[1],從而得到d[2]=5

    按照上面的描述,我們發現算法的關鍵不再是對len的計算,而是元素插入位置的計算,插入元素的時間複雜度即爲找到LIS的時間複雜度,因此我們使用二分查找來插入,由於我們所謂的插入其實是一種覆蓋,因此應該找到下界來防止d[len]被錯誤地覆蓋,使用STL的lower_bound即可。

    代碼如下:

    class Solution {
    public:
    int lengthOfLIS(vector<int>& nums) {
        int cnt = nums.size();
        if(cnt == 0) return 0;
        int *d = (int*)malloc((cnt+1)*sizeof(int));
        int len = 1;
        d[len] = nums[0];
        for(int i = 1; i < cnt; i++){
            if(nums[i]>d[len]){
                d[++len]=nums[i];
            }else{
                int pos = lower_bound(d,d+len,nums[i]) - d;
                d[pos] = nums[i];
            }
        }
        return len;
    }
    };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章