什麼是最長遞增子串行呢?
問題描述如下:
設l=是n
個不同的實數的序列,
l的遞增子串行是這樣乙個子串行
lin=
,其中k1且
ak1。求最大的m值。
對於這個問題有以下幾種解決思路:
1、把a1,a2,...,an
排序,假設得到
a'1,a'2,...,a'n
,然後求a的
a'的最長公共子串,這樣總的時間複雜度為
o(nlg(n))+o(n^2)=o(n^2); 2
、動態規劃的思路:
另設一輔助陣列
b,定義
b[n]
表示以a[n]
結尾的最長遞增子串行的長度,則狀態轉移方程如下:
b[k]=max(max(b[j]|a[j]
這個狀態轉移方程解釋如下:在
a[k]
前面找到滿足
a[j]的最大
b[j],
然後把a[k]
接在它的後面,可得到
a[k]
的最長遞增子串行的長度,或者
a[k]
前面沒有比它小的
a[j]
,那麼這時
a[k]
自成一串行,長度為
1.最後整個數列的最長遞增子串行即為
max(b[k] | 0<=k<=n-1);
實現**如下:
#include
using namespace std;
int main()
for(max=i=0;i求出整個數列的最長遞增子串行的長度
if(b[i]>max)
max=b[i];
cout<
}return 0;
}顯然,這種方法的時間複雜度仍為
o(n^2); 3
、對第二種思路的改進:
第二種思路在狀態轉移時的複雜度為
o(n),
即在找a[k]
前面滿足
a[j]的最大
b[j]
時採用的是順序查詢的方法,複雜度為
o(n).
設想如果能把順序查詢改為折半查詢,則狀態轉移時的複雜度為
o(lg(n)),
這個問題的總的複雜度就可以降到
nlg(n).
另定義一陣列
c,c中元素滿足
c[b[k]]=a[k],
解釋一下,即當遞增子串行的長度為
b[k]
時子串行的末尾元素為
c[b[k]]=a[k].
先給出這種思路的**,然後再對其做出解釋。
#include
using namespace std;
int find(int *a,int len,int n)//
若返回值為x,則
a[x]>=n>a[x-1]
return left;
}void fill(int *a,int n)
int main()
for(max=i=0;i
if(b[i]>max)
max=b[i];
cout<
}return 0;
}對於這段程式,我們可以用演算法導論上的
loop invariants
來幫助理解.
loop invariant: 1
、每次迴圈結束後
c都是單調遞增的。
(這一性質決定了可以用二分查詢)
2、每次迴圈後,
c[i]
總是儲存長度為
i的遞增子串行的最末的元素,若長度為
i的遞增子串行有多個,剛儲存末尾元素最小的那個
.(這一性質決定是第
3條性質成立的前提)
3、每次迴圈完後,
b[i]
總是儲存以
a[i]
結尾的最長遞增子串行。
initialization:
1、進入迴圈之前,
c[0]=-1,c[1]=a[0],c
的其他元素均為
1000,c
是單調遞增的;
2、進入迴圈之前,
c[1]=a[0],
儲存了長度為
1時的遞增序列的最末的元素,且此時長度為
1的遞增了序列只有乙個,
c[1]
也是最小的;
3、進入迴圈之前,
b[0]=1
,此時以
a[0]
結尾的最長遞增子串行的長度為1.
maintenance:
1、若在第
n次迴圈之前
c是單調遞增的,則第
n次迴圈時,
c的值只在第
6行發生變化,而由
c進入迴圈前單調遞增及
find
函式的性質可知(見
find
的注釋),
此時c[j+1]>c[j]>=a[i]>c[j-1],
所以把c[j]
的值更新為
a[i]
後,c[j+1]>c[j]>c[j-1]
的性質仍然成立,即
c仍然是單調遞增的;
2、迴圈中,
c的值只在第
6行發生變化,由
c[j]>=a[i]
可知,c[j]
更新為a[i]
後,c[j]
的值只會變小不會變大,因為進入迴圈前
c[j]
的值是最小的,則迴圈中把
c[j]
更新為更小的
a[i]
,當然此時
c[j]
的值仍是最小的;
3、迴圈中,
b[i]
的值在第
7行發生了變化,因為有
loop invariant
的性質2
,find
函式返回值為j有:
c[j-1]這說明
c[j-1]
是小於a[i]
的,且以
c[j-1]
結尾的遞增子串行有最大的長度,即為
j-1,
把a[i]
接在c[j-1]
後可得到以
a[i]
結尾的最長遞增子串行,長度為
(j-1)+1=j;
termination:
迴圈完後,
i=n-1,b[0],b[1],...,b[n-1]
的值均已求出,即以
a[0],a[1],...,a[n-1]
結尾的最長遞增子串行的長度均已求出,再通過第
8行的迴圈,即求出了整個陣列的最長遞增子串行。仔細分析上面的**可以發現,每次迴圈結束後,假設已經求出
c[1],c[2],c[3],...,c[len]
的值,則此時最長遞增子串行的長度為
len,
因此可以把上面的**更加簡化,即可以不需要陣列
b來輔助儲存,第
8行的迴圈也可以省略。
#include
using namespace std;
int find(int *a,int len,int n)//
修改後的二分查詢,若返回值為x,則
a[x]>=n
return left;
}int main()
cout<
}return 0;
}
最長遞增子串行求解
演算法難,難於上青天 搞懂乙個演算法不容易,還是寫篇部落格為以後複習做好準備 include define n 1000 using namespace std int getlongcommonsub int a 6 int dp 6 int n return dp i 1 int main in...
最長遞增子串行問題的求解
最長遞增子串行問題的求解 最長遞增子串行問題是乙個很基本 較常見的小問題,但這個問題的求解方法卻並不那麼顯而易見,需要較深入的思考和較好的演算法素養才能得出良好的演算法。一,最長遞增子串行問題的描述 設l 是n個不同的實數的序列,l的遞增子串行是這樣乙個子串行lin 其中k1是對序列l 按遞增排好序...
動態規劃(三)最長遞增子串行zZ
最長遞增子串行是動態規劃經典問題之一,與最大連續子串行的思路一致,問題描述是在乙個已知序列中,取若干元素 不必連續 按原先的先後順序組成乙個新序列使得新序列是遞增的,最長子序列問題就是求給定序列的所有遞增子串行中最長的那個子串行的長度。分析 1 用陣列a n 儲存序列a0 an 1。2 設定陣列dp...