給定兩個字符串S和T,對於T我們允許三種操作:
(1) 在任意位置添加任意字符
(2) 刪除存在的任意字符
(3) 修改任意字符
問最少操作多少次可以把字符串T變成S?
例如: S= “ABCF” T = “DBFG”
那么我們可以
(1) 把D改為A
(2) 刪掉G
(3) 加入C
(2) 刪掉G
(3) 加入C
所以答案是3。
分析: 這個最少的操作次數,通常被稱之為編輯距離。“編輯距離”一次本身具有最短的意思在里面。因為題目有“最短”這樣的關鍵詞,首先我們想到的是BFS。是的,當S的距離為m, T的距離為n的時候,我們可以找到這樣的操作次數的界限:
(1) 把T中字符全刪了,再添加S的全部字符,操作次數m + n。
(2) 把T中字符刪或加成m個,再修改 操作次數最多 |n – m| + m。
雖然,我們找到了這樣的上界,BFS從實際角度並不可行,因為搜索空間是指數的,這取決於S中的字符種類——具體的數量級不好估計。
這個問題之所以難,是難在有“添加”“刪除”這樣的操作,很麻煩。我們試試換個角度理解問題,把它看成字符串對齊的問題,事實上從生物信息學對比基因的角度,我們可以這樣理解問題。
給定字符串S和T,我們可以用一種特殊字符促成兩個字符串的對齊。我們加的特殊字符是“-”, 我們允許在S和T中任意添加這種特殊字符使得它長度相同,然后讓這兩個串“對齊”,最終兩個串相同位置出現了不同字符,就扣1分,我們要使得這兩個串對齊扣分盡量少。
對於例子 我們實際上采取了這樣的對齊方式:
12345
ABCF-
DB-FG
注意:如果要對齊,兩個“-”相對是沒有意義的,所以我們要求不出現這種情況。
那么看一下:
(1) S,T對應位置都是普通字符,相同,則不扣分。 例如位置2,4
(2) S,T對應位置都是普通字符,不同,則扣1分。 例如位置1
(3) S在該位置是特殊字符,T在該位置是普通字符,則扣1分,例如位置5
(4) S在該位置是普通字符,T在該位置是特殊字符,則扣1分,例如位置3
我們來看看扣分項目對應什么?
(1) 不扣分,直接對應
(2) 對應把T中對應位置的字符修改
(3) 對應在T中刪除該字符
(4) 對應在T中添加該字符
好了,目標明確,感覺像不像 LCS?我們嘗試一下:
設f(i,j)表示S的前i位和T的前j位對齊后的最少扣分。
那我們來看看最后一位,對齊的情況
(1) 必須S[i] == T[j], 這時前i – 1和j – 1位都已經對齊了,這部分肯定要最少扣分。這種情況下最少的扣分是f(i-1,j-1)
(2) 和(1)類似,S[i]≠T[j],這種情況下最少的扣分是f(i -1, j – 1) + 1
(3) S的前i位和T的前(j – 1)位已經對齊了,這部分扣分也要最少。這種情況下最少的扣分是f(i,j-1) + 1
(4) S的前(i-1)位已經和T的前j位對齊了,這部分扣分要最少。這種情況下最少的扣分是f(i,j-1) + 1
具體f(i,j)取什么值,顯然是要看哪種情況的扣分最少。
給定字符串S和T,我們可以用一種特殊字符促成兩個字符串的對齊。我們加的特殊字符是“-”, 我們允許在S和T中任意添加這種特殊字符使得它長度相同,然后讓這兩個串“對齊”,最終兩個串相同位置出現了不同字符,就扣1分,我們要使得這兩個串對齊扣分盡量少。
對於例子 我們實際上采取了這樣的對齊方式:
12345
ABCF-
DB-FG
注意:如果要對齊,兩個“-”相對是沒有意義的,所以我們要求不出現這種情況。
那么看一下:
(1) S,T對應位置都是普通字符,相同,則不扣分。 例如位置2,4
(2) S,T對應位置都是普通字符,不同,則扣1分。 例如位置1
(3) S在該位置是特殊字符,T在該位置是普通字符,則扣1分,例如位置5
(4) S在該位置是普通字符,T在該位置是特殊字符,則扣1分,例如位置3
我們來看看扣分項目對應什么?
(1) 不扣分,直接對應
(2) 對應把T中對應位置的字符修改
(3) 對應在T中刪除該字符
(4) 對應在T中添加該字符
好了,目標明確,感覺像不像 LCS?我們嘗試一下:
設f(i,j)表示S的前i位和T的前j位對齊后的最少扣分。
那我們來看看最后一位,對齊的情況
(1) 必須S[i] == T[j], 這時前i – 1和j – 1位都已經對齊了,這部分肯定要最少扣分。這種情況下最少的扣分是f(i-1,j-1)
(2) 和(1)類似,S[i]≠T[j],這種情況下最少的扣分是f(i -1, j – 1) + 1
(3) S的前i位和T的前(j – 1)位已經對齊了,這部分扣分也要最少。這種情況下最少的扣分是f(i,j-1) + 1
(4) S的前(i-1)位已經和T的前j位對齊了,這部分扣分要最少。這種情況下最少的扣分是f(i,j-1) + 1
具體f(i,j)取什么值,顯然是要看哪種情況的扣分最少。
為了方便,我們定義函數same(i,j)表示如果S[i] == T[j]則為0,否則為1。
我們來表示一下遞推式:
f(i,j) = min(f(i – 1, j – 1) + same(i,j), f(i – 1,j ) + 1, f(i, j – 1) + 1)
初值是什么?
f(0, j) = j
f(i, 0) = i
這時因為對於S的前0位,我們只能在之前加入“-”,或者說把T全部刪掉了。類似地,對於T地前0位,我們只能把S的字符都加進來,別無選擇。
注意上述兩個式子的重合點 f(0,0) = 0也符合我們的定義,並不矛盾。
時間復雜度? O(m * n),空間復雜度? O(m * n)。同樣我們發現到f(i,j)只與本行和上一行有關,可以省掉一維的空間復雜度,從而達到O(n)。
優化后的偽代碼:
我們來表示一下遞推式:
f(i,j) = min(f(i – 1, j – 1) + same(i,j), f(i – 1,j ) + 1, f(i, j – 1) + 1)
初值是什么?
f(0, j) = j
f(i, 0) = i
這時因為對於S的前0位,我們只能在之前加入“-”,或者說把T全部刪掉了。類似地,對於T地前0位,我們只能把S的字符都加進來,別無選擇。
注意上述兩個式子的重合點 f(0,0) = 0也符合我們的定義,並不矛盾。
時間復雜度? O(m * n),空間復雜度? O(m * n)。同樣我們發現到f(i,j)只與本行和上一行有關,可以省掉一維的空間復雜度,從而達到O(n)。
優化后的偽代碼:
for j = 0 to n do f[j] = j endfor for i = 1 to m do last = f[0] f[0] = i for j = 1 to n do temp = f[i,j] f[i,j] = min(last + same(i,j), temp + 1, f[j – 1] + 1) last = temp endfor endfor
注意: 我們對於i實際上更新j的順序是由小到達的,所以我們需要保存“舊的”f[i-1,j – 1]。
題解:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char a[1010],b[1010]; int f[1010][1010]; int main() { cin>>a; cin>>b; int m=strlen(a); int n=strlen(b); for(int i=1;i<=m;i++) f[i][0]=i; for(int j=1;j<=n;j++) f[0][j]=j; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]; else f[i][j]=min(min(f[i-1][j-1],f[i-1][j]),f[i][j-1])+1; cout<<f[m][n]; }
如果對你有所幫助,別忘了加好評哦;么么噠!!下次見!88