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;          }      }    };