寫在前面:說來也巧,昨天剛在洛谷上做線性dp,在學習LIS和LCS的優化問題,今天每日一題就出了這道LIS。
思路:
首先說樸素算法的思路,
首先我們要定義一個集合,我們不妨以dp【i】表示數組中以第i位結尾的最長上升子序列的長度,然後從左往右依次迭代就可以了。
然後我們需要確定狀態,dp【i】表示數組中以第i位結尾的最長上升序列長度,那麼它和之前dp【0~i】有什麼關係呢?如果nums【i】>nums【j】其中j=【0…i】那麼dp【i】=dp【j】+1.至此,我們就確定了狀態轉移方程dp【i】=max(dp【j】+1,dp【i】)其中j是0到i且所有滿足條件的下標。
代碼如下:
/*class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.empty()) return 0;
if(nums.size()==1) return 1;
vector<int>dp(nums.size(),0);
for(int i=0;i<nums.size();i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(nums[i]>nums[j])
dp[i]=max(dp[j]+1,dp[i]);
}
return *max_element(dp.begin(), dp.end());
}
};
上面這個算法很明顯,時間複雜度是O(n^2)的。
然後我們考慮如何優化呢?
首先我們從上面那個算法可以知道,我們每次更新值是因爲找到了前面可以更新的點,最後枚舉找出最大的。
那麼我們首先換一個思路,我們不妨定義dp【i】表示長度位i的上升子序列的最小結尾的數。
打個比方,比如第一個數爲9,dp數組裏面就放了一個【9】,他代表長度爲1的子序列的最小結尾值爲9,然後第二個數爲2,2<9。所以2開始後面可能會有一個上升的序列,9後面也可能有一個上升的序列(有沒有感覺像列車調度這道題)那麼dp數組就更新爲【2】表示當前長度爲1的上升序列最小結尾爲2.然後第三個數爲4,4>2,這個是重點,表示4大於當前長度爲1的上升序列的最小結尾,那麼把他接到2後面長度就變成了2,所以dp數組裏面就是【2,4】表示現在長度爲1的上升序列最小結尾爲2,長度爲2的上升序列的最小結尾爲4。如果這個數組裏面就是9,2,4這三個數,那麼此時程序已經運行完了,就直接輸出dp數組的大小(就是2)就ojbk了。(是不是感覺越發的像列車調度這道題了。)
好,這個思路講完了,我們總結一下,插入新的數的時候,(比如還是上面那個例子,我們緊接着要插入一個3,3比2大比4小所以要替換4的位置,如果是插入1,1比2小所以替換2的位置)。也就是說我們要在dp數組裏找最接近插入的數的位置,這麼經典的查找算法我們自然而然就想到了二分(因爲dp數組裏的數是嚴格單調遞增的)。至此,優化完成!
代碼如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> minnums;
for(int v : nums)
{
if(!minnums.size() || v > minnums.back())
minnums.push_back(v);
else
*lower_bound(minnums.begin(), minnums.end(), v) = v;
}
return minnums.size();
}
};