最⻓公共⼦序列
最?公共?序列
文章目錄
- 最?公共?序列
- 一、問題描述
- **?、 動(dòng)態(tài)規(guī)劃思路**
- **方法一:動(dòng)態(tài)規(guī)劃(直接加上備忘錄,否則會(huì)超時(shí))**
- **方法二:DP table解法**
- **三、 總結(jié)**
一、問題描述
給定兩個(gè)字符串 text1 和 text2,返回這兩個(gè)字符串的最長公共子序列。一個(gè)字符串的 子序列 是指這樣一個(gè)新的字符串:它是由原字符串在不改變字符的 相對順序的情況下刪除某些字符(也可以不刪除任何字符)后組成的新字符串。例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 兩個(gè)字符串的「公共子序列」是這兩個(gè)字符串所共同擁有的子序列。若這兩個(gè)字符串沒有公共子序列,則返回 0。示例 1:
輸入:text1 = "abcde", text2 = "ace" 輸出:3 解釋:最長公共子序列是 "ace",它的長度為 3。示例 2:
輸入:text1 = "abc", text2 = "abc" 輸出:3 解釋:最長公共子序列是 "abc",它的長度為 3。示例 3:
輸入:text1 = "abc", text2 = "def" 輸出:0 解釋:兩個(gè)字符串沒有公共子序列,返回 0。提示:
- 1 <= text1.length <= 1000
- 1 <= text2.length <= 1000
輸入的字符串只含有小寫英文字符。
?、 動(dòng)態(tài)規(guī)劃思路
第?步, ?定要明確 dp 數(shù)組的含義。 對于兩個(gè)字符串的動(dòng)態(tài)規(guī)劃問題,套路是通?的
?如說對于字符串 s1 和 s2 , ?般來說都要構(gòu)造?個(gè)這樣的 DP table:
為了?便理解此表, 我們暫時(shí)認(rèn)為索引是從 1 開始的, 待會(huì)的代碼中只要稍作調(diào)整即可。
其中, dp[i][j] 的含義是: 對于 s1[1…i] 和 s2[1…j] ,它們的 最長公共子序列 ?度是 dp[i][j] 。
?如上圖的例?, d[2][4] 的含義就是: 對于 “ac” 和 “babc” , 它們的最長公共子序列 ?度是 2。 我們最終想得到的答案應(yīng)該是 dp[3][6] 。
第?步, 定義 base case
我們專門讓索引為 0 的?和列表?空串, dp[0][…] 和 dp[…][0] 都應(yīng)該初始化為 0, 這就是 base case。
?如說, 按照剛才 dp 數(shù)組的定義, dp[0][3]=0 的含義是: 對于字符串"" 和 “bab” , 其最長公共子序列的?度為 0。 因?yàn)橛?個(gè)字符串是空串, 它們的最?公共?序列的?度顯然應(yīng)該是 0
第三步, 找狀態(tài)轉(zhuǎn)移?程。
這是動(dòng)態(tài)規(guī)劃最難的?步, 不過好在這種字符串問題的套路都差不多, 權(quán)且借這道題來聊聊處理這類問題的思路
狀態(tài)轉(zhuǎn)移說簡單些就是做選擇, ?如說這個(gè)問題, 是求 s1 和 s2 的最?公共?序列, 不妨稱這個(gè)?序列為 lcs 。 那么對于 s1 和 s2 中的每個(gè)字符, 有什么選擇?
很簡單, 兩種選擇, 要么在 lcs 中, 要么不在
這個(gè)「在」 和「不在」 就是選擇, 關(guān)鍵是, 應(yīng)該如何選擇呢?
這個(gè)需要?jiǎng)狱c(diǎn)腦筋: 如果某個(gè)字符應(yīng)該在 lcs 中, 那么這個(gè)字符肯定同時(shí)存在于 s1 和s2 中, 因?yàn)?lcs 是最?公共?序列嘛。
所以本題的思路是這樣:
?兩個(gè)指針 i 和 j 從后往前遍歷 s1 和 s2 , 如果 s1[i]==s2[j] , 那么這個(gè)字符?定在 lcs 中; 否則的話, s1[i] 和 s2[j] 這兩個(gè)字符?少有?個(gè)不在 lcs 中, 需要丟棄?個(gè)。 先看?下遞歸解法, ?較容易理解:
def longestCommonSubsequence(str1, str2) -> int:def dp(i, j):# 空串的 base caseif i == -1 or j == -1:return 0if str1[i] == str2[j]:# 這邊找到?個(gè) lcs 的元素, 繼續(xù)往前找return dp(i - 1, j - 1) + 1else:# 誰能讓 lcs 最?, 就聽誰的return max(dp(i-1, j), dp(i, j-1))# i 和 j 初始化為最后?個(gè)索引 return dp(len(str1)-1, len(str2)-1)對于第?種情況, 找到?個(gè) lcs 中的字符, 同時(shí)將 i j 向前移動(dòng)?位, 并給 lcs 的?度加?; 對于后者, 則嘗試兩種情況, 取更?的結(jié)果
方法一:動(dòng)態(tài)規(guī)劃(直接加上備忘錄,否則會(huì)超時(shí))
class Solution { public:int dp(string& s1, string& s2,int i,int j,vector<vector<int>>& ret){//當(dāng)有 i 或者 j 任意一個(gè)小于0,就代表其遍歷結(jié)束 或者 它本身就是空字符串,但是結(jié)果都是返回0,因?yàn)橐呀?jīng)沒有字符了嗨皮配個(gè)求,直接return0;if(i < 0 || j < 0){return 0;}//優(yōu)先在備忘錄中查找,避免重復(fù)當(dāng)計(jì)算if(ret[i][j] != -1){return ret[i][j];}//如果當(dāng)前位置當(dāng)兩個(gè)字符相等,最長公共子序列當(dāng)長度加一,繼續(xù)往前遞歸if(s1[i] == s2[j]){ret[i][j] = dp(s1,s2,i - 1,j - 1,ret) + 1;}else//如果當(dāng)前兩個(gè)位置不想等,注意:1.i位置當(dāng)字符可能出現(xiàn)//在最長公共子序列中;2.j位置當(dāng)字符可能出現(xiàn)在最長公共子序列當(dāng)中;//3.i和j兩個(gè)字符都不出現(xiàn)在公共子序列當(dāng)中,主要的情況是1,2兩種,//遞歸選取其中能讓最長公共子序列的長度最大的一個(gè)結(jié)果,對于第3種情//況,是可以不加在代碼中和其他兩種進(jìn)行比較的,因?yàn)榈?種情況的最長公//共子序列肯定比第1,2兩種情況短的,在進(jìn)行max時(shí)肯定取不到它的值,//所以直接省道。注意此處的長度不要加一,因?yàn)橹挥许憫?yīng)的字符相等時(shí)才可以加一{ret[i][j] = max(dp(s1,s2,i - 1,j,ret),dp(s1,s2,i,j - 1,ret));}return ret[i][j];}int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> ret(text1.size() + 1,vector<int>(text2.size() + 1,-1));return dp(text1,text2,text1.size() - 1,text2.size() - 1,ret);} };方法二:DP table解法
def longestCommonSubsequence(str1, str2) -> int:m, n = len(str1), len(str2)# 構(gòu)建 DP table 和 base casedp = [[0] * (n + 1) for _ in range(m + 1)]# 進(jìn)?狀態(tài)轉(zhuǎn)移for i in range(1, m + 1):for j in range(1, n + 1):if str1[i - 1] == str2[j - 1]:# 找到?個(gè) lcs 中的字符dp[i][j] = 1 + dp[i-1][j-1]else:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) return dp[-1][-1]C++代碼:
class Solution { public:int longestCommonSubsequence(string text1, string text2) {//注意此處初始化就不能初始化為 -1 了,得初始化為0,避免影響結(jié)果vector<vector<int>> dp(text1.size() + 1,vector<int>(text2.size() + 1,0));//遍歷for(int i = 1;i < text1.size() + 1;i++){for(int j = 1;j < text2.size() + 1;j++){//注意i和j只從1開始的//如果當(dāng)前兩個(gè)字符相等,就把dp[i - 1][j - 1]的結(jié)果 + 1賦值給dp[i][j](相當(dāng)于把i - 1,j - 1之前的最長公共梓序列的長度 加上此時(shí)相等一個(gè)字符就等于i和j位置的最長公共子序列的長度)if(text1[i - 1] == text2[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;}else{//同理,在博客當(dāng)中,就不注釋了dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]);}}}//返回結(jié)果,注意vector的大小定義return dp[text1.size()][text2.size()];} };三、 總結(jié)
對于兩個(gè)字符串的動(dòng)態(tài)規(guī)劃問題, ?般來說都是像本??樣定義 DP table,因?yàn)檫@樣定義有?個(gè)好處, 就是容易寫出狀態(tài)轉(zhuǎn)移?程, dp[i][j] 的狀態(tài)可以通過之前的狀態(tài)推導(dǎo)出來:
總結(jié)
- 上一篇: 鸡蛋掉落
- 下一篇: Nim 游戏 、⽯头游戏1、石头游戏2