LeetCode 214. Shortest Palindrome(迴文串,迴文樹,KMP)

  • 2020 年 2 月 18 日
  • 筆記

https://leetcode.com/problems/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.

Example 1:

Input: "aacecaaa" Output: "aaacecaaa" Example 2:

Input: "abcd" Output: "dcbabcd"

題意:給你一個字元串,讓你在這個字元串前面加入最少的字元,讓整個字元串變成一個迴文串。

題解:迴文串,讓人第一聯想到的是什麼呢?我聯想的第一個是動態規劃。但是字元串長度超過5萬,怕是要超時。接著我聯想到了迴文樹。這是個解決迴文串問題的神器。最後看題解,也可以用KMP解決。

首先,根據題目的意思,我們找一個從左邊第一個字元開始的最長迴文串。首先用迴文樹怎麼解決呢?簡直是殺雞用屠龍刀!

輸入一個字元串,建迴文樹。我們能計算那些問題呢?我輕輕列舉一下,字元串中有多少個不同的迴文串,字元串中迴文串的總數,字元串中不同迴文串的數量,字元串從某個字元開始的最長迴文串長度,字元串從某個字元結束的最長迴文串長度,字元串從某個字元開始的迴文串個數,字元串從某個字元串結束的迴文串個數,字元串中奇數迴文串的數量,字元串中偶數迴文串的數量…………

所以這道題目,用字元串從某個字元開始的最長迴文串長度,可以解決。

接下來簡單介紹一個迴文樹:迴文樹是由兩顆樹組成,一棵樹存儲偶數串,一棵樹存儲奇數串,每個節點都是一種迴文串。節點之間的邊有兩種,next[p][c] 表示節點p上的迴文串兩端加上c字元後形成的新迴文串所在節點;fail[p] 表示節點p上的迴文串的最長後綴迴文串所在的節點。

以上是整個樹的架構。 此外節點上還有別的值:該迴文串的數量,該迴文串後綴迴文串的數量。

迴文樹:

#define MAX 50005    struct Tree          {                  char s[MAX];          int cnt[MAX];          int num[MAX];          int next[MAX][26];          int fail[MAX];          int len[MAX];          int p;          int last;          int n;            int newNode(int x)          {              memset(next[p],0,sizeof(next[p]));              len[p]=x;              cnt[p]=0;              num[p]=0;              return p++;          }            void init()          {              p=0;              n=0;              last=0;              newNode(0);              newNode(-1);              fail[0]=1;              fail[1]=0;              s[0]=-1;          }            int GetFail(int x)          {              while(s[n-len[x]-1]!=s[n])                  x=fail[x];              return x;          }            void Add(int x)          {              x -= 'a';              s[++n] = x;              int cur = GetFail(last);                if(!(last=next[cur][x]))              {                  int now = newNode(len[cur]+2);                    fail[now]=next[GetFail(fail[cur])][x];                    next[cur][x]= now;                    num[now] = num[fail[now]]+1;                    last = now;              }              cnt[last]++;          }            void Count()          {              for(int i=p-1;i>=0;i--)              {                  cnt[fail[i]]+=cnt[i];              }          }              };    class Solution {  public:      string shortestPalindrome(string s) {            Tree tree;          tree.init();          for(int i=s.length()-1;i>=0;i--)          {              tree.Add(s[i]);          }            tree.Count();            int len = tree.len[tree.last];            string ans="";            for(int i=s.length()-1;i>=len;i--)          {              ans+=s[i];          }            ans+=s;            return ans;        }  };

KMP

這道題目還可以用KMP做,是不是感覺很厲害? 這個思路最最最關鍵的地方,就是逆轉思維!不知道逆轉裁判你們有沒有玩過。

把字元串S 逆轉成S1 ,然後把Str = S + "." +S1 拼接起來,這樣我們要求的S左起最長迴文串,就等於Str的最長公共前後綴!是不是要逆轉思維,才能想到?

最長公共前後綴是個啥?就是KMP,就是Next數組

class Solution {  public:      int next[MAX*2];      string shortestPalindrome(string s) {            string s2="";          for(int i=s.length()-1;i>=0;i--)          {              s2+=s[i];          }            string str=s+"."+s2;            GetNext(str);            int len = next[str.length()-1];            string ans="";            for(int i=s.length()-1;i>=len;i--)          {              ans+=s[i];          }            ans+=s;            return ans;        }        void GetNext(string s)      {          next[0]=0;          for(int i=1;i<s.length();i++)          {              int p = next[i-1];              while(p>0&&s[i]!=s[p])              {                   p=next[p-1];              }                if(s[i]==s[p])              {                  next[i]=p+1;              }              else                  next[i]=0;          }      }    };