POJ #1458 Common Subsequence
Description
問題的描述以及樣例在這裏:1458 Common Subsequence
Sample
INPUT:
abcfbc abfcab
programming contest
abcd mnp
OUTPUT: 4 2 0
思路
首先,我們可以想到用暴力法解決,一個序列的子集有 2n 個,兩個子集相互比較找出相同且元素最多的子集即可。但是算法的運行時間是指數階,肯定會TLE 的。
可以換個角度思考,從兩個序列的末尾開始比較,總結出求LCS的遞歸式。設序列 X、Y 分別有它們的前綴串,Xm = <x1
1.如果 xm = yn ,則 zk = xm = ym ,且 zk-1 是xm-1、yn-1 的一個 LCS
2.如果 xm != yn,那麽 zk != xm 意味著 zk 是 xm-1 、yn 的一個CLS
3.如果 xm != yn,那麽 zk != yn 意味著 zk 是 xm 、yn-1 的一個CLS
舉個例子, <a, b, c> 與 <a, b>,比較兩序列末尾元素 c、b,發現不匹配,那麽就認為它們的 LCS是 <a, b> 與 <a, b> 的LCS,或者是 <a, b, c> 與 <a> 的 LCS,至於是哪個取決於後兩者 LCS 哪個大。如果末尾元素匹配了,說明找到了 LCS中的一個元素,則讓LCS+1 ,繼續尋找 <a, b> 、<a> 的LCS...
如果我們用 c[i][j] 代替zk 表示LCS的長度,i 表示序列X的長度,j 表示序列Y的長度,有如下遞歸式:
寫出遞歸的程序,但是發現其實很多子問題是重復求解的,比如 i = 6, j = 5,C[5, 5] 就被重復求解了。像這種的樹型遞歸,我們可以采用記憶化搜索的策略解決,通俗的將就是再寫一個備忘錄,把求過的解都記錄在裏面,下一次問題重復時直接取出其中的解即可。
這種算法的時間復雜度和互相獨立的子問題個數有關,假設輸入的規模是 m、n,那麽相互獨立的子問題有多少個呢?
m·n 個,可以這麽想: c[m][n] 是一個,c[m-1][n] 又是一個,c[m][n-1] 又是一個...
所以,記憶化搜索算法的時間復雜度是O(mn)
#include<iostream> #include<string> #include<algorithm> #include<cstring> using namespace std; const int MAXSIZE = 1000; int c[MAXSIZE][MAXSIZE]; int getLCS (const string& x, const string& y,int i, int j) { //傳入的是長度 if (i == 0 || j == 0) { //長度為 0 時,表示序列為空,此時LCS = 0 return 0; } if (c[i][j] >= 0) { return c[i][j]; } if (x[i-1] == y[j-1]) { //用於比較的是下標,下標= 長度 - 1 return c[i][j] = getLCS(x, y, i-1, j-1) + 1; } else { return c[i][j] = std::max(getLCS(x, y, i-1, j), getLCS(x, y, i, j-1)); } } int main(void) { string x,y; while (cin >> x >> y ) { memset (c, -1, sizeof(c)); //LCS可能是0,所以應初始化為-1 int ans = getLCS(x, y, x.size(), y.size()); cout << ans << endl; } return 0; }
其實上面的算法就是 DP 的一種,因為它滿足 DP 的三個特點:
1.將原問題分解成幾個子問題
2.所有問題只需要解決一次
3.存儲子問題的解
但是這種自頂向下的辦法,存儲的空間不密集,會浪費很多空間。
POJ #1458 Common Subsequence