[Leetcode] Shortest Palindrome 最短迴文拼接法

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:segmentfault

Given "aacecaaa", return "aaacecaaa".ui

Given "abcd", return "dcbabcd".this

KMP算法

複雜度

時間 O(logN) 空間 O(H).net

思路

這題要用到部分KMP算法的知識,能夠先參考實現StrStr這篇文章。
這題的技巧性很是強,咱們觀察一下abb這個字符串,將其反轉後獲得bba,若是隻是想獲得迴文串,那把這個反轉的字符串放在前面獲得bbaabb就確定是了。但這明顯不是最長的,咱們再展現一個技巧,將該反轉字符串放在後面再觀察一下abbbba,它的前綴和後綴的狀況有這些:指針

a          a
ab        ba
abb      bba
abbb    bbba
...     ...

顯然,這個合併的字符串長度最長的相同先後綴是a,這時候咱們把反轉後的字符串bba中最後那個a去掉,獲得bb,這時候再把bb接到原字符串前面,獲得bbabb,這就是最短的迴文拼接方法了!code

再用一個例子,好比aaba,翻轉後獲得abaa,而後拼接起來獲得aabaabaa,其最長公共先後綴是aa,去掉這個後綴的反轉字符串是ab,再接到原字符串上就是abaaba,即最短的迴文拼接。orm

那如何求這個最長公共先後綴有多長呢?這裏就要借用KMP算法中的partial match table了,對於剛纔的aaba,其鏈接後爲aabaabaa,它的表是:rem

a a b a a b a a
0 1 0 1 2 3 1 2

第一個a是0,由於沒有區分先後綴。第二個a是1,由於在第2個位置,能夠有最長爲1的相同先後綴(a),依次類推。這樣咱們只要知道最後一個字母對應的數,就是這個字符串的最長公共先後綴長度了。那如何求這個表lps[]呢?咱們用i表示其前綴的匹配位置,用j表示後綴的匹配位置。而後咱們從i=1,j=0,i表示後綴匹配到的位置,j表示前綴匹配到得位置,開始依次向後尋找前綴和後綴的匹配狀況。匹配時分爲幾種狀況:字符串

  1. 字母相同,則i和j都加1,且lps[i] = j + 1,由於後綴匹配的長度是前綴的長度加1。前綴爲j的已經匹配了,如今多一個不就是再加1嗎。

  2. 字母不相同,且j還不是0時,能夠將j回退至lps[j-1],看看上一個子前綴到哪裏結束的,從那裏從新匹配。

  3. 若是字母不相同,且j是0,已經沒法回退,則說明lps[i]也是0了,根本沒法匹配的意思,同時i也要加1,開始看下一個字母了。

這裏狀況2可能屢次重複,直到進入了狀況3。還不懂的能夠看這個文章

獲得這個表後,咱們把反轉字符串除去相應的後綴,而後加到前面就好了。

注意

  • 爲了方便處理空字符串,咱們在反轉拼接的時候中間加了#,這個字符要保證不會出如今字符串中。

  • 在計算表時,當先後綴指針指向字符相同時,lps[i] = j + 1而不是lps[i] = lps[i - 1] + 1; 當j退回0時,lps[i] = 0;

代碼

public class Solution {
    public String shortestPalindrome(String s) {
        // 將字符串反轉後拼接到後面
        String rev = (new StringBuilder(s)).reverse().toString();
        String combine = s + "#" + rev;
        // 計算LPS表值
        int[] lps = new int[combine.length()];
        getLPS(combine, lps);
        int remove = lps[lps.length-1];
        // 去掉後綴後,將反轉字符串拼回前面
        String prepend = rev.substring(0, rev.length() - remove);
        return prepend + s;
    }
    
    private void getLPS(String s, int[] lps){
        // i是後綴末尾的指針,j是前綴末尾的指針
        int j = 0, i = 1;
        lps[0] = 0;
        // 從j=0,i=1開始找,錯開了一位
        while(i < s.length()){
            // 若是字母相等,則繼續匹配,最長相同先後綴的長度也加1
            if(s.charAt(i) == s.charAt(j)){
                lps[i] = j + 1;
                i++;
                j++;
            // 若是不一樣
            } else {
                // 若是前綴末尾指針還沒退回0點,則找上一個子前綴的末尾位置
                if(j != 0){
                    j = lps[j - 1];
                // 若是退回0點,則最長相同先後綴的長度就是0了
                } else {
                    lps[i] = 0;
                    i++;
                }
            }
        }
    }
}
相關文章
相關標籤/搜索