1. 程式人生 > >Leetcode題解系列——300. Longest Increasing Subsequence與673. Number of Longest Increasing Subsequenc(c++版)

Leetcode題解系列——300. Longest Increasing Subsequence與673. Number of Longest Increasing Subsequenc(c++版)

題目連結:300.Longest Increasing Subsequence

題目大意:最長增長子序列,這是一道經典的動態規劃問題。要求輸出一串數字的最長增長子序列的長度。

增長子序列,要求nums[i] < nums[j], when i < j.

注意點:

  1. 使用動態規劃時,序列長度初始的值為1還是0。
  2. 注意判斷陣列長度為0的情況。

一.演算法設計

這裡有兩種時間複雜度的解法。

1.O( n 2
n^2
)的方法。

對於每一個數組中的值,檢視前面是否有比它小的值,如果有,則改變當前的序列長度為前面的序列長度+1,最後輸出len中最大的值,即為該陣列的最長增長子序列的長度。

由於具有n個狀態,每個狀態遷移需要O(n),故總時間複雜度為O( n 2

n^2 )。

2.O(nlogn)的方法

利用二分查詢與貪心演算法的結合來實現。由於題目不需要輸出子序列的具體序列,只需要輸出長度,故我們可以每次找遞增子序列的最後一個數的最小值放入res中,這就是貪心的思想。

每次找該長度i的最小值,保證了後面能有更多的空間查詢比這個數的較大值,故一定可以找到最長子序列。

而在查詢的時候使用二分搜尋,可以降低搜尋的時間複雜度。

故最後的總的時間複雜度為O(nlogn)

二.程式碼實現

O( n

2 n^2 )複雜度演算法示例

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.empty()) return 0;
        int _size = nums.size();
        int count = 1;
        int len[10000] = {0};
        //設定所有序列長度的初始值為1
        fill(len,len+_size,1);
        for(int i = 1; i < _size; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    len[i] = max(len[i], len[j]+1);
                }
            }
            count = max(count,len[i]);
        }
        return count;
    }
};

O(nlogn)複雜度演算法示例

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> res;
        for(int num:nums){
        	// 二分查詢res中是否有比num還要大於等於的數
            auto it = lower_bound(res.begin(), res.end(), num);
            // 若沒有找到,則是第一次訪問到最長序列的最後元素
            if(it == res.end()){
                res.push_back(num);
            }
            //若找到了,則將小的num值賦予找到的下標元素
            else{
                *it = num;
            }
        }
        // 最後,res所儲存的是最長序列的最後一個數的最小值
        // 而size就是最長序列的長度
        return res.size();
    }
};

三.演算法變形

通過這一演算法,我們還可以算出最長子序列的個數。

題目連結:673. Number of Longest Increasing Subsequence

這時候,我們要利用第一種O( n 2 n^2 )的演算法,而不能使用O(nlogn)的演算法,因為O(nlogn)的演算法僅僅能找出最長子序列的長度,以及儲存每一個位置的最小值,而不能找出所有子串。

統計有多少個最長字串,即找出每一個位置能有多少個數字可以擴充套件

所以可以定義狀態ans[i]表示,當前i位置,達到最長遞增子序列的方法個數

如果下一個結點不是第一次到達該長度,說明有新的路徑產生
ans[i] += ans[j]
如果該節點為第一次到達最長長度,之前的ans[i]必須賦值回1,即只有一條路能到達
ans[i] = ans[j]

最後,只需要所有等價於最大子序列長度的最後一個元素下標,通過這些最後數字的下標的ans值疊加來得到最長子序列的個數。

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
       if(nums.empty()) return 0;
        int _size = nums.size();
        int max_len = 1;
        int count = 0;
        int len[10000] = {0};
        int ans[10000] = {0};
        fill(ans,ans+_size,1);
        fill(len,len+_size,1);
        for(int i = 1; i < _size; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    //不是第一次到達
                    if(len[i] == len[j]+1){
                        ans[i] += ans[j];
                    }
                    //第一次到達
                    else if(len[i] < len[j]+1){
                        len[i] = len[j]+1;
                        ans[i] = ans[j];
                    }
                }
            }
            max_len = max(max_len,len[i]);
        }
        
        for(int i = 0; i < _size; i++){
            if(len[i] == max_len) count += ans[i];
        }
        return count;
    }
};