《靈魂駭客2》“日刊・林檎與惡魔的未來預測”開始!
阿新 • • 發佈:2022-03-10
最長公共子序列問題
題目:最長公共子序列問題
《程式設計師程式碼面試指南》第65題 P220 難度:尉★★☆☆
本題是非常經典的動態規劃問題,先來介紹求解動態規劃表的過程。如果str1的長度為M,str2的長度為N,生成大小為M×N的矩陣dp。dp[i][j]的含義是str1[0..i]與str2[0..j]的最長公共子序列的長度。從左到右,再從上到下計算矩陣dp。
- 矩陣dp第一列即dp[0..M-1][0],dp[i][0]的含義是str1[0..i]與str2[0]的最長公共子序列長度。顯然dp[i][0]最大為1。一旦dp[i][0]被設定為1,之後的dp[i+1..M-1][0]也都為1。
- 矩陣dp第一行
- 對於其他位置(i,j),dp[i][j]的值可能來自這三種情況:①可能是dp[i-1][j] ②可能是dp[i][j-1] ③如果str1[i]==str2[j],還可能是dp[i-1][j-1]+1。這三個可能的值中,選最大的作為dp[i][j]的值。
具體過程參看如下的getdp方法:
public int[][] getdp(char[] str1, char[] str2) { int[][] dp = new int[str1.length][str2.length]; dp[0][0] = str1[0] == str2[0] ? 1 : 0; for (int i = 1; i < str1.length; i++) { dp[i][0] = Math.max(dp[i - 1][0], str1[i] == str2[0] ? 1 : 0); } for (int j = 1; j < str2.length; j++) { dp[0][j] = Math.max(dp[0][j - 1], str1[0] == str2[j] ? 1 : 0); } for (int i = 1; i < str1.length; i++) { for (int j = 1; j < str2.length; j++) { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); if (str1[i] == str2[j]) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); } } } return dp; }
dp矩陣中最右下角的值代表str1整體和str2整體的最長公共子序列的長度。通過整個dp矩陣的狀態,可以得到最長公共子序列。具體方法如下:
- 從矩陣的右下角開始,有三種移動方式:向上、向左、向左上。假設移動的過程中,i表示此時的行數,j表示此時的列數,同時用一個變數res來表示最長公共子序列。
- 如果dp[i][j]大於dp[i-1][j]和dp[i][j-1],說明之前在計算dp[i][j]的時候,一定是選擇了決策dp[i-1][j-1]+1,可以確定str1[i]等於str2[j],並且這個字元一定屬於最長公共子序列,把這個字元放進res,然後向左上方移動。
- 如果dp[i][j]等於dp[i-1][j]
- 如果dp[i][j]等於dp[i][j-1],與步驟3同理,向左方移動。
- 如果dp[i][j]同時等於dp[i-1][j]和dp[i][j-1],向上還是向下無所謂,選擇其中一個即可,反正不會錯過必須選擇的字元。
也就是說,通過dp求解最長公共子序列的過程就是還原出當時如何求解dp的過程,來自哪個策略,就朝哪個方向移動。全部過程請參看如下程式碼中的Icse方法。
public String lcse(String str1, String str2) {
if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) {
return "";
}
char[] chs1 = str1.toCharArray();
char[] chs2 = str2.toCharArray();
int[][] dp = getdp(chs1, chs2);
int m = chs1.length - 1;
int n = chs2.length - 1;
char[] res = new char[dp[m][n]];
int index = res.length - 1;
while (index >= 0) {
if (n > 0 && dp[m][n] == dp[m][n - 1]) {
n--;
} else if (m > 0 && dp[m][n] == dp[m - 1][n]) {
m--;
} else {
res[index--] = chs1[m];
m--;
n--;
}
}
return String.valueOf(res);
}
總的時間複雜度為O(M×N)。如果本題只想求最長公共子序列的長度,則可以使用空間壓縮的方法。