給定兩個字串 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
輸入的字串只含有小寫英文本元。
最長公共子串行(longest common subsequence,簡稱 lcs)是一道非常經典的面試題目,因為它的解法是典型的二維動態規劃,大部分比較困難的字串問題都和這個問題乙個套路,比如說編輯距離。而且,這個演算法稍加改造就可以用於解決其他問題,所以說 lcs 演算法是值得掌握的。
題目就是讓我們求兩個字串的 lcs 長度。
肯定有讀者會問,為啥這個問題就是動態規劃來解決呢?因為子串行型別的問題,窮舉出所有可能的結果都不容易,而動態規劃演算法做的就是窮舉 + 剪枝,它倆天生一對兒。所以可以說只要涉及子串行問題,十有**都需要動態規劃來解決,往這方面考慮就對了。
下面就來手把手分析一下,這道題目如何用動態規劃技巧解決。
第一步,一定要明確dp
陣列的含義。對於兩個字串的動態規劃問題,套路是通用的。
比如說對於字串s1
和s2
,一般來說都要構造乙個這樣的 dp table:
為了方便理解此表,我們暫時認為索引是從 1 開始的,待會的**中只要稍作調整即可。其中,dp[i][j]
的含義是:對於s1[1..i]
和s2[1..j]
,它們的 lcs 長度是dp[i][j]
。
比如上圖的例子,d[2][4]
的含義就是:對於"ac"
和"babc"
,它們的 lcs 長度是 2。我們最終想得到的答案應該是dp[3][6]
。
第二步,定義 base case。
我們專門讓索引為 0 的行和列表示空串,dp[0][..]
和dp[..][0]
都應該初始化為 0,這就是 base case。
比如說,按照剛才 dp 陣列的定義,dp[0][3]=0
的含義是:對於字串""
和"bab"
,其 lcs 的長度為 0。因為有乙個字串是空串,它們的最長公共子串行的長度顯然應該是 0。
第三步,找狀態轉移方程。
這是動態規劃最難的一步,不過好在這種字串問題的套路都差不多,權且借這道題來聊聊處理這類問題的思路。
狀態轉移說簡單些就是做選擇,比如說這個問題,是求s1
和s2
的最長公共子串行,不妨稱這個子串行為 lcs。那麼對於s1
和s2
中的每個字元,有什麼選擇?很簡單,兩種選擇,要麼在lcs
中,要麼不在。
這個「在」和「不在」就是選擇,關鍵是,應該如何選擇呢?這個需要動點腦筋:如果某個字元應該在lcs
中,那麼這個字元肯定同時存在於s1
和s2
中,因為lcs
是最長公共子串行嘛。所以本題的思路是這樣:
用兩個指標i
和j
從後往前遍歷s1
和s2
,如果s1[i]==s2[j]
,那麼這個字元一定在lcs
中;否則的話,s1[i]
和s2[j]
這兩個字元至少有乙個不在lcs
中,需要丟棄乙個。先看一下遞迴解法,比較容易理解:
def longestcommonsubsequence
(str1, str2)
->
int:
def dp
(i, j)
: # 空串的 base case
if i ==
-1 or j ==-1
:return
0if str1[i]
== str2[j]
: # 這邊找到乙個 lcs 的元素,繼續往前找
return
dp(i -
1, j -1)
+1else
: # 誰能讓 lcs 最長,就聽誰的
return
max(
dp(i-
1, j),dp
(i, j-1)
) # i 和 j 初始化為最後乙個索引
returndp(
len(str1)-1
,len
(str2)-1
)
對於第一種情況,找到乙個lcs
中的字元,同時將i
j
向前移動一位,並給 lcs 的長度加一;對於後者,則嘗試兩種情況,取更大的結果。
其實這段**就是暴力解法,我們可以通過備忘錄或者 dp table 來優化時間複雜度,比如通過前文描述的 dp table 來解決:
class
solution
return dp[len1]
[len2];}
}
對於兩個字串的動態規劃問題,一般來說都是像本文一樣定義 dp table,因為這樣定義有乙個好處,就是容易寫出狀態轉移方程,dp[i][j]
的狀態可以通過之前的狀態推導出來:
找狀態轉移方程的方法是,思考每個狀態有哪些「選擇」,只要我們能用正確的邏輯做出正確的選擇,演算法就能夠正確執行。
1143 最長公共子串行
給定兩個字串 text1 和 text2,返回這兩個字串的最長公共子串行。乙個字串的 子串行 是指這樣乙個新的字串 它是由原字串在不改變字元的相對順序的情況下刪除某些字元 也可以不刪除任何字元 後組成的新字串。例如,ace 是 abcde 的子串行,但 aec 不是 abcde 的子串行。兩個字串的...
1143 最長公共子串行
一.題目描述 二.動態規劃解法 例如 s1 abcde s2 ace 求兩個字串的公共子串行,答案是 ace 1.s t 2.子問題劃分 1 如果s的最後一位等於t的最後一位,則最大子串行就是和的最大子串行 1 2 如果s的最後一位不等於t的最後一位,那麼最大子串行就是 和 最大子串行 和 最大子串...
1143 最長公共子串行
給定兩個字串 text1 和 text2,返回這兩個字串的最長公共子串行的長度。乙個字串的 子串行 是指這樣乙個新的字串 它是由原字串在不改變字元的相對順序的情況下刪除某些字元 也可以不刪除任何字元 後組成的新字串。例如,ace 是 abcde 的子串行,但 aec 不是 abcde 的子串行。兩個...