1. 程式人生 > >[LeetCode] Shortest Palindrome 最短迴文串

[LeetCode] Shortest Palindrome 最短迴文串

Given a string S, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.

For example:

Given "aacecaaa", return "aaacecaaa".

Given "abcd", return "dcbabcd".

Credits:
Special thanks to

@ifanchu for adding this problem and creating all test cases. Thanks to @Freezen for additional test cases.

這道題讓我們求最短的迴文串,LeetCode中關於迴文串的其他的題目有 Palindrome Number 驗證迴文數字 Validate Palindrome 驗證迴文字串 Palindrome Partitioning 拆分迴文串Palindrome Partitioning II 拆分迴文串之二 Longest Palindromic Substring 最長迴文串

。題目讓我們在給定字串s的前面加上最少個字元,使之變成迴文串,那麼我們來看題目中給的兩個例子,最壞的情況下是s中沒有相同的字元,那麼最小需要新增字元的個數為s.size() - 1個,第一個例子的字串包含一個迴文串,只需再在前面新增一個字元即可,還有一點需要注意的是,前面新增的字串都是從s的末尾開始,一位一位往前新增的,那麼我們只需要知道從s末尾開始需要新增到前面的個數。這道題如果用brute force無法通過OJ,所以我們需要用一些比較巧妙的方法來解。這裡我們用到了KMP演算法,KMP演算法是一種專門用來匹配字串的高效的演算法,具體方法可以參見這篇博文從頭到尾徹底理解KMP。我們把s和其轉置r連線起來,中間加上一個其他字元,形成一個新的字串t,我們還需要一個和t長度相同的一位陣列next,其中next[i]表示從t[i]到開頭的子串的相同字首字尾的個數,具體可參考KMP演算法中解釋。最後我們把不相同的個數對應的字串新增到s之前即可,程式碼如下:

C++ 解法一:

class Solution {
public:
    string shortestPalindrome(string s) {
        string r = s;
        reverse(r.begin(), r.end());
        string t = s + "#" + r;
        vector<int> next(t.size(), 0);
        for (int i = 1; i < t.size(); ++i) {
            int j = next[i - 1];
            while (j > 0 && t[i] != t[j]) j = next[j - 1];
            next[i] = (j += t[i] == t[j]);
        }
        return r.substr(0, s.size() - next.back()) + s;
    }
};

Java 解法一:

public class Solution {
    public String shortestPalindrome(String s) {
        String r = new StringBuilder(s).reverse().toString();
        String t = s + "#" + r;
        int[] next = new int[t.length()];
        for (int i = 1; i < t.length(); ++i) {
            int j = next[i - 1];
            while (j > 0 && t.charAt(i) != t.charAt(j)) j = next[j - 1];
            j += (t.charAt(i) == t.charAt(j)) ? 1 : 0;
            next[i] = j;
        }
        return r.substring(0, s.length() - next[t.length() - 1]) + s;
    }
}

從上面的Java和C++的程式碼中,我們可以看出來C++和Java在使用雙等號上的明顯的不同,感覺Java對於雙等號對使用更加苛刻一些,比如Java中的雙等號只對primitive類資料結構(比如int, char等)有效,但是即便有效,也不能將結果直接當1或者0來用。而對於一些從Object派生出來的類,比如Integer或者String等,不能直接用雙等號來比較,而是要用其自帶的equals()函式來比較,因為雙等號判斷的是不是同一個物件,而不是他們所表示的值是否相同。同樣需要注意的是,Stack的peek()函式取出的也是物件,不能直接和另一個Stack的peek()取出的物件直接雙等,而是使用equals或者先將其中一個強行轉換成primitive類,再和另一個強行比較。

下面這種方法的寫法比較簡潔,雖然不是明顯的KMP演算法,但是也有其的影子在裡面,首先我們還是先將其的翻轉字串搞出來,然後比較原字串s的字首後翻轉字串t的對應位置的字尾是否相等,起始位置是比較s和t是否相等,如果相等,說明s本身就是迴文串,不用新增任何字元,直接返回即可;如果不想等,s去掉最後一位,t去掉第一位,繼續比較,以此類推直至有相等,或者迴圈結束,這樣我們就能將兩個字串在正確的位置拼接起來了。很有意思的是,這種方法對應Java寫法卻會TLE,無法通過OJ。

C++ 解法二:

class Solution {
public:
    string shortestPalindrome(string s) {
        string t = s;
        reverse(t.begin(), t.end());
        int n = s.size(), i = 0;
        for (i = n; i >= 0; --i) {
            if (s.substr(0, i) == t.substr(n - i)) {
                break;
            }
        }
        return t.substr(0, n - i) + s;
    }
};

下面這種Java寫法也是在找相同的字首字尾,但是並沒有每次把字首字尾取出來比較,而是用兩個指標分別指向對應的位置比較,然後用end指向相同字尾的起始位置,最後再根據end的值來拼接兩個字串。有意思的是這種方法對應的C++寫法會TLE,跟上面正好相反,那麼我們是否能得出Java的substring操作略慢,而C++的reverse略慢呢,我也僅僅是猜測而已。

Java 解法三:

public class Solution {
    public String shortestPalindrome(String s) {
        int i = 0, end = s.length() - 1, j = end;
        char arr = s.toCharArray();
        while (i < j) {
            if (arr[i] == arr[j]) {
                ++i; --j;
            } else {
                i = 0; --end; j = end;
            }
        }
        return new StringBuilder(s.substring(end + 1)).reverse().toString() + s;
    }
}

參考資料: