【經典動態規劃問題】最長上升子序列 LIS
目錄
最長上升子序列:
一個數的序列bi,當b1 < b2 < … < bS的時候,我們稱這個序列是上升的。對於給定的一個序列(a1, a2, …, aN),我們可以得到一些上升的子序列(ai1, ai2, …, aiK),這裡1 <= i1 < i2 < … < iK <= N。
O(N^2)動態規劃:
狀態:dp[i],表示以A[i]為結尾陣列的的最長上升子序列長度
初始化:A[i]的最長上升子序列最小為1,也就是左邊的數都大於等於A[i],最長上升子序列只有A[i]元素本身,dp[i] = 1;
dp[i]表示以A[i]的最長子序列長度,我們現在找倒數第二個元素。
遍歷A[0,...i-1],設其中小於A[i]的元素為A[k,j,z...],倒數第二個元素必定在A[k,j,z]中,dp[i]有幾種可能:
dp[i] = dp[k]+1;
dp[i ]= dp[j]+1;
dp[i] = dp[z]+1;
取這幾種可能結果中的最大值,即為dp[i]的值。
最後返回的則是dp陣列中的元素最大值。
class LongestIncreasingSubsequence { public: int getLIS(vector<int> A, int n) { // write code here //dp[i]為A[0...i]中的最長上升子序列的長度 int dp[501]= {0}; for(int i = 0;i<n;i++){ dp[i] = 1;//初始化最長上升子序列的長度為1,即自身 } int ans = 1;//記錄最大值 for(int i = 1;i<n;i++){ for(int j = 0;j<i;j++){ if(A[j]<A[i]) dp[i] = max(dp[i],dp[j]+1); } //重要的是最大值 ans = max(dp[i],ans); } return ans; } };
這道題還有複雜度更低的做法, 可在面試中作為演算法的優化,同時能夠返回最長上升子序列的元素值。
O(N*logN):貪心+二分
從左到右遍歷陣列,用dp陣列維護當前的最長上升子序列,size變數維護最長上升子序列的長度。
如A={ 2,1,3,4,8,6}
A[0]等於2,dp = {2},size=1;
A [1]等於1,小於dp的末尾元素2,因此讓1把2替換掉,dp = {1},size = 1;長度為1的最長子序列,結尾元素為1顯然比結尾元素為2的序列更容易加"上升元素"
A [2]等於3,大於dp的末尾元素1,因此dp中加入2,dp = {1,2},size = 2;
A [3]等於4,大於dp的末尾元素2,因此dp中加入4,dp = {1,2,4},size = 3;
A [4]等於8,大於dp的末尾元素4,因此dp中加入8,dp = {1,2,4,8},size = 4;
A [5]等於6,小於dp的末尾元素8,大於倒數第二個元素4,因此將6替換8,dp = {1,2,4,6},size = 4,因為長度為4的最長上升子序列,結尾是6會比結尾是8的序列要更有利於新增新元素。
得到最長上升子序列為{1,2,4,6},長度為4;
這裡使用了貪心策略:一個上升子序列,最後一個元素越小,越有利於新增後續比它大的元素。
dp為有序陣列,因此若A[i]小於dp末尾元素,需要找到dp中第一個大於等於A[i]的元素並讓A[i]替換掉它。(較小的值越往前面約好)
注意這裡的二分查詢是求下界的,即>=所查詢物件的第一個位置。
class LongestIncreasingSubsequence {
public:
int binarysearch(int A[], int n,int item){
int start = 0;
int end = n-1;
while(start<=end){
int mid = (start+end)/2;
if(A[mid]<item)
start = mid+1;
else
end = mid-1;
}
return start;
}
int getLIS(vector<int> A, int n) {
// write code here
//dp[i]為A[0...i]中的最長上升子序列的長度
int dp[501]= {0};
int size = 0;
dp[size++] = A[0];
for(int i = 1;i<n;i++){
if(A[i]> dp[size-1])//當前元素大於dp陣列中的最後一個元素
dp[size++] = A[i];
else{
int firstBig = binarysearch(dp,size,A[i]);//用二分法找到第一個大於等於A[i]的值,進行替換
dp[firstBig] = A[i];
}
}
return size;
}
};
此外在求陣列中第一個大於等於A[i]的值時,可以使用lower_bound()
lower_bound(int* first,int* last,val);函式在(first,last](左開右閉)區間中進行二分查詢。因此陣列必須是排好序的陣列。
函式返回從first開始的第一個大於或等於val的元素的地址。如果所有元素都小於val,則返回last的地址。
第一個大於等於A[i]的陣列下標為:pos = lower_bound(dp,dp+size,A[i])-dp
因此 int firstBig = dp[pos];