Leetcode进阶之路——Palindrome

本文介绍三道跟Palindrome相关的题目。
首先是125. Valid Palindrome

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
Note: For the purpose of this problem, we define empty string as valid palindrome.
Example 1:
Input: “A man, a plan, a canal: Panama”
Output: true


Example 2:
Input: “race a car”
Output: false

大意为,只考虑字母和数字,且忽略大小写,判断所给的input是否为回文字符串。
那么思路就很简单了:
先遍历整个字符串,将其保存为只有大(或小)写字母和数字的字符串,然后对这个转换得到的字符串判断是否为回文

class Solution {
public:
    bool isPalindrome(string s) {
        string res = "";
        for(int i = 0; i < s.length(); ++i)
        {
            if(s[i] > 64 && s[i] < 91)
            {
                //转为小写字符
                res += (s[i] + 32);
            }
            else if(s[i] > 96 && s[i] < 123)
                res += s[i];
            else if(s[i] > 47 && s[i] < 58)
                res += s[i];
        }
        int len = res.length();
        //判断是否为回文,只遍历一半即可
        for(int i = 0; i < len / 2; ++i)
            if(res[i] != res[len - 1 - i])
                return false;
        return true;
    }
};

然后680. Valid Palindrome II

Given a non-empty string s, you may delete at most one character. Judge whether you can make it a palindrome.
Example 1:
Input: “aba”
Output: True


Example 2:
Input: “abca”
Output: True
Explanation: You could delete the character ‘c’.

题意:给定一个字符串,判断在至多删除一个字符时,是否为回文串。
最开始会想到的方法很简单,一个字符一个字符遍历,看删除这个字符后,剩下的字符串是否为回文串。出题者很显然不想让我们这么做,会TLE,因此想到另一种方法:
如果是回文串,那么头尾的对应位置一定是相同的
因此分别比较头尾的对应位置,一旦发现某个位置两个字符不相等,则必定删除其中之一,只需判断这两种情况下得到的字符串是否回文,若有一个回文,则返回true,否则为false
题外话:删除字符串中某个位置的字符,我这里用了substr方法,假如有个字符串string s = "fdajwel";
要删除字符a,它在s[2],则可以这么写:s = s.substr(0, 2) + s.substr(3)
该函数原型为string substr (size_t pos = 0, size_t len = npos) const;
size_t pos为起始位置,size_t len为长度,若省略长度字段,则表示从第pos位置起,一直到字符串末尾

class Solution {
public:
    //判断是否为回文串
    bool isValid(string s)
    {
        int len = s.length();
        for (int i = 0; i < len / 2; ++i)
            if (s[i] != s[len - 1 - i])
                return false;
        return true;
    }

    bool validPalindrome(string s) {
        if (isValid(s)) return true;
        int len = s.length();
        //若长度为2,肯定可以通过删除一个字符为回文
        if (len == 0 || len == 2) return true;
        if (s[0] != s[len - 1])
        {
            if (isValid(s.substr(1)) || isValid(s.substr(0, len - 1)))
                return true;
            return false;
        }
        for (int i = 0; i < len / 2; ++i)
        {
            if (s[i] != s[len - 1 - i])
            {
                //删除第i个字符
                if (isValid(s.substr(0, i) + s.substr(i + 1))) return true;
                //删除第len - 1 - i个字符
                if (isValid(s.substr(0, len - 1 - i) + s.substr(len - i))) return true;
                //若两者均不为true,直接返回false
                return false;
            }
        }
        return false;
    }
};

最后一道214. 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”

题意,给定一个字符串,在字符串前面添加尽可能少的字符,使其变为回文串
注意一点,这里是在前面添加,因此对于aabba,最短的回文串是abbaabba,虽然如果允许在后面加的话,应该是aabbaa,但我不知道题目故意这么出还是有什么别的用意,因此下面的代码也没考虑后面加字符。
首先是暴力解法。

class Solution {
public:
    bool isPalindrome(string s)
    {
        int len = s.length();
        for (int i = 0; i < len / 2; ++i)
        {
            if (s[i] != s[len - 1 - i])
                return false;
        }
        return true;
    }

    string shortestPalindrome(string s) {
        int len = s.length();
        if (len == 0 || len == 1) return s;
        int pos1 = 0;
        char c = s[0];
        for (int j = len - 1; j >= 0; --j)
        {
            if (s[j] == c)
            {
                if (isPalindrome(s.substr(0, j + 1)))
                {
                    pos1 = j;
                    break;
                }
            }
        }
        string ss = s.substr(pos1 + 1);
        reverse(ss.begin(), ss.end());
        return ss + s;
    }
};

首先,如果是回文串,那么原串的第一个字符一定在回文串中,因此先找到原串中,以第一个字符为起点的最长的回文串,之后将剩下的字符倒转放在原串前即可。
例如对于abbaa,发现以第一个字符a开头最长的回文串是abba,还剩末尾的a,则将其放在开头得到aabbaa
当然,这道题被赋予Hard难度,也不能这么水过…这种方法耗时200ms,看了别人那么多<50的也是很心动,因此接下来介绍KMP算法
网上已经有很多相关教程(贴个链接: 如何更好的理解和掌握KMP算法,觉得比那些所谓详解更易理解,特别是PMT和next指针),大多是用于匹配两个字符串的,乍一看好像跟我们这里不相关,那么这里先把前缀和后缀的概念引入,来解这道题:
仍旧是abbaa,我们知道它最长的回文串是aabbaabbaa(倒转串+原串),然后分别计算原串和倒转串的前缀和后缀:

原串 前缀 倒转串 后缀
abbaa a, ab, abb, abba aabba a, ba, bba, abba

因此公共的最大子串是abba,长度为4,倒转串长度为5,因此从倒转串中截取5-4=1的字符放入原串开头,即得到aabbaa
那么再返回KMP中:
KMP算法原先是用来匹配两个字符串的,比如在aabbabcdabbabde 中查找abbabd,第二次朴素比较:
aabbabcdabbabde
  abbabd
这时候发现,原串第七个字符c和目标串第六个字符d不匹配,朴素的办法是,把原串上的指针i往后移动一位至b,目标串的指针回到第一位a,重新新一轮的比较,而KMP则采用另一种思路:我去找已匹配的字符串中,前缀和后缀相同长度最长的位置。
例如已匹配完的字符串是abbab,后缀有字符串ab,前缀也有字符串ab,那么我们就可以知道下一次比较,其余部分不需要再比较了,下一轮比较应该是:
aabbabcdabbabde
        abbabd
从代码上讲,某个字符串加上一个新字符后的公共前后缀长度,一定与该字符前一个字符的长度相关:要么长度不变(例如aaceaa和aaceaaa),要么长度加一(例如aacea和aaceaa),要么长度置零(例如aacea和aacead)
于是结合这道题,只要把前缀和后缀相同的部分剔除,保留不相同的那部分即可,就有了下面的代码:

class Solution {
public:
    string shortestPalindrome(string s) {
        string ts = s;
        reverse(ts.begin(), ts.end());
        string str = s + "#" + ts;
        vector<int> v(str.length(), 0);
        for (int i = 1; i < str.length(); ++i)
        {
            int j = v[i - 1];
            //j最少为0,这里是当发生不匹配的时候,回溯的过程,如上面abbabd的例子,回溯到ab
            while (j > 0 && str[i] != str[j])
                j = v[j - 1];
            //决定了接下来是否加1
            if (str[i] == str[j])
                j++;
            v[i] = j;
        }
        //v[str.length() - 1]是公共前后缀的最大长度,用上面的方法从倒转串中减去响应长度即可
        int len = s.length() - v[str.length() - 1];
        return ts.substr(0, len) + s;
    }
};

上面的代码中加了’#’,是为了防止例如aaa这样的字符串,如果不加分隔符,会影响最后的结果。
然后就贴一张运行时间图(中间的TLE只怪自己又作妖了= =):
运行截图
可以看到加快了不止一点。

猜你喜欢

转载自blog.csdn.net/u013700358/article/details/81083811