就字符串回文的几道题,看看这类问题的解决方案。
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对应的哈希值不同,从而用来进行匹配查找。
其原理可以看一下这两篇博客:
基于这一原理,解法如下:
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算法,我们到时候再分析。