DP問題求解(三)回文字符串問題

DP問題求解(三)回文字符串問題

所謂回文字符串,Palindromic Substring,指正序和倒序相同的字符串,如aba=>aba

經典的回文字符串問題

詳細題目參考leetcode 5. Longest Palindromic Substring,找出一個字符串中最長的回文子串,如"babad"返回"bab"。

這道很經典的題目,可以用DP來求解,狀態方程如下:

status(i,j)=(i+1 > j-1 || status(i+1,j-1) )&& (s[i] == s[j])

其中status(i,j)代表s[i]....s[j]是否是回文字符串,顯然,status(i,j)是回文字符,當且僅當status(i+1,j-1)是回文字符并且s[i]==s[j]。

注意考慮為什么要添加i+1 > j-1的判斷,因為當j = i + 1的時候,字符子串中只有兩個字符,所以只需要對s[i]是否等于s[j]進行判斷即可。

狀態方程一寫出來,整個題目就迎刃而解了。參考代碼如下:

string longestPalindrome(string s) {
        int len = (int)s.size();
        bool status[len][len];
        memset(status,false,sizeof(status));
        for(int i = 0;i<len;i++){
            status[i][i]=true;
        }
        int maxLen = 1;
        int left = 0;
  //在這里一定要注意更新status的順序,不然結果不正確
        for(int i = len-2;i>=0;i--){
            for(int j = i+1;j<len;j++){
                status[i][j] = ((i+1 > j-1) || status[i+1][j-1]) && (s[i] == s[j]);
                if(status[i][j] && (j-i+1)>maxLen){
                    maxLen = j-i+1;
                    left = i;
                }
            }
        }
        return s.substr(left,maxLen);
    }

回文字符串分割

詳細題目參考leetcode 132. Palindrome Partitioning II,給定一個字符串,在保證分割的子串全都為回文字符串的基礎上,返回最少的切割次數。

這道題還有一個初級版本,是返回所有可能的分割方式,原題參考leetcode 131. Palindrome Partitioning,這道題用普通的DFS遞歸思路就可以解決,具體解決辦法等我寫DFS系列的時候會詳細介紹。如果把這道題的求解思路完全套到上面這道題中,肯定會出現超時錯誤,因為沒有進行任何的狀態存儲。

所以需要想辦法存儲下來它的中間狀態,下面要說的這種解法,雖然在leetcode上時間排名不高,但我認為比較好理解。

首先為了避免重復對字符子串是否是回文的進行判斷,可以按照上一道題中的套路,用一個二維status數組存儲該信息,具體步驟和上面那道題一樣,就不重復介紹了。

然后使用一個一維數組dp[i]表示子串s.substr(0,i)能構成回文子字符串組合的最少切割次數,這樣假設存在k(k < i),有status[k][i]== true,那么這時候dp[i]可以寫成dp[i] = dp[k-1]+1,這樣的話遍歷所有的k小于i,找到最小的dp[i]值,最后dp[len-1]即為所求,具體代碼示例如下:

int status[1500][1500];//如果s過長,在函數內會報錯
int minCut(string s) {
        int len = (int)s.size();
        int dp[len];
        dp[0]= 0;

        memset(status,false,sizeof(status));
        for(int i = 0;i<len;i++){
            status[i][i] = true;
        }

        for(int i = len-2;i>=0;i--){
            for(int j = i+1;j<len;j++){
                status[i][j] = (i+1 > j-1 || status[i+1][j-1]) && (s[i] == s[j]);
            }
        }

        for(int i = 1;i<len;i++){
            int tmp = -1;
            for(int j = 0;j<=i;j++){
                if(status[j][i]){
                    if(j == 0){
                        tmp = 0;
                        break;
                    }
                    if(tmp == -1 || dp[j-1]+1 < tmp){
                        tmp = dp[j-1]+1;
                    }
                }
            }
            dp[i] = tmp;
        }
        return dp[len-1];
    }

最長的回文子序列(Subsequence)

這里要說明一下這道題和第一道最長回文子字符串的區別,即 longest palindrome Substring 和 longest palindrome Subsequence,Substring代表子字符串,所選的字符串應該是連續的,而Subsequence子序列則不要求連續,比如"abcdef"中,Subsequence可以為"abdf",而Substring的下標必須連續。

詳細題目參考leetcode 516. Longest Palindromic Subsequence,從給定字符串中找出最長的回文子序列的長度,如輸入"bbbab",輸出4("bbbb")。

因為沒法列舉出所有的回文子序列,所以可以考慮用一個二維數組保存最長回文子序列的信息,比如status(i,j)代表s[i]...s[j]之間的最長回文子序列的長度,這樣可以寫出狀態方程:

status(i,j) = max(status(i,j-1),status(i+1,j))

status(i,j) = max(status(i,j),stauts(i+1,j-1)) s[i] == s[j]

最后返回status(0,len-1)即為所求。

參考代碼如下:

int longestPalindromeSubseq(string s) {
        int len = (int)s.size();
        int status[len][len];
        memset(status,0,sizeof(status));
        for(int i = 0;i<len;i++){
            for(int j = i;j<len;j++){
                status[i][j] = 1;
            }
        }
        for(int i = len-2;i>=0;i--){
            for(int j = i+1;j<len;j++){
                status[i][j] = max(status[i+1][j],status[i][j-1]);
                if(s[i] == s[j]){
                    status[i][j] = max(status[i+1][j-1]+2,status[i][j]);
                }
            }
        }
        return status[0][len-1];
    }

復雜的計算回文子序列個數問題

同樣和上面那道題一樣是Subsequence問題

詳細題目參考leetcode 730. Count Different Palindromic Subsequences,從給定字符串中招待所有不重復回文子序列的個數,由于結果會很大,最終結果去10^9+7的模。如輸入"bccb",返回6。

很顯然暴力解肯定會出現超時錯誤,那應該如何使用dp來保存狀態,使用status(i,j)表示s(i)...s(j)之間的回文子序列的個數,那么狀態轉移方程分為以下幾種情況:

  • 當s(i) != s(j)時,

    此時,status(i,j) = status(i+1,j)+status)i,j-1)-status(i+1,j-1)

  • 當s(i) == s(j)時,

    又分為以下幾種情況:

    》當s(i+1)....s(j-1)之間沒有和s(i)相同的元素,則

    status(i,j)=status(i+1,j-1)*2+2

    其中的2增加的是s(i),s(j)兩個組成的回文子序列

    》當s(i+1)...s(j-1)之間有且只有一個和s(i)相同的元素,則

    status(i,j)=status(i+1,j-1)*2+1

    其中的1增加的是s(i)s(j)兩個組成的回文子序列,而單獨的s(i)已經計算過了所以沒必要再加了

    》當s(i+1)...s(j-1)中有多個和s(i)相同的元素,則

    status(i,j)=status(i+1,j-1)*2-status(low+1,high-1)

    其中low,high分別代表從左數第一個和s(i)相同的元素,high代表從右數第一個和s(j)相同的元素。減掉的中間部分是重復計算的s(i)s(low+1)...s(high-1)s(j)和s(low)s(low+1)...s(high-1)s(high)

最后需要說明的是,由于結果很可能出現溢出的情況,所以在上面每一步操作中都應該對status(i,j)進行取模操作,而不應該等到最后。又因為在運算中涉及到減操作,很可能得到負值導致結果不正確,所以在status(i,j)為負時應加上模使其變為正,詳細代碼如下:

int status[1000][1000];
int countPalindromicSubsequences(string S) {
        int len = (int)S.size();
        int mod = 1000000007;
        memset(status,0,sizeof(status));
        for(int i = 0;i<len;i++){
            status[i][i] = 1;
        }
        for(int i = len-2;i>=0;i--){
            for(int j = i+1;j<len;j++){
                if(S[i] != S[j]){
                    status[i][j] = (status[i][j-1] + status[i+1][j]-status[i+1][j-1])%mod;
                }
                else{
                    int low = i+1;
                    int high = j-1;
                    while(low <= high && S[low] != S[i]){
                        low++;
                    }
                    while(low <= high && S[high] != S[j]){
                        high--;
                    }
                    if(low > high){
                        status[i][j] = (2*status[i+1][j-1]+2)%mod;
                    }
                    else if(low == high){
                        status[i][j] = (2*status[i+1][j-1]+1)%mod;
                    }
                    else{
                        status[i][j] = (2*status[i+1][j-1] - status[low+1][high-1])%mod;
                    }
                }
                status[i][j] = status[i][j]<0?(status[i][j]+mod)%mod:status[i][j];
            }
        }
        return status[0][len-1];
    }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • 動態規劃(Dynamic Programming) 本文包括: 動態規劃定義 狀態轉移方程 動態規劃算法步驟 最長...
    廖少少閱讀 3,313評論 0 18
  • 0. 動態規劃分析 0.1 動態規劃、遞歸和貪心算法的區別 動態規劃就是利用分治思想和解決冗余的辦法來處理問題,所...
    dreamsfuture閱讀 7,450評論 2 6
  • ------------6.11更新-----------明天(tuo yan)繼續把 Basic & Tall刷...
    Quasars閱讀 2,358評論 0 2
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,761評論 0 33
  • 自我的頹廢與消極 這是你走向自己墳墓的表現 如果你始終不會回頭 正路只會與我漸行漸遠 直到你看不到它 當你處在漂浮...
    半分微涼閱讀 179評論 1 1