摘要:若且,則是和的最長公共子序列若且,則是和的最長公共子序列。遞歸結構容易看到最長公共子序列問題具有子問題重疊性質。例如,在計算和的最長公共子序列時,可能要計算出和及和的最長公共子序列。
雖是讀書筆記,但是如轉載請注明出處 http://segmentfault.com/blog/exploring/
.. 拒絕伸手復制黨
本章講解:
1. LCS(最長公共子序列)O(n^2)的時間復雜度,O(n^2)的空間復雜度;
2. 與之類似但不同的最長公共子串方法。
最長公共子串用動態(tài)規(guī)劃可實現(xiàn)O(n^2)的時間復雜度,O(n^2)的空間復雜度;還可以進一步優(yōu)化,用后綴數(shù)組的方法優(yōu)化成線性時間O(nlogn);空間也可以用其他方法優(yōu)化成線性。
3.LIS(最長遞增序列)DP方法可實現(xiàn)O(n^2)的時間復雜度,進一步優(yōu)化最佳可達到O(nlogn)
一些定義:
字符串 X, Y 長度 分別m,n
子串:字符串S的子串r[i,...,j],i<=j,表示r串從i到j這一段,也就是順次排列r[i],r[i+1],...,r[j]形成的字符串
前綴:Xi =﹤x1,?,xi﹥ 即 X 序列的前 i 個字符 (1≤i≤m);
Yj=﹤y1,?,yj﹥即 Y 序列的前 j 個字符 (1≤j≤n);
假定 Z=﹤z1,?,zk﹥∈LCS(X , Y)
有關后綴數(shù)組的定義
LCS 問題描述定義:
一個數(shù)列 S,如果分別是兩個或多個已知數(shù)列的子序列,且是所有符合此條件序列中最長的,則 S 稱為已知序列的最長公共子序列。
例如:輸入兩個字符串 BDCABA 和 ABCBDAB,字符串 BCBA 和 BDAB 都是是它們的最長公共子序列,則輸出它們的長度 4,并打印任意一個子序列. (Note: 不要求連續(xù))
判斷字符串相似度的方法之一 - LCS 最長公共子序列越長,越相似。
復雜度對于一般性的 LCS 問題(即任意數(shù)量的序列)是屬于 NP-hard。但當序列的數(shù)量確定時,問題可以使用動態(tài)規(guī)劃(Dynamic Programming)在多項式時間解決。可達時間復雜度:O(m*n)
July 10分鐘講LCS視頻,
最優(yōu)子結構性質:
設序列 X=
若 xm = yn,則 zk = xm = yn 則 Zk-1 是 Xm-1 和 Yn-1 的最長公共子序列;
若 xm ≠ yn, 要么Z是 Xm-1 和 Y 的最長公共子序列,要么 Z 是X和 Yn-1 的最長公共子序列。
2.1 若 xm ≠ yn 且 zk≠xm ,則 Z是 Xm-1 和 Y 的最長公共子序列;
2.2 若 xm ≠ yn 且 zk ≠yn ,則 Z 是X和 Yn-1 的最長公共子序列。
綜合一下2 就是求二者的大者
遞歸結構:
遞歸結構容易看到最長公共子序列問題具有子問題重疊性質。例如,在計算 X 和 Y 的最長公共子序列時,可能要計算出 X 和 Yn-1 及 Xm-1 和 Y 的最長公共子序列。而這兩個子問題都包含一個公共子問題,即計算 Xm-1 和 Yn-1 的最長公共子序列。
遞歸結構容易看到最長公共子序列問題具有子問題重疊性質。例如,在計算 X 和 Y 的最長公共子序列時,可能要計算出 X 和 Yn-1 及 Xm-1 和 Y 的最長公共子序列。而這兩個子問題都包含一個公共子問題,即計算Xm-1 和 Yn-1 的最長公共子序列。
計算最優(yōu)值:
子問題空間中,總共只有O(m*n) 個不同的子問題,因此,用動態(tài)規(guī)劃算法自底向上地計算最優(yōu)值能提高算法的效率。
長度表C 和 方向變量B:
java實現(xiàn):
/* 動態(tài)規(guī)劃 * 求最長公共子序列 * @ author by gsm * @ 2015.4.1 */ import java.util.Random; public class LCS { public static int[][] lengthofLCS(char[] X, char[] Y){ /* 構造二維數(shù)組c[][]記錄X[i]和Y[j]的LCS長度 (i,j)是前綴 * c[i][j]=0; 當 i = j = 0; * c[i][j]=c[i-1][j-1]+1; 當 i = j > 0; Xi == Y[i] * c[i][j]=max(c[i-1][j],c[i][j+1]); 當 i = j > 0; Xi != Y[i] * 需要計算 m*n 個子問題的長度 即 任意c[i][j]的長度 * -- 填表過程 */ int[][]c = new int[X.length+1][Y.length+1]; // 動態(tài)規(guī)劃計算所有子問題 for(int i=1;i<=X.length;i++){ for (int j=1;j<=Y.length;j++){ if(X[i-1]==Y[j-1]){ c[i][j] = c[i-1][j-1]+1; } else if(c[i-1][j] >= c[i][j-1]){ c[i][j] = c[i-1][j]; } else{ c[i][j] = c[i][j-1]; } } } // 打印C數(shù)組 for(int i=0;i<=X.length;i++){ for (int j=0;j<=Y.length;j++){ System.out.print(c[i][j]+" "); } System.out.println(); } return c; } // 輸出LCS序列 public static void print(int[][] arr, char[] X, char[] Y, int i, int j) { if(i == 0 || j == 0) return; if(X[i-1] == Y[j-1]) { System.out.print("element " + X[i-1] + " "); // 尋找的 print(arr, X, Y, i-1, j-1); }else if(arr[i-1][j] >= arr[i][j-1]) { print(arr, X, Y, i-1, j); }else{ print(arr, X, Y, i, j-1); } } public static void main(String[] args) { // TODO Auto-generated method stub char[] x ={"A","B","C","B","D","A","B"}; char[] y ={"B","D","C","A","B","A"}; int[][] c = lengthofLCS(x,y); print(c, x, y, x.length, y.length); } }最長公共子串 一個問題
定義 2 個字符串 query 和 text, 如果 query 里最大連續(xù)字符子串在 text 中存在,則返回子串長度. 例如: query="acbac",text="acaccbabb", 則最大連續(xù)子串為 "cba", 則返回長度 3.
方法 時間復雜度:O(m*n)的DP這個 LCS 跟前面說的最長公共子序列的 LCS 不一樣,不過也算是 LCS 的一個變體,在 LCS 中,子序列是不必要求連續(xù)的,而子串則是 “連續(xù)” 的
我們還是像之前一樣 “從后向前” 考慮是否能分解這個問題,類似最長公共子序列的分析,這里,我們使用c[i,j] 表示 以 Xi 和 Yj 結尾的最長公共子串的長度,因為要求子串連續(xù),所以對于 Xi 與 Yj 來講,它們要么與之前的公共子串構成新的公共子串;要么就是不構成公共子串。故狀態(tài)轉移方程
X[i-1] == Y[j-1],c[i,j] = c[i-1,j-1] + 1; X[i-1] != Y[j-1],c[i,j] = 0;
對于初始化,i==0 或者 j==0,c[i,j] = 0
代碼:
public class LCString { public static int lengthofLCString(String X, String Y){ /* 構造二維數(shù)組c[][]記錄X[i]和Y[j]的LCS長度 (i,j)是前綴 * c[i][j]=0; 當 i = j = 0; * c[i][j]=c[i-1][j-1]+1; 當 i = j > 0; Xi == Y[i] * c[i][j]=0; 當 i = j > 0; Xi != Y[i] * 需要計算 m*n 個子問題的長度 即 任意c[i][j]的長度 * -- 填表過程 */ int[][]c = new int[X.length()+1][Y.length()+1]; int maxlen = 0; int maxindex = 0; for(int i =1;i<=X.length();i++){ for(int j=1;j<=Y.length();j++){ if(X.charAt(i-1) == Y.charAt(j-1)){ c[i][j] = c[i-1][j-1]+1; if(c[i][j] > maxlen) { maxlen = c[i][j]; maxindex = i + 1 - maxlen; } } } } return maxlen; } public static void main(String[] args) { String X = "acbac"; String Y = "acaccbabb"; System.out.println(lengthofLCString(X,Y)); } }時間復雜度O(nlogn)的后綴數(shù)組的方法
有關后綴數(shù)組以及求最長重復子串
前面提過后綴數(shù)組的基本定義,與子串有關,可以嘗試這方面思路。由于后綴數(shù)組最典型的是尋找一個字符串的重復子串,所以,對于兩個字符串,我們可以將其連接到一起,如果某一個子串 s 是它們的公共子串,則 s 一定會在連接后字符串后綴數(shù)組中出現(xiàn)兩次,這樣就將最長公共子串轉成最長重復子串的問題了,這里的后綴數(shù)組我們使用基本的實現(xiàn)方式。
值得一提的是,在找到兩個重復子串時,不一定就是 X 與 Y 的公共子串,也可能是 X 或 Y 的自身重復子串,故在連接時候我們在 X 后面插入一個特殊字符‘#’,即連接后為 X#Y。這樣一來,只有找到的兩個重復子串恰好有一個在 #的前面,這兩個重復子串才是 X 與 Y 的公共子串
各方案復雜度對比設字符串 X 的長度為 m,Y 的長度為 n,最長公共子串長度為 l。 對于基本算法(brute force),X 的子串(m 個)和 Y 的子串(n 個)一一對比,最壞情況下,復雜度為 O(m*n*l),空間復雜度為 O(1)。 對于 DP 算法,由于自底向上構建最優(yōu)子問題的解,時間復雜度為 O(m*n);空間復雜度為 O(m*n),當然這里是可以使用滾動數(shù)組來優(yōu)化空間的,滾動數(shù)組在動態(tài)規(guī)劃基礎回顧中多次提到。 對于后綴數(shù)組方法,連接到一起并初始化后綴數(shù)組的時間復雜度為 O(m+n),對后綴數(shù)組的字符串排序,由于后綴數(shù)組有 m+n 個后綴子串,子串間比較,故復雜度為 O((m+n)*l*lg(m+n)),求得最長子串遍歷后綴數(shù)組,復雜度為 O(m+n),所以總的時間復雜度為 O((m+n)*l*lg(m+n)),空間復雜度為 O(m+n)。 總的來說使用后綴數(shù)組對數(shù)據(jù)做一些 “預處理”,在效率上還是能提升不少的。LIS 最長遞增子序列
問題描述:找出一個n個數(shù)的序列的最長單調遞增子序列: 比如A = {5,6,7,1,2,8} 的LIS是5,6,7,8
1. O(n^2)的復雜度:1.1 最優(yōu)子結構:
LIS[i] 是以arr[i]為末尾的LIS序列的長度。則:
LIS[i] = {1+Max(LIS(j))}; j;
LIS[i] = 1, j, 但是不存在arr[j]
所以問題轉化為計算Max(LIS(j)) 0
1.2 重疊的子問題:
以arr[i] (1<= i <= n)每個元素結尾的LIS序列的值是 重疊的子問題。
所以填表時候就是建立一個數(shù)組DP[i], 記錄以arr[i]為序列末尾的LIS長度。
1.3 DP[i]怎么計算?
遍歷所有j的元素,檢查是否DP[j]+1>DP[i] && arr[j]
int maxLength = 1, bestEnd = 0; DP[0] = 1; prev[0] = -1; for (int i = 1; i < N; i++) { DP[i] = 1; prev[i] = -1; for (int j = i - 1; j >= 0; j--) if (DP[j] + 1 > DP[i] && array[j] < array[i]) { DP[i] = DP[j] + 1; prev[i] = j; } if (DP[i] > maxLength) { bestEnd = i; maxLength = DP[i]; }2. O(nlog)的復雜度
基本思想:
首先通過一個數(shù)組MaxV[nMaxLength]來緩存遞增子序列LIS的末尾元素最小值;通過nMaxLength 記錄到當前遍歷為止的最長子序列的長度;
然后我們從第2元素開始,遍歷給定的數(shù)組arr,
1. arr[i] > MaxV[nMaxLength], 將arr[i]插入到MaxV[++nMaxLength]的末尾 -- 意味著我們找到了一個新的最大LIS
2. arr[i] <= MaxV[nMaxLength], 找到MaxV[]中剛剛大于arr[i]的元素,arr[j].arr[i]替換arr[j]
因為MaxV是一個有序數(shù)組,查找過程可以使用log(N)的折半查找。
這樣運行時間: n個整數(shù)和每個都需要折半查找 -- n*logn = O(nlogn)
if > 說明j能夠放在最長子序列的末尾形成一個新的最長子序列.
if< 說明j需要替換前面一個剛剛大與array[j]的元素
最后,輸出LIS時候,我們會用一個LIS[]數(shù)組,這邊LIS[i]記錄的是以元素arr[i]為結尾的最長序列的長度
初始化準備工作:
MaxV[1]首先會被設置成序列第一個元素 即 MaxV[1] = arr[0],在遍歷數(shù)組的過程中會不斷的更新。
nMaxLength = 1
舉個栗子:
arr = {2 1 5 3 6 4 8 9 7}
首先i=1, 遍歷到1, 1 通過跟MaxV[nMaxLength]比較: 1
發(fā)現(xiàn)1更有潛力(更小的有潛力,更小的替換之)
1 更有潛力, 那么1就替換MaxV[nMaxLength] 即 MaxV[nMaxLength] =1 ;
這個時候 MaxV={1}, nMaxlength = 1,LIS[1] = 1;
然后 i =2, 遍歷到5, 5通過跟MaxV[nMaxLength]比較, 5>MaxV[nMaxLength],
發(fā)現(xiàn)5 更大; 鏈接到目前得到的LIS尾部;
這個時候 MaxV={1,5}, nMaxlength++ = 2, MaxV[nMaxLength]=5, LIS[i] = 1+1 = 2;
然后 i =3,遍歷到3, 3 通過跟MaxV[nMaxLength]比較, 3
發(fā)現(xiàn)3更有 潛力,然后從 nMaxLength往前比較,找到第一個剛剛比3大元素替換之。(稍后解釋什么叫剛剛大)
這個時候 MaxV={1,3}, nMaxlength = 2; 3只是替換, LIS[i]不變 = LIS[3]= 2;
然后 i =4,遍歷到6, 6 通過跟 MaxV[nMaxLength]比較, 6>MaxV[nMaxLength],
發(fā)現(xiàn)6更大; 6就應該鏈接到目前得到的LIS尾部;
這個時候,MaxV={1,3,6} ,nMaxlength = 3,MaxV[nMaxLength+1]=6 , LIS[4] = 3
然后i =5,遍歷到4, 4 通過跟MaxV[nMaxLength] = 6比較, 4
發(fā)現(xiàn)4更有潛力,然后從nMaxLength往前比較,找到剛剛比4大元素 也就是 6替換之。
這個時候 MaxV={1,3,4}, nMaxlength = 3,4只是替換, LIS[i]不變 = LIS[5]= 3;
然后i=6, 遍歷到8, 8通過跟MaxV[nMaxLength]比較, 8>MaxV[nMaxLength],
發(fā)現(xiàn)8更大; 8就應該鏈接到目前得到的LIS尾部;
這個時候 MaxV={1,3,4,8}, nMaxlength = 4, Maxv[nMaxlength]=8 LIS[6]=4,
然后i=7, 遍歷到9, 9通過跟MaxV[nMaxLength]比較, 9>MaxV[nMaxLength],
發(fā)現(xiàn)9更大; 9就應該鏈接到目前得到的LIS尾部;
這個時候 MaxV={1,3,4,8,9}, nMaxlength = 5, Maxv[nmaxlength]=9, LIS[7] = 5;
然后i=8, 遍歷到7, 7 通過跟MaxV[nMaxLength] = 9比較, 7
發(fā)現(xiàn)7更有潛力,然后從nMaxLength往前比較,找到第一個比7大元素 也就是 8替換之。
這個時候 MaxV={1,3,4,7,9}, nMaxLength = 5, Maxv[nMaxlength]=9
LIS[8] = LIS[替換掉的index] = 4;
-- | 2 | 1 | 5 | 3 | 6 | 4 | 8 | 9 | 7 |
---|---|---|---|---|---|---|---|---|---|
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
LIS | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 5 | 4 |
MaxV | 2 | 1 | 1,5 | 1,3 | 1,3,6 | 1,3,4 | 1,3,4,8 | 1,3,4,8,9 | 1,3,4,7 |
java實現(xiàn):
import java.util.*; public class LIS { public static int lengthofLCS(int[] arr){ // 輔助變量 int[] MaxV = new int [arr.length+1]; // 記錄遞增子序列 LIS 的末尾元素最小值 int nMaxLength = 1; // 當前LIS的長度 int [] LIS = new int[arr.length+1]; //LIS[i]記錄的是以第i個元素為結尾的最長序列的長度 // 初始化 MaxV[0] = -100; MaxV[nMaxLength] = arr[0]; LIS[0] = 0;LIS[1] = 1; for(int i=1;iMaxV[nMaxLength]){ MaxV[++nMaxLength] = arr[i]; LIS[i] = LIS[i-1]+1; } else{ // 新元素 更小,更有“潛力”,替換大的元素 int index = binarySearch(MaxV,arr[i],0,nMaxLength); //* LIS[i] =index; MaxV[index] = arr[i]; } } Arrays.sort(LIS); return LIS[LIS.length-1]; } // 在MaxV數(shù)組中查找一個元素剛剛大于arr[i] // 返回這個元素的index public static int binarySearch(int []arr, int n, int start, int end){ while(start n) { end = mid -1; } else return mid; } return end; } public static void main(String[] args) { int[] arr = {2,1,5,3,6,4,8,9,7}; System.out.println(lengthofLCS(arr)); } }
* : MaxV里面的數(shù)組下標代表了長度為index的最長子序列末尾元素,反過來就是末尾元素在MaxV里對應的下標就是他子序列的長度
可以轉化為LCS的問題給一個字符串,求這個字符串最少增加幾個字符能變成回文
要在一條河的南北兩邊的各個城市之間造若干座橋.橋兩邊的城市分別是 a(1)...a(n) 和 b(1)...b(n). 且南邊 a(1)...a(n) 是亂序的,北邊同理,但是要求 a(i) 只可以和 b(i) 之間造橋, 同時兩座橋之間不能交叉. 希望可以得到一個盡量多座橋的方案.
以我和藍盆友的討論做結:
- 通常DP是一個不算最好,但是比最直接的算法好很多的方法。 DP一般是O(n^2);但是如果想進一步優(yōu)化 O(nlogn)就要考慮其他的了
- 對,要想更好的方法就是要挖掘題目本身更加隱匿的性質了
想更一進步的支持我,請掃描下方的二維碼,你懂的~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64299.html
摘要:但不是和的最長公共子序列,而序列和也均為和的最長公共子序列,長度為,而和不存在長度大于等于的公共子序列。最長公共子序列給定序列和,從它們的所有公共子序列中選出長度最長的那一個或幾個。為和的最長公共子序列長度。 最長公共子序列(Longest Common Subsequence LCS)是從給定的兩個序列X和Y中取出盡可能多的一部分字符,按照它們在原序列排列的先后次序排列得到。LCS問...
摘要:遇到問題查查,看看,大神的講解問問島胖君下面是我最近整理出來的關于字符串的文章的怎么翻譯匯集目錄非常希望強化博客的功能,比如分類,置頂。 雖是讀書筆記,但是如轉載請注明出處 http://segmentfault.com/blog/exploring/ .. 拒絕伸手復制黨 最近在看算法和語言,基本屬于看知識 --> java實現(xiàn) --> 整理blog 這個路線。 遇到問題查查st...
摘要:最長公共子序列問題指的是求解兩個序列和的長度最長的公共子序列。當然,可以看出,問題容易出現(xiàn)重疊子問題,這時候,就需要用動態(tài)規(guī)劃法來解決。 問題介紹 ??給定一個序列$X=$,另一個序列$Z=$滿足如下條件時稱為X的子序列:存在一個嚴格遞增的X的下標序列${i_1,i_2,...,i_k}$,對所有的$j=1,2,...,k$滿足$x_{i_j}=z_j.$??給定兩個序列$X$和$Y$...
摘要:最長公共子序列問題指的是求解兩個序列和的長度最長的公共子序列。當然,可以看出,問題容易出現(xiàn)重疊子問題,這時候,就需要用動態(tài)規(guī)劃法來解決。 問題介紹 ??給定一個序列$X=$,另一個序列$Z=$滿足如下條件時稱為X的子序列:存在一個嚴格遞增的X的下標序列${i_1,i_2,...,i_k}$,對所有的$j=1,2,...,k$滿足$x_{i_j}=z_j.$??給定兩個序列$X$和$Y$...
摘要:源代碼管理中,指令,可以查找出編輯前后文件的差異,這是基于動態(tài)規(guī)劃實現(xiàn)的。編輯距離,判斷字符串的相似程度,也是基于動態(tài)規(guī)劃計算。 本文是《算法圖解》筆記 應用場景 一切脫離實際應用場景的算法都是耍流氓! 生物學家根據(jù)最長公共序列來確定 DNA 鏈的相似性,進而判斷兩種動物或疾病有多相似。最長公共序列還被用來尋找多發(fā)性硬化癥治療方案。 源代碼管理中,git diff指令,可以查找出編輯...
閱讀 1805·2023-04-26 02:14
閱讀 3727·2021-11-23 09:51
閱讀 1385·2021-10-13 09:39
閱讀 3971·2021-09-24 10:36
閱讀 3013·2021-09-22 15:55
閱讀 3518·2019-08-30 12:57
閱讀 2039·2019-08-29 15:30
閱讀 1986·2019-08-29 13:19