第一级:125、验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: “A man, a plan, a canal: Panama”
输出: true
直接双指针:过滤掉非字母和数字字符,然后判断首尾字符的小写或者大写是否一致即可。
def isPalindrome(self, s: str) -> bool:
low, high = 0, len(s)-1
while(low<high):
if not s[low].isalnum():
low+=1
continue
if not s[high].isalnum():
high-=1
continue
if s[low].lower()!=s[high].lower():
return False
low+=1
high-=1
return True
第一级之二:680、验证回文字符串2
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
本题的区别在于可以删除一个字符串,但是只能一次。因此步骤可以为:
1)使用双指针,当左边和右边的字符不一样时:
2)判断左边删除一个字符和右边删除一个字符哪个可以成功成为新的回文串,两者有一个即可。
3)如果一直左右指针一样,直接返回True 即可(即没有进入情况2)
代码如下:
class Solution(object):
def validPalindrome(self, s):
def ispalindrome(s, l, r):
while l<r:
if s[l]!=s[r]:
return False
l+=1
r-=1
return True
"""
:type s: str
:rtype: bool
"""
l, r = 0, len(s)-1
while l<r:#步骤1)
if s[l]==s[r]:
l+=1
r-=1
else:#此时进入判断2)
return ispalindrome(s, l+1, r) or ispalindrome(s, l, r-1)
return True
第一级之三:234、回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
划重点:如果不要求o(1)时间复杂度,可以直接
1)快慢指针寻找中间节点
2)将前半部分链表加入到一个stack中
3)stack弹出数据与中间节点之后的链表数据进行逐一对比,判断。
此时,时间和空间复杂度都是o(n),但是题目进阶要求空间复杂度为o(1)
因此,需要将步骤2)的栈进行替换,此时直观的想法是利用链表反转,等同于栈的作用。详情见代码:
class Solution(object):
def isPalindrome(self, head):
"""
:type head: ListNode
:rtype: bool
"""
#1、直观思路为利用快慢指针将寻找链表中间节点,同时将中间节点之前的值依次入栈,而后逐一出栈比较,复杂度为0(n)
#2、如果要求o(1),当找到中间位置后,对前或后的链表进行翻转,之后逐一进行对比,判断是否回文.
if head==None or head.next==None:
return True
slow, fast =head, head
pre, cur = ListNode(0), head
while fast and fast.next:
cur = slow#更新cur的位置
#快慢指针寻找中间位置
slow = slow.next
fast = fast.next.next
#直接在这半部分进行链表翻转,采用当前位置直接翻转的方法
cur.next = pre#将当前指针指向上一个指针,第一次为新建的指针
pre = cur#更新pre指针的位置
#判断是否为奇数个节点
if fast:
slow =slow.next #跳过中间的判断
#开始对比前后两半指针的值,此时前半部分的头指针为cur, 后半部分为slow
while cur and slow:
if cur.val!=slow.val:
return False
cur = cur.next
slow = slow.next
return True
第二级:9、回文数
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
偏数学技巧型:如果不要求不能转化为字符,则直接转化为字符进行首位指针判定即可。
对于只能针对数字处理:1、负数以及10的整数肯定返回false。需要注意判定10的整数时x%10,要注意0的情况。2、利用数学定理。12反过来为21。针对回文数,只要判定前半部分是否等于后半部分反过来即可。而数字在求前半部分时正好可以直接求后半部分反过来是啥。如:123321:1)x%10=1,x//10=12332,inverse=1。2)12332//10=1233; inverse10+2=12(21反过来)。3)1233//10=123;inverse10+3=123。此时123=123,返回True。因此,只需要判断一半数字即可,如何确定一半这个边界呢?当剩余的x<=inverse时:表明循环可以结束了,计算到一半了。但是,如果是非回文,如12345。x和inverse分别为:123,543,此时循环结束,最后判断x==inverse时,返回False,也没有问题。
def isPalindrome(self, x: int) -> bool:
if x ==0:
return True
if x< 0 or x%10==0:
return False
inverse = 0
while x>inverse:
inverse = inverse*10+x%10
x//=10
return x==inverse or x ==inverse//10
第三级:5、最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
思路:首先想到暴力法,遍历i, 遍历j, 之后判断i-j之间是否为回文子串。然后求取最大值即可。时间复杂度为n的三次方。
优化:采用中心扩散法,遍历i, 以i作为中心分别向两边扩散,求取回文边界,而后更新边界。其实也可以用动态规划和马拉车法,目前还没有掌握,后续有时间研究一下。
def ispalid(self, s, left, right):#用于求取从i扩散的边界
while left>=0 and right<len(s) and s[left]==s[right]:#满足当前相等时,往左右两边扩散
left-=1
right+=1
return left+1, right-1#退出循环时,left已经不等于right
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
#采用中心扩散法,对于每个位置,从两个位置分别判断。
n = len(s)
if n<=1:
return s
start, end =0,0 #用于记录最终结果的start以及end
for i in range(n):
#分别作为奇数回文、偶数回文的中心进行扩散
start1, end1 = self.ispalid(s, i, i)#返回的是满足回文的起始位置
start2, end2 = self.ispalid(s, i, i+1)#偶数的回文
if end1 - start1 > end -start:
start, end = start1, end1
if end2 - start2 > end - start:
start, end = start2, end2
return s[start:end+1]
第三级:647. 回文子串的个数
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
思路:也可以继续采用中心扩展法,不同的是不是求最长距离,而是有几个则加几个。那么在子函数ispalid中,每次while循环里(就是s[i]==s[j])时都加一个1进行计数,返回计数结果即可。
def countSubstrings(self, s: str) -> int:
###直接中心扩展法,有几个则加几个。中心扩展法注意奇数回文和偶数回文。
def ispalid(s, i, j):
count = 0
while i>=0 and j < len(s) and s[i] == s[j]:
i -= 1
j += 1
count += 1
return count
res = 0
for i in range(len(s)):
res += ispalid(s, i, i)
res += ispalid(s, i, i + 1)
return res