LeetCode 214. Shortest Palindrome(回文串,回文树,KMP)

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

猜你喜欢

转载自www.cnblogs.com/dacc123/p/12292223.html