【LeetCode】647. 迴文子串
阿新 • • 發佈:2020-08-19
題目連結
647. 迴文子串
題目描述
示例 1:
輸入:"abc"
輸出:3
解釋:三個迴文子串: "a", "b", "c"
示例 2:
輸入:"aaa"
輸出:6
解釋:6個迴文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
輸入的字串長度不會超過 1000 。
解題思路
1.暴力列舉
- 字串問題最土最有效的方法應該就是暴力枚舉了,利用雙重for迴圈可以得到字串s的所有子串,然後對這些子串進行一一判斷,如果是迴文串,ans++;
- 一般來說,暴力都會被卡超時,沒想到這題竟然過了
2.中心擴充套件法
通過觀察可以發現,迴文字串分為兩種:
- 偶數型迴文字串:如果迴文字串的長度為偶數,那麼它中間一定是兩個相同的元素,例如cbaabc,中間是兩個相同的aa;
- 奇數型迴文字串:如果迴文字串的長度為奇數,那麼它中間一定是一個元素,例如cbabc,中間一個a元素;
所以用for先找偶數型(aa)迴文,找s[i]=s[i+1]
,找到後再往兩邊推,直到s[i-len]與s[i+1+len]
不等為止。
然後再找奇數型(aba)迴文,找s[i]=s[i+2]
,找到後處理同上。
因為這是從迴文中間出發,所以也叫中心擴充套件法。
3.動態規劃
動態規劃一般也只能應用於有最優子結構的問題。最優子結構的意思是區域性最優解能決定全域性最優解(對有些問題這個要求並不能完全滿足,故有時需要引入一定的近似)。簡單地說,問題能夠分解成子問題來解決。 動態規劃演算法分以下4個步驟: (1)描述最優解的結構 (2)遞迴定義最優解的值 (3)按自底向上的方式計算最優解的值 //此3步構成動態規劃解的基礎。 (4)由計算出的結果構造一個最優解。 //此步如果只要求計算最優解的值時,可省略。 好,接下來,咱們討論適合採用動態規劃方法的最優化問題的倆個要素:最優子結構性質,和子問題重疊性質。 (1)最優子結構 如果問題的最優解所包含的子問題的解也是最優的,我們就稱該問題具有最優子結構性質(即滿足最優化原理)。意思就是,總問題包含很多個子問題,而這些子問題的解也是最優的。 (2)重疊子問題 子問題重疊性質是指在用遞迴演算法自頂向下對問題進行求解時,每次產生的子問題並不總是新問題,有些子問題會被重複計算多次。動態規劃演算法正是利用了這種子問題的重疊性質,對每一個子問題只計算一次,然後將其計算結果儲存在一個表格中,當再次需要計算已經計算過的子問題時,只是在表格中簡單地檢視一下結果,從而獲得較高的效率。
該問題滿足最優子結構性質:一個最長迴文串去掉兩頭以後,剩下的部分依然是最長迴文串。
第一步:定義狀態
dp[i][j]:表示子串s[i,j]是否為迴文子串。
當dp[i][j] = 1:表示子串s[i,j]為迴文子串,反之。
第二步:狀態轉移方程
這一步在做分類討論(根據頭尾字元是否相等)。
if(j-i >= 2){
dp[i][j] = 1, if dp[i+1][j-1] == 1 && s[i] == s[j]
dp[i][j] = 0, if dp[i+1][j-1] == 0 || s[i] != s[j]
}
else{
dp[i][j] = 1,if s[i] == s[j]
dp[i][j] = 0,if s[i] != s[j]
}
-Q:j-i >= 2這個條件是如何求解出來的?
-A:因為dp[i][j]表示子串s[i,j]是否為迴文子串,所以i<=j,同理dp[i+1][j-1]中,i+1 <= j-1,整理得j - i <= 2.
第三步:考慮初始化
初始化的時候,單個字元一定是迴文串,因此把對角線先初始化為 1,即 dp[i,i]
= 1 。
第四步:考慮輸出
判斷dp[i,j]
是否等於 1,等於的話ans++即可。
第五步:考慮狀態是否可以壓縮
因為在填表的過程中,只參考了左下方的數值。事實上可以壓縮,但會增加一些判斷語句,增加程式碼編寫和理解的難度,丟失可讀性。在這裡不做狀態壓縮。
AC程式碼
1.暴力列舉
class Solution {
//判斷是否為迴文子串建議都這樣寫for迴圈語句,這樣能夠減少迴圈次數。
boolean charge(String s){
for(int i = 0; i < s.length() / 2; i++){
if(s.charAt(i) != s.charAt(s.length() - i - 1)) return false;
}
return true;
}
public int countSubstrings(String s) {
//因為單個字母也算迴文,所以ans的初始值就是字串的長度。
int ans = s.length();
for(int i = 0; i < s.length() - 1; i++){
StringBuilder sb = new StringBuilder();
sb.append(s.charAt(i));
for(int j = i + 1; j < s.length(); j++){
sb.append(s.charAt(j));
if(charge(sb.toString())){
ans++;
}
}
}
return ans;
}
}
2.中心擴充套件法
class Solution {
public int countSubstrings(String s) {
int ans = s.length();
//奇數型迴文
for(int i = 0; i < s.length() - 2; i++){
if(s.charAt(i) == s.charAt(i+2)){
ans++;
int temp = 1;
while((i-temp)>=0 && (i+2+temp)<s.length() && s.charAt(i-temp)==s.charAt(i+2+temp)){
ans++;
temp++;
}
}
}
//偶數型迴文
for(int i = 0; i < s.length() - 1; i++){
if(s.charAt(i) == s.charAt(i+1)){
ans++;
int temp = 1;
while((i-temp)>=0&&(i+1+temp)<s.length() && s.charAt(i-temp)==s.charAt(i+1+temp)){
ans++;
temp++;
}
}
}
return ans;
}
}
3.動態規劃法
填表必須是從上往下如箭頭所示,一開始我選擇從左到右填表,卡了很久。
dp[0][5]依賴與dp[1][4]以及s[0]s[5]是否相等,如果你是從左到右填表,那dp[1][4]的值是未知的。
class Solution {
public int countSubstrings(String s) {
int[][] dp = new int[s.length()][s.length()];
int ans = 0;
for(int i = 0; i < s.length(); i++){
dp[i][i] = 1;
ans++;
}
//下面的兩個for迴圈就是在填表格!但是要注意填表的順序!!!!!!!!
for(int j = 0; j < s.length(); j++){
for(int i = 0; i < j; i++){
if(j - i <= 2){
if(s.charAt(j) == s.charAt(i)) dp[i][j] = 1;
else dp[i][j] = 0;
}
else{
if(s.charAt(j) == s.charAt(i) && dp[i+1][j-1]==1) dp[i][j] = 1;
else dp[i][j] = 0;
}
if(dp[i][j] == 1) ans++;
}
}
return ans;
}
}