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
".uiGiven "
abcd
", return "dcbabcd
".this
時間 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表示前綴匹配到得位置,開始依次向後尋找前綴和後綴的匹配狀況。匹配時分爲幾種狀況:字符串
字母相同,則i和j都加1,且lps[i] = j + 1
,由於後綴匹配的長度是前綴的長度加1。前綴爲j的已經匹配了,如今多一個不就是再加1嗎。
字母不相同,且j還不是0時,能夠將j回退至lps[j-1]
,看看上一個子前綴到哪裏結束的,從那裏從新匹配。
若是字母不相同,且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++; } } } } }