之前看到這種問題的時候,以爲這種問題仍是挺無聊的,雖然作出來後本身會聽激動的,但不少時候不知道這有什麼用。今天看到《算法導論》那裏提到這個用來搞兩條 DNA 的比較,頓時就以爲這種問題仍是挺有意思。c++
問題是這樣的,有一個 DNA s1 是 "ACCGG" ,另外一條 DNA s2 是 "GTCGT",求最大的公共子序列,能夠不連續的。算法
。。。ui
先思考一下。spa
首先,想到的解決方法通常都是暴力破解的。先求出全部 s1 的全部子序列,再求出 s2 的全部子序列。而後經過對比最後得到最長的公共子序列。code
固然這時間複雜度也是挺感人的,假如 s1 的長度是 m,s2 的長度是 n。用相似 2 進制能夠表示全部數的思想,可知求出 s1 的全部子序列的複雜讀是 O(2^m),可求出 s2 的全部子序列的複雜度是 O(2^n),而遍歷對比的複雜度是 O(2^m*n)cdn
...blog
這複雜度太難以接受了。。。遞歸
若是是樹形遞歸的思想呢?字符串
樹形遞歸的思想是這樣的,假如 s1[0:i-1] 與 s2[0:j-1] 之間有一條最長的公共子序列 seq 若是 s1[i] == s2[j] ,那麼最長公共子序列就是 seq + s1[i] 若是不相等,最長公共子序列就是 LCS(s1[0:i],s2[0:j-1]) 或者是 LSC(s1[0:i-1], s2[0:j]) 中取最長。 那麼是何時結束呢?應該是 i < 0 或者 j < 0 吧。string
因此若是用遞歸很容易寫出這樣的代碼
string lcs(string s1, string s2, int i, int j) {
if (i < 0 || j < 0)
return "";
else if (s1[i] == s2[j])
return lcs(s1, s2, i - 1, j - 1) + s1[i];
else {
string p1 = lcs(s1, s2, i - 1, j);
string p2 = lcs(s1, s2, i, j - 1);
if (p1.length() > p2.length()) {
return p1;
}
return p2;
}
}
string lcs(string s1, string s2) {
return lcs(s1, s2, s1.length() - 1, s2.length() - 1);
}
複製代碼
這固然是一如既往的有重複計算的問題的。若是用動態規劃的思想。大概會是這樣。
就比較容易寫出這樣的代碼。
int lcs2(string s1, string s2) {
int m = s1.length();
int n = s2.length();
vector<vector<int>> c(m + 1, vector<int>(n + 1));
for (int i = 0; i < m + 1; i++) {
for (int j = 0; j < n + 1; j++) {
if (i == 0 || j == 0)
c[i][j] = 0;
//算法導論字符串是從1開始的,因此這裏要減一
//也方便後續的操做
else if (s1[i - 1] == s2[j - 1])
c[i][j] = c[i - 1][j - 1] + 1 ;
else
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
return c[m][n];
}
複製代碼
可是這樣的操做只能說求出了最長公共子序列的長度。並無將序列求出來了。 求出序列簡單,按照原來的思路再遍歷一次就能夠了
string res = "";
int i = m;
int j = n;
while (i > 0 && j > 0) {
if (s1[i - 1] == s2[j - 1]) {
res = s1[i - 1] + res;
j--;
i--;
} else if(c[i-1][j] > s2[i][j-1]){
i--;
}else {
j--;
}
}
return res;
複製代碼
若是 s1[i-1] == s2[j-1] 就說明最長自序列的一個元素,就 i++,j++ ,向右下角走。 不然對比右邊和下邊哪邊打就走哪邊。
string res = "";
int i = 1;
int j = 1;
while (i < m && j < n) {
if (s1[i - 1] == s2[j - 1]) {
res = res + s1[i - 1];
j++;
i++;
} else if (c[i + 1][j] >= c[i][j + 1]) {
i++;
} else {
j++;
}
}
return res;
複製代碼