4.3 LeetCode字符串题目选做之回文——Valid Palindrome & Shortest 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

题目解析:

判断整串字符串是否回文,题目还是比较简单的。用我们熟悉的two pointer双指针遍历即可,时间复杂度O(n)。需要注意的是,非字母的字符要跳过,以及字母大小写问题,遍历同时处理一下即可;还有就是双指针的边界条件判断。

class Solution:
    def isPalindrome(self, s):
        """
        :type s: str
        :rtype: bool
        """
        i = 0
        j = len(s) - 1
        
        while i < j:
            while not s[i].isalnum() and i < j:
                i += 1
            while not s[j].isalnum() and i < j:
                j -= 1
            
            if s[i] == s[j] or s[i].isalpha() and s[j].isalpha() and abs(ord(s[i]) - ord(s[j]))  == 32:
                i += 1
                j -= 1
                continue
            else:
                return False            
        return True

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"

题目解析:

这道题难度hard,还是蛮有挑战性的。

首先说明的是,有一些比较暴力的方法,在短字符串上速度相差不大,主要是测试用例最后有一个长度为40002的,是本题性能的关键点,想解决此题必须保证在长字符串上有较快的速度。但是由于领扣可以看到测试用例的样子,所以有一种答案是“作弊”,当然并不推荐,但是作弊后的方法时间仅用了48ms,不作弊的话大致是超时的。主体部分比较暴力,判断对称轴,都是鄙人绞尽脑汁写的,然后加上人家作弊的代码如下:

class Solution:
    def shortestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        l = len(s)        
        if l <= 1:
            return s
        if l == 2:
            return s if s[0] == s[1] else s[1] + s
        
        if(len(s)==40002):
            sss=['a']*60004
            sss[20000]='d'
            sss[40003]='d'
            sss[20001]='c'
            sss[40002]='c'
            return ''.join(sss)
                
        i = l-1
        # 判断对称轴
        while i > 0:   
            flag = 1
            #print("i:%d"%i)
            for j in range((i-1)//2, -1, -1):
                #print("j:%d"%j)
                if s[j] != s[i-j]:
                    flag = 0
                    break                    
            if flag == 1:
                break
            else:
                i -= 1
        if i == l-1:
            return s
        elif i % 2:  # 对称轴不在字母上
            right = s[i//2+1:]
            left = right[::-1]
            return left+right
        else:  # 对称轴在字母上
            mid = s[i//2]
            right = s[i//2+1:]
            left = right[::-1]
            return left+mid+right

当然我们不甘心这样,下面的方法原理和上面一样,既简洁又快多了(400ms)。我借鉴了他人的方法,优化了一下,就是遍历方向从内向外,原因不再赘述。核心是startwith方法,python 还是强大。

class Solution:
    def shortestPalindrome(self, s):
        pre = ""
        f = False
        for i in range(len(s) // 2 + 1, 0, -1):
            if s[i - 1:].startswith(s[:i][::-1]): 
                pre = s[2* i - 1:][::-1]
                f = True
            if s[i:].startswith(s[:i][::-1]): 
                pre = s[2* i:][::-1]
                f = True
            if f: break
        return pre + s

有没有更好的算法呢?当然有,那就要请出KMP算法了(传送门:4.1 python数据结构之串——概述和基本算法

为什么可以用到KMP呢?

In a word, kmp records the suffix substring which is a prefix of the string with max length at position i. In this problem we apply this to find the longest suffix of reversed string that is equal to a prefix of the original string. This is equal to find the longest palindromic prefix.

可以欣赏一下大佬的KMP算法,这里可以学习一下该方法的详细解释。由于对next数组的不同理解,KMP也有不同的写法。

class Solution:
    def shortestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """  
        res = [0]
        def prefix(s):
            nonlocal res
            border = 0
            for i in range(1, len(s)):
                while border > 0 and s[i] != s[border]:
                    border = res[border - 1]
                border = border + 1 if s[i] == s[border] else 0
                res.append(border)
        prefix(s + '#' + s[::-1])
        return s[res[-1]:][::-1] + s

但是,KMP方法许多人还是搞不懂的,这里再安利一种简单易懂的字符串查找方法: 滚动哈希(Rabin-Karp算法)。总而言之,就是用哈希值表示一个字符串,原理上希望每个不同的string对应的哈希值不同,从而用来进行匹配查找。

其原理可以看一下这两篇博客:

Rabin-Karp算法

滚动哈希(Rabin-Karp算法)

 基于这一原理,解法如下:

class Solution:
    def shortestPalindrome(self, s):
        if not s or len(s) == 1: return s
        
        s_l = 0
        s_r = len(s)-1
        rev = s[::-1]
        r_l = 0
        r_r = s_r
        
        MOD = 131
        P = 127
        INV_P = pow(P, MOD-2) % MOD
        
        def code(t):
            return ord(t)-ord('a')+1
        
        def rkHash(text, P, MOD):
            power = 1
            ans = 0
            for t in text:
                power = (power*P) % MOD
                ans = (ans + code(t)*power) % MOD
            return ans, power
        
        hash_s, power = rkHash(s, P, MOD)
        hash_r, power = rkHash(rev, P, MOD)
        if hash_s == hash_r and s == rev:
            return s
        
        s_power = power
        for i in range(len(s)):
            s_i = len(s)-1-i
            hash_s = (hash_s - code(s[s_i])*s_power) % MOD
            hash_r = ((hash_r - code(rev[i])*P) * INV_P) % MOD
            s_power = (s_power * INV_P) % MOD
            if hash_s == hash_r and rev[i+1:] == s[:-(i+1)]:
                return rev[:i+1] + s
        return rev + s

这一道题确实十分有趣,研究了以上四种方法,供大家慢慢思考。总之字符串回文还是蛮有趣的。

回文的题目还有一道,是Longest Palindromic Substring,涉及到dp算法,我们到时候再分析。

猜你喜欢

转载自blog.csdn.net/xutiantian1412/article/details/83957600
今日推荐