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

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

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

經(jīng)典的回文字符串問題

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

這道很經(jīng)典的題目,可以用DP來求解拷窜,狀態(tài)方程如下:

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]進行判斷即可挽鞠。

狀態(tài)方程一寫出來疚颊,整個題目就迎刃而解了。參考代碼如下:

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材义,給定一個字符串,在保證分割的子串全都為回文字符串的基礎上嫁赏,返回最少的切割次數(shù)其掂。

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

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

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

然后使用一個一維數(shù)組dp[i]表示子串s.substr(0,i)能構成回文子字符串組合的最少切割次數(shù)蝇率,這樣假設存在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過長监氢,在函數(shù)內(nèi)會報錯
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)

這里要說明一下這道題和第一道最長回文子字符串的區(qū)別,即 longest palindrome Substring 和 longest palindrome Subsequence藤违,Substring代表子字符串浪腐,所選的字符串應該是連續(xù)的,而Subsequence子序列則不要求連續(xù)顿乒,比如"abcdef"中议街,Subsequence可以為"abdf",而Substring的下標必須連續(xù)璧榄。

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

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

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

復雜的計算回文子序列個數(shù)問題

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

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

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

  • 當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)已經(jīng)計算過了所以沒必要再加了

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

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

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

最后需要說明的是,由于結果很可能出現(xiàn)溢出的情況献烦,所以在上面每一步操作中都應該對status(i,j)進行取模操作滓窍,而不應該等到最后。又因為在運算中涉及到減操作巩那,很可能得到負值導致結果不正確吏夯,所以在status(i,j)為負時應加上模使其變?yōu)檎敿毚a如下:

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];
    }
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末即横,一起剝皮案震驚了整個濱河市噪生,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌东囚,老刑警劉巖跺嗽,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舔庶,居然都是意外死亡抛蚁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門惕橙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钉跷,你說我怎么就攤上這事弥鹦。” “怎么了爷辙?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵彬坏,是天一觀的道長。 經(jīng)常有香客問我膝晾,道長栓始,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任血当,我火速辦了婚禮幻赚,結果婚禮上禀忆,老公的妹妹穿的比我還像新娘。我一直安慰自己落恼,他們只是感情好箩退,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佳谦,像睡著了一般戴涝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钻蔑,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天啥刻,我揣著相機與錄音,去河邊找鬼咪笑。 笑死可帽,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蒲肋。 我是一名探鬼主播蘑拯,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼兜粘!你這毒婦竟也來了申窘?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤孔轴,失蹤者是張志新(化名)和其女友劉穎剃法,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路鹰,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡贷洲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晋柱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片优构。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雁竞,靈堂內(nèi)的尸體忽然破棺而出钦椭,到底是詐尸還是另有隱情,我是刑警寧澤碑诉,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布彪腔,位于F島的核電站,受9級特大地震影響进栽,放射性物質發(fā)生泄漏德挣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一快毛、第九天 我趴在偏房一處隱蔽的房頂上張望格嗅。 院中可真熱鬧番挺,春花似錦、人聲如沸吗浩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懂扼。三九已至禁荸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阀湿,已是汗流浹背赶熟。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留陷嘴,地道東北人映砖。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像灾挨,于是被迫代替她去往敵國和親邑退。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

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