第九章 動態(tài)規(guī)劃part13
詳細(xì)布置
647. 回文子串
動態(tài)規(guī)劃
-
確定dp數(shù)組(dp table)以及下標(biāo)的含義
dp[i] 和 dp[i-1] 烤咧,dp[i + 1] 看上去都沒啥關(guān)系。
所以我們要看回文串的性質(zhì)抢呆。 如圖:
我們在判斷字符串S是否是回文煮嫌,那么如果我們知道 s[1],s[2]抱虐,s[3] 這個子串是回文的昌阿,那么只需要比較 s[0]和s[4]這兩個元素是否相同,如果相同的話恳邀,這個字符串s 就是回文串懦冰。
那么此時我們是不是能找到一種遞歸關(guān)系,也就是判斷一個子字符串(字符串的下表范圍[i,j])是否回文谣沸,依賴于刷钢,子字符串(下表范圍[i + 1, j - 1])) 是否是回文。
所以為了明確這種遞歸關(guān)系鳄抒,我們的dp數(shù)組是要定義成一位二維dp數(shù)組闯捎。
布爾類型的dp[i][j]:表示區(qū)間范圍[i,j] (注意是左閉右閉)的子串是否是回文子串,如果是dp[i][j]為true许溅,否則為false瓤鼻。 確定遞推公式
在確定遞推公式時,就要分析如下幾種情況贤重。
整體上是兩種茬祷,就是s[i]與s[j]相等,s[i]與s[j]不相等這兩種并蝗。
- 當(dāng)s[i]與s[j]不相等祭犯,dp[i][j]一定是false。
- 當(dāng)s[i]與s[j]相等時滚停,有如下三種情況
- 情況一:下標(biāo)i 與 j相同沃粗,同一個字符例如a,當(dāng)然是回文子串
- 情況二:下標(biāo)i 與 j相差為1键畴,例如aa最盅,也是回文子串
- 情況三:下標(biāo):i 與 j相差大于1的時候,例如cabac起惕,此時s[i]與s[j]已經(jīng)相同了涡贱,我們看i到j(luò)區(qū)間是不是回文子串就看aba是不是回文就可以了,那么aba的區(qū)間就是 i+1 與 j-1區(qū)間惹想,這個區(qū)間是不是回文就看dp[i + 1][j - 1]是否為true问词。
以上三種情況分析完了,那么遞歸公式如下:
if (s[i] == s[j]) {
if (j - i <= 1) { // 情況一 和 情況二
result++;
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情況三
result++;
dp[i][j] = true;
}
}
result就是統(tǒng)計回文子串的數(shù)量嘀粱。
注意這里沒有列出當(dāng)s[i]與s[j]不相等的時候激挪,因為在下面dp[i][j]初始化的時候辰狡,就初始為false。
dp數(shù)組如何初始化
dp[i][j]可以初始化為true么灌灾? 當(dāng)然不行搓译,怎能剛開始就全都匹配上了悲柱。
所以dp[i][j]初始化為false锋喜。-
確定遍歷順序
dp[i + 1][j - 1] 在 dp[i][j]的左下角,如圖:
首先從遞推公式中可以看出,情況三是根據(jù)dp[i + 1][j - 1]是否為true豌鸡,在對dp[i][j]進(jìn)行賦值true的嘿般。
如果這矩陣是從上到下涯冠,從左到右遍歷炉奴,那么會用到?jīng)]有計算過的dp[i + 1][j - 1],也就是根據(jù)不確定是不是回文的區(qū)間[i+1,j-1]蛇更,來判斷了[i,j]是不是回文瞻赶,那結(jié)果一定是不對的。
所以一定要從下到上派任,從左到右遍歷砸逊,這樣保證dp[i + 1][j - 1]都是經(jīng)過計算的。 -
舉例推導(dǎo)dp數(shù)組
舉例掌逛,輸入:"aaa"师逸,dp[i][j]狀態(tài)如下:
class Solution {
public int countSubstrings(String s) {
char[] chars = s.toCharArray();
int len = s.length();
boolean[][] dp = new boolean[len][len];
int result = 0;
for(int i = len - 1; i >= 0; i--){
for(int j = i; j < len; j++){
if(chars[i] == chars[j]){
if(j - i <= 1){ // 情況1 和情況2
dp[i][j] = true;
result++;
}else if(dp[i + 1][j - 1]){ //情況3
dp[i][j] = true;
result++;
}
}
}
}
return result;
}
}
雙指針法
動態(tài)規(guī)劃的空間復(fù)雜度是偏高的,我們再看一下雙指針法豆混。
首先確定回文串篓像,就是找中心然后向兩邊擴(kuò)散看是不是對稱的就可以了。
在遍歷中心點的時候皿伺,要注意中心點有兩種情況员辩。
一個元素可以作為中心點,兩個元素也可以作為中心點鸵鸥。
那么有人同學(xué)問了奠滑,三個元素還可以做中心點呢。其實三個元素就可以由一個元素左右添加元素得到脂男,四個元素則可以由兩個元素左右添加元素得到养叛。
所以我們在計算的時候,要注意一個元素為中心點和兩個元素為中心點的情況宰翅。
這兩種情況可以放在一起計算弃甥,但分別計算思路更清晰
class Solution {
public int countSubstrings(String s) {
int result = 0;
for(int i = 0; i < s.length(); i++){
result += extend(s, i, i); //以i為中心
result += extend(s, i, i + 1); //以i和i+1為中心
}
return result;
}
private int extend(String s, int i, int j){
int res = 0;
while(i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)){
res++;
i--; //注意這里是i-- j++
j++;
}
return res;
}
}
516.最長回文子序列
647. 回文子串,求的是回文子串汁讼,而本題要求的是回文子序列淆攻, 大家要搞清楚兩者之間的區(qū)別阔墩。
文章講解
思路
- 本題是求回文子序列,回文子串是要連續(xù)的瓶珊,回文子序列可不是連續(xù)的啸箫!
確定dp數(shù)組(dp table)以及下標(biāo)的含義
dp[i][j]:字符串s在[i, j]范圍內(nèi)最長的回文子序列的長度為dp[i][j]。-
確定遞推公式
如果s[i]與s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
在判斷回文子串的題目中伞芹,關(guān)鍵邏輯就是看s[i]與s[j]是否相同忘苛。
如果s[i]與s[j]不相同唱较,說明s[i]和s[j]的同時加入 并不能增加[i,j]區(qū)間回文子序列的長度扎唾,那么分別加入s[i]、s[j]看看哪一個可以組成最長的回文子序列南缓。
加入s[j]的回文子序列長度為dp[i + 1][j]胸遇。
加入s[i]的回文子序列長度為dp[i][j - 1]。
那么dp[i][j]一定是取最大的汉形,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
- dp數(shù)組如何初始化
- 首先要考慮當(dāng)i 和j 相同的情況纸镊,從遞推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 遞推公式是計算不到 i 和j相同時候的情況。
- 所以需要手動初始化一下概疆,當(dāng)i與j相同逗威,那么dp[i][j]一定是等于1的,即:一個字符的回文子序列長度就是1届案。
- 其他情況dp[i][j]初始為0就行庵楷,這樣遞推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 中dp[i][j]才不會被初始值覆蓋。
-
確定遍歷順序
從遞歸公式中楣颠,可以看出尽纽,dp[i][j] 依賴于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1]童漩,如圖:
所以遍歷i的時候一定要從下到上遍歷弄贿,這樣才能保證下一行的數(shù)據(jù)是經(jīng)過計算的。
j可以正常從左向右遍歷矫膨。 -
舉例推導(dǎo)dp數(shù)組
輸入s:"cbbd" 為例差凹,dp數(shù)組狀態(tài)如圖:
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len + 1][len + 1];
for(int i = len - 1; i >= 0; i--){
dp[i][i] = 1;
for(int j = i + 1; j < len; j++){
if(s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i + 1][j - 1] + 2;
}else{
dp[i][j] = Math.max(dp[i][j], Math.max(dp[i + 1][j], dp[i][j - 1]));
}
}
}
return dp[0][len - 1];
}
}
動態(tài)規(guī)劃總結(jié)篇
https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E6%80%BB%E7%BB%93%E7%AF%87.html