提一个非常简单的问题:如何验证回文串。
最先想到的是,把字符串反转,然后和原字符串比较。
那么顺势总结一下字符串反转的方法。
有的小伙伴可能会想到reverse()方法。但要注意,reverse()是列表才有的方法,用于字符串会报错。当然,把字符串转换成列表就可以:
ls = list(mystr)
ls.reverse()
mystr1 = ''.join(ls)
不过,这又是何必呢。
内置函数reversed()可以用于各种序列,包括元组、列表、字符串、range等。要注意的是,它返回的是迭代器。
mystr1 = ''.join(list(reversed(mystr)))
这未免还是不太方便。
别忘了,python有切片大法!
mystr1 = mystr[::-1]
简洁!优美!得劲!
于是验证回文串的函数完成了:
# 判断回文串
def isp(s):
return s == s[::-1]
这个方法好是好,但它仍然不可避免地需要完成一次字符串反转。我的理解是,它不得不进行一次对字符串的完整遍历。
于是,以“双指针”为噱头的解法出现了。
说得这么玄乎,其实道理很简单:
- 左右两个指针分别指向字符串两端的字符;
- 不断地将两个指针相向移动,每次移动一步,并判断它们指向的字符是否相同,如果相同就继续移动一步,如果不同就说明不是回文串;
- 当两个指针相遇时,就说明是回文串。
这个思路,大致有两种写法:
# 判断回文串
def isp1(s):
length = len(s)
for i in range(length//2):
if s[i]!=s[length-i-1]:
return False
return True
# 判断回文串
def isp2(s):
left, right = 0,len(s)-1
while left < right:
if s[left] != s[right]:
return False
left, right = left + 1, right - 1
return True
我原以为不涉及字符串反转的双指针法会快一些,然而事实是,经测试,上述两种代码的运行速度都显著低于字符串反转。
所以说,还是切片厉害啊。
回到leetcode原题目:给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,忽略字母的大小写。
最开始,我立刻把题目拆解成两部分:处理字符串和验证回文串。而官方标答给出了一种骚操作:不事先处理字符串,直接在原始字符串上进行比较。
如果不对字符串做提前处理,压力就转移到遍历时的每个字符身上,要先判断该字符是否为字母或数字,然后统一字母大小写。
思路本身是简单的,要读懂代码需要稍微转一下脑子,注意看注释:
# 判断回文串,官方标答
def isPalindrome(s):
left, right = 0, len(s) - 1
while left < right:
# 左指针不断地向右移动,直到遇到一个字母或数字字符,或者两指针重合
while left < right and not s[left].isalnum():
left += 1
# 右指针不断地向左移动,直到遇到一个字母或数字字符,或者两指针重合
while left < right and not s[right].isalnum():
right -= 1
if left < right:
if s[left].lower() != s[right].lower():
return False
left, right = left + 1, right - 1
return True
经测试,这个方法在时间上没有任何优势,但占用的内存空间减小了,说明它具有较小的空间复杂度。
不过,像我这种菜鸟,能考虑好时间复杂度就不错了!
参考资料:
https://leetcode-cn.com/problems/valid-palindrome/solution/yan-zheng-hui-wen-chuan-by-leetcode-solution/