算法-兩個字串差異性問題
這類問題都可以藉助動態規劃實現,其中字符串1作爲x軸,字符串2作爲y軸
精要概括一下,就是兩個字符串支持四種操作,分別是添加,修改,刪除,不變
1、編輯距離問題
給你兩個單詞 word1 和 word2,請你計算出將 word1 轉換成 word2 所使用的最少操作數 。
你可以對一個單詞進行如下三種操作:
插入一個字符
刪除一個字符
替換一個字符
示例 1:
輸入:word1 = "horse", word2 = "ros"
輸出:3
解釋:
horse -> rorse (將 'h' 替換爲 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')
示例 2:
輸入:word1 = "intention", word2 = "execution"
輸出:5
解釋:
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換爲 'e')
enention -> exention (將 'n' 替換爲 'x')
exention -> exection (將 'n' 替換爲 'c')
exection -> execution (插入 'u')
藉助這張圖可以更好理解:
編輯距離支持插入刪除和替換操作,我們建立動態規劃方程dp[i][j]來表示字符串在第i-1和第j-1處的匹配情況,可以分爲兩種情況
cs1[i-1]==cs2[j-1],表明字符串匹配,不需要編輯,那麼:
dp[i][j]=dp[i-1][j-1]
cs1[i-1]!=cs2[j-1],需要執行插入刪除替換操作,究竟哪一種操作好呢?我們應當計算最小值,再+1步。
dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
那動態規劃方程的初始狀態如何確定呢?
我們知道從空字串變到某個字串只有一種可能性,就是添加字符,所以,數組第一行和第一列都等於對應的行列值。
...
dp[i][0]=i;
...
dp[0][j]=j;
...
至此問題就迎刃而解了:
public int minDistance(String word1, String word2) {
char[] cs1=word1.toCharArray();
char[] cs2=word2.toCharArray();
int[][] dp=new int[cs1.length+1][cs2.length+1];
for(int i=0;i<=cs1.length;i++){
dp[i][0]=i;
}
for(int j=0;j<=cs2.length;j++){
dp[0][j]=j;
}
for(int i=1;i<=cs1.length;i++){
for(int j=1;j<=cs2.length;j++){
if(cs1[i-1]==cs2[j-1]){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
}
return dp[cs1.length][cs2.length];
}
2、最長公共子序列問題
給定兩個字符串 text1 和 text2,返回這兩個字符串的最長公共子序列。
一個字符串的 子序列 是指這樣一個新的字符串:它是由原字符串在不改變字符的相對順序的情況下刪除某些字符(也可以不刪除任何字符)後組成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。兩個字符串的「公共子序列」是這兩個字符串所共同擁有的子序列。
若這兩個字符串沒有公共子序列,則返回 0。
示例 1:
輸入:text1 = "abcde", text2 = "ace"
輸出:3
解釋:最長公共子序列是 "ace",它的長度爲 3。
示例 2:
輸入:text1 = "abc", text2 = "abc"
輸出:3
解釋:最長公共子序列是 "abc",它的長度爲 3。
示例 3:
輸入:text1 = "abc", text2 = "def"
輸出:0
解釋:兩個字符串沒有公共子序列,返回 0。
提示:
1 <= text1.length <= 1000
1 <= text2.length <= 1000
輸入的字符串只含有小寫英文字符。
下面的圖是從別的地方找來的,藉以說明本問題的解決方案。與編輯距離不同的是,本題目中不能添加字符,只能刪減字符,所以數組的第一行和第一列都爲0,而編輯距離中由於可以添加字串,所以第一行第一列都爲1(除0,0位置元素)。
本題中只有刪除操作
設dp[i][j]爲字符串在i位置和j位置的最長字串長度,那麼可以看出,如果cs[i-1]==cs[j-1],那麼位置(i,j)的值dp[i][j]=dp[i-1][j-1]+1
否則的話dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])。也就是刪除一個字符
由此我們可以得出結論
public int longestCommonSubsequence(String text1, String text2) {
char cs1[]=text1.toCharArray();
char cs2[]=text2.toCharArray();
int[][] dp=new int[cs1.length+1][cs2.length+1];
for(int i=1;i<=cs1.length;i++){
for(int j=1;j<=cs2.length;j++){
if(cs1[i-1]==cs2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[cs1.length][cs2.length];
}
3、不同的子序列
給定一個字符串 S 和一個字符串 T,計算在 S 的子序列中 T 出現的個數。
一個字符串的一個子序列是指,通過刪除一些(也可以不刪除)字符且不干擾剩餘字符相對位置所組成的新字符串。(例如,"ACE" 是 "ABCDE" 的一個子序列,而 "AEC" 不是)
示例 1:
輸入:S = "rabbbit", T = "rabbit"
輸出:3
解釋:
如下圖所示, 有 3 種可以從 S 中得到 "rabbit" 的方案。
(上箭頭符號 ^ 表示選取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例 2:
輸入:S = "babgbag", T = "bag"
輸出:5
解釋:
如下圖所示, 有 5 種可以從 S 中得到 "bag" 的方案。
(上箭頭符號 ^ 表示選取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
這題與上面兩題如出一轍,只不過條件改成了只能刪除或者不刪除。
不刪除是什麼情況呢?
dp[i][j]=dp[i-1][j-1];
刪除是什麼情況呢?
p[i][j]=dp[i][j-1];
如果對應兩個位置的字符相同,我們選擇刪除或者不刪除都可以,所以:
dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
public int numDistinct(String s, String t) {
int len1=s.length();
int len2=t.length();
int [][]dp=new int[len2+1][len1+1];
for(int i=0;i<=len1;i++){
dp[0][i]=1;//第一行爲1,因爲刪除字符得到空字符
}
char[] cs=s.toCharArray();
char[] ct=t.toCharArray();
for(int i=1;i<=len2;i++){
for(int j=1;j<=len1;j++){
if(cs[j-1]==ct[i-1]){
dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
}else{
dp[i][j]=dp[i][j-1];
}
}
}
return dp[len2][len1];
}