Leetcode#300 最長上升子序列

題目:

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

輸入:

一個無序整數數組,

輸出:

該整數數組的最長上升子序列的長度

示例:

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

說明:

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

思路:

分析:

  1. 首先仔細審題,明確題目中的條件。
    子序列:不要求連續子序列,只要保證元素前後順序一致即可;
    上升:這裏的“上升”是“嚴格上升”,類似於 [2, 3, 3, 6, 7] 這樣的子序列是不符合要求的;
  2. 一個序列可能有多個最長上升子序列,題目中只要我們求這個最長的長度。如果使用回溯搜索,選擇所有的子序列進行判斷,時間複雜度爲 O((2n)n)O( (2^n) * n )
  3. 觀察輸入的規模,以第 i 個數字結尾的最長上升子序列長度取決於前 i 個數字,與其後的數字無關。可以使用動態規劃解決該問題,用dp數組記錄以每個數字結尾時的最長子序列的長度,即我們要求 nums[i] 必須被選取。反正一個子序列一定要以一個數字結尾,那我就將狀態這麼定義,這一點是重要且常見的

方法一:DP解法
子問題: 以第 ii 個元素結尾的最長子序列的長度
最優子結構: dp[i]
決策: 以第 ii 個元素結尾
遞推關係式:
只要數組 nums 長度不爲0,則最長子序列長度最小值爲1,故將 dp 數組初始化爲1;
計算以第 i 個數字結尾的最長子序列長度 dp[i] 時,我們應該把索引是 [0, … ,i - 1] 的最長子序列長度 dp[j] 都看一遍,如果當前的數 nums[i] 大於之前的某個數nums[j],那麼 nums[i] 就可以接在 nums[j] 後形成一個更長的子序列。dp[i] 是所有 dp[j] 中的最大值加 1 (即加nums[i]本身的長度1)。
dp[i]={1,initialize dp[i] for each i=0,1...n;max(dp[j]+1),if j<i and nums[i]>nums[j]. dp[i]=\left\{ \begin{aligned} 1 & , & initialize\ dp[i]\ for\ each\ i = 0,1...n ;\\ max(dp[j]+1) & , &if\ j < i\ and\ nums[i] > nums[j].\\ \end{aligned} \right.
示例:
數組:   [10,9,2,5,3,7,101,18]\;[10, 9, 2, 5, 3, 7, 101, 18]
dp 的值:1    1    1  2    2    3441 \;\; 1 \;\; 1 \; 2 \;\; 2 \;\; 3 \quad 4 \quad 4
時間複雜度: O(n2)O(n^2)
空間複雜度: O(n)O(n)
實現代碼:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        vector<int>dp(nums.size(), 1);
        int max_len = INT_MIN;
        for(int i = 0; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            max_len = max_len < dp[i] ? dp[i] : max_len;
        }
        return max_len;
    }
};

方法二:貪心 + 二分查找
進階部分,若要將算法複雜度降到O(nlogn)O(nlogn),看到這個複雜度一般思路就是使用二分查找。
貪心思想:如果要使上升子序列儘可能的長,則需要讓序列上升得儘可能慢,因此希望每次在上升子序列最後加上的那個數儘可能的小。
維護一個數組dp[], dp[length]表示遞增子序列長度爲length時,序列最後的那個元素值。
初始化: dp[1]=nums[0]
遍歷nums,如果 nums[k]>dp[length],則 dp[length+1] = nums[k], length++;否則,使用二分查找,在dp[1]~dp[length]中查找從左到右第一個大於nums[k]的元素dp[j],更新dp[j]=nums[k]
時間複雜度: O(nlogn)O(n*logn)
空間複雜度: O(n)O(n)
實現代碼:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        vector<int>dp(nums.size() + 1, 0);
        dp[1] = nums[0]; 
        int max_len = 1;
        for(int i = 1; i < nums.size(); i++){
            if(nums[i] > dp[max_len]){
                dp[++max_len] = nums[i];
            }
            else{
                //二分查找
                int left = 1, right = max_len;
                int mid;
                while(left < right){
                    mid = left + (right - left) / 2;
                    if(nums[i] <= dp[mid]){
                        right = mid;
                    }
                    else{
                        left = mid + 1;
                    }
                }
                dp[left] = nums[i];               
            }
        }
        return max_len;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章