題目鏈接: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]=9
,2
的到來會更新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; } };