LeetCode 131. Palindrome Partitioning

這篇文章是 LeetCode 131. Palindrome Partitioning 的分析與解法。

問題描述

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab", Return

[
  ["aa","b"],
  ["a","a","b"]
]

這道題的意思就是將給定的字符串分成迴文串的組合,就像例子中所說,aab有兩種迴文串組合:aa,ba,a,b.

問題分析

對於這個問題,我們很簡單的將它分解爲兩個子問題:

  • 拆分字符串
  • 判斷一個字符串是否是迴文串

Step 1 判斷迴文字符串

如果一個字符串正讀和反讀結果都一樣,我們就說它是一個迴文字符串。判斷一個字符串是不是迴文的有很多種方法,我想起來 3 種方法,都會在接下來的文章中進行介紹,並給出源碼(文中的代碼皆爲 C++)。

反轉字符串法

這個方法是最容易理解的,將字符串反轉,如果和原來的字符串一樣,那麼它就是迴文的,這個方法在編碼上也是最簡單的:

bool isPalindrome_reverse(string s, int i, int j){
    string r = s;
    reverse(s.begin(),s.end());
    if(s.compare(r)!=0){
        return false;
    }
    return true;
}

雙指針法

雙指針法是通過兩個指針,一個指向字符串首,另一個指向字符串尾,如果兩個指針指向的字符相同,則兩個指針向中間移動,繼續判斷。

bool isPalindrome_doublepoints(string s, int i, int j){
    while(i < j){
        if(s[i] != s[j]){
            return false;
        }
        i++;
        j--;
    }
    return true;
}

遞歸法

遞歸法和雙指針法很類似,當前字符串是否迴文取決於首尾字符是否相同,然後遞歸的判斷除去首尾的剩餘字符串是否迴文。

bool isPalindrome_recursion(string s, int i,int j){
    if(i == j){
        return true;
    }
    else{
        if(s[i] == s[j]){
            i++;
            j--;
            if(i < j){
                return isPalindrome_recursion(s, i, j);
            }
            else{
                return true;
            }
        }
        else{
            return false;
        }
    }
}

Step 2 拆分字符串

這一步是這個問題的關鍵,解決拆分字符串的方案也有 2 種:暴力回溯法遞歸法

暴力回溯法

暴力回溯法比較好理解,它使用的是回溯法的思想,我們窮舉出來字符串的所有子串組合,然後判斷其中的子串是不是迴文的,去掉不符合要求的組合,剩餘的就是我們要的結果。

在進行窮舉的時候,如果遇到不是迴文的子串,我們就進行回溯。

以題目中的aab爲例:

實現代碼如下:

void backtrace(vector<vector<string>> &vec, vector<string> &temp, string s, int start){
    if(start == s.length()){
        vec.push_back(temp);
    }
    else{
        for(int i = start; i < s.length(); i++){
            if(isPalindrome(s, start, i)){
                temp.push_back(s.substr(start, i-start+1));
                backtrace(vec, temp, s, i+1);
                temp.pop_back();
            }
        }
    }
}

遞歸法

遞歸法的思路是把一個字符串分爲 A+B,如果 A 爲迴文則遞歸的求 B 的迴文組合,然後將 A 和 B 的迴文串組合做笛卡爾積。

以字符串 aabb 爲例:

  1. 將aabb 分爲 a+abb,然後求 abb 的迴文組合爲[a, b, b], [a, bb],所以做笛卡爾積後爲:[a, a, b,b ], [a, a, bb]
  2. 將字符串分爲 aa+bb,然後求 bb 的迴文組合爲[b, b], [bb],結果爲[aa, b, b], [aa, bb]
  3. 將字符串分爲 aab+b,aab 不迴文
  4. aabb 迴文,結果爲[aabb]
  5. 最終結果爲:[a, a, b,b ], [a, a, bb], [aa, b, b], [aa, bb], [aabb]

實現代碼如下:

vector<vector<string>> partition_recursion(string s){
    vector<vector<string>> vec;

    if(s.length() == 0){
        return vec;
    }

    if(isPalindrome_recursion(s, 0, s.length()-1)){
        vector<string> temp;
        temp.push_back(s);
        vec.push_back(temp);
    }

    for(int i = 1; i <= s.length(); i++){
        string left = s.substr(0, i);
        if(isPalindrome(left, 0, left.length()-1)){
            string right = s.substr(i, s.length()-i);
            vector<vector<string>> rightVec = partition_recursion(right);
            if(rightVec.size() > 0){
                for(int j = 0; j < rightVec.size(); j++){
                    vector<string> temp;
                    temp.push_back(left);
                    for(int x = 0; x < rightVec[j].size(); x++){
                        temp.push_back(rightVec[j][x]);
                    }
                    vec.push_back(temp);
                }
            }
        }
    }  
    return vec;
}

結果測試

將幾種方法組合後的測試結果如下:

反轉字符串法 雙指針法 遞歸法
暴力回溯法 16 ms 13 ms
遞歸法 89 ms 76 ms

我們看到回溯法要明顯優於遞歸的方法。

本文的完整代碼詳見我的 GitHub


本文的版權歸作者 羅遠航 所有,採用 Attribution-NonCommercial 3.0 License。任何人可以進行轉載、分享,但不可在未經允許的情況下用於商業用途;轉載請註明出處。感謝配合!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章