1. 程式人生 > 實用技巧 >【LeetCode】647. 迴文子串

【LeetCode】647. 迴文子串

題目連結

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;
    }
}