最長遞增子序列 | Medium | 300. Longest Increasing Subsequence
題目敘述

- 題目難度:
Medium - 題目敘述: 題目給定一個整數陣列
nums,回傳所有可能的遞增子序列中最長的長度
子序列(Subsequence) 可由原先陣列中刪除多個元素來得到,但不可更動其元素順序,其中遞增子序列代表元素由左至右數字漸增
Ex. [5,8,3,2,4,5,9,15,7,20]
其子序列包含:[5,8,4,5,20] 非遞增子序列[2,4,9,15] 遞增子序列[15,7,20] 非遞增子序列[5,9,15,20] 遞增子序列
解法
一開始的想法
一開始的想法比較偏向暴力解,一開始先思考要怎麼手動找出遞增子序列,並且要找到最長的。
對於 nums = [10,9,2,5,3,7,101,18]
1 | i=0 [10,9] |
接著就會是每一輪迭代
1 | i=0 [9,2] |
我的解答
錯誤做法
1 | int lengthOfLIS(vector<int>& nums){ |
這是我一開始的寫法,透過一個 tempList 來保存子序列,並且透過兩個迴圈來查看遞增子序列,同時更新最長序列長度。但這個程式會有問題:
- 遺漏所有可能的子序列,內層迴圈會以
nums[i]為起點,並且一旦選擇某一個數字作為當前遞增子序列的一部分後,沒有回頭檢查其他可能的分支
Example: [0,1,0,3,2,3],在選擇 [0,1,3] 就可能忽略 [0,1,2] 這個子序列
- 無法回朔選擇
tempList沒有特別用意,就只是為了計算長度- 時間複雜度高
正確做法
1 | int lengthOfLIS(vector<int>& nums){ |
改良版本收先先針對 Edge Case做處理,但也可忽略(畢竟題目給的條件會至少會有一個元素),接著宣告 dp 陣列, dp[i] 代表以 nums[i] 為結尾的最長遞增子序列的長度,初始化為 1,因為每個元素至少可以單獨成為長度為 1 的子序列。
接著是雙層迴圈,外層迴圈用來跑 nums[i],由於 i=0 的時候,第一個元素本身就已經是長度為1 的子序列,並且如果外層為 0,內層就不會有 j < i 的條件在,所以可以跳過這個狀況。
而內層為 0,對於每個 nums[i] 可以檢查 nums[0] 到 nums[i-1] 是否有小於 nums[i] 的元素,如果存在 nums[j] < nums[i],則可以將 nums[i] 接在 nums[j] 結尾的子序列之後 (dp[j] + 1, 當前元素也占用一個子序列長度)
由於會遍歷不同的 j 值,並且更新到 dp[i],因此一旦有 nums[i] < nums[j] 狀況時,就可以比較 dp[j]+1 與上一輪更新的 dp[i] 誰比較長,最後,每一次內層迴圈跑完,就代表以 nums[i] 為頭的子序列已經找完一輪了,因此可以更新 max_length。雙層迴圈跑完最後回傳 max_length 即可。
執行結果

最佳化解答
這是由 geeksforgeek 提供的最佳化解答,時間複雜度僅有 $O(nlogn)$,這段主要是透過 Binary Search 來實現
1 | int lengthOfLIS(vector<int>& nums){ |
這裡需要說明一下,首先宣告一個用來儲存最長子序列的陣列 res (並且是遞增子序列),首先把第一個 nums[0] pusH 到 res 中,之後對於每個 nums[i] 有兩種可能的操作,若 nums[i] 大於 res 中的最末端元素(最大元素),則直接push到 res 的末端。
但如果 nums[i] 小於 res 中的末段元素,但他也有可能會是其他子序列的頭或尾端,因此需要找到他比哪個 res 中的值還要大,再加入進 res 中的對應位置。這時候就要用到 <bits/stdc++.h> 中的函數 std::lower_bound() 它可以用來找到 res 中第一個不小於 nums[i]的值
The lower_bound function returns an iterator pointing to the first element that is not less than the current number.
之後獲取該值的 index 並且保存到變數 low,接著替換 res 中 index 為 low 的值為 nums[i]
1 | [1,2,7,8,3,4,5,9,0] |
對於上面的範例,最長子序列為
[1,2,3,4,5,9]且長度為 6
執行結果

複雜度
| 方法 | 時間複雜度 | 空間複雜度 | 優勢 | 適用情境 |
|---|---|---|---|---|
| 動態規劃 (DP) | $O(n^2)$ | $O(n)$ | 簡單易懂,適合中小規模陣列 | 當數組長度 $n \leq 10^3$ 時 |
| 二分搜尋法 (貪婪法) | $O(n \log n)$ | $O(n)$ | 更高效,適合處理大規模數陣列 | 當數組長度 $n > 10^3$ 時 |









