leecode题型整理之双指针
Posted WXiujie123456
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leecode题型整理之双指针相关的知识,希望对你有一定的参考价值。
目录
- 双指针适用的题型的特点
- leetcode原题
- 167. [两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/)
- [633. 平方数之和](https://leetcode-cn.com/problems/sum-of-square-numbers/)
- [345. 反转字符串中的元音字母](https://leetcode-cn.com/problems/reverse-vowels-of-a-string/)
- [680. 验证回文字符串 Ⅱ](https://leetcode-cn.com/problems/valid-palindrome-ii/)
- [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)
- [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/)
- [524. 通过删除字母匹配到字典里最长单词](https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/)
双指针适用的题型的特点
双指针问题一般应用于字符串、列表等能直接访问的数据结构,且有序。让两个指针从这个数据结构开头和结尾分别遍历,使之满足条件。
或用于链表,指针速度不一。
leetcode原题
167. 两数之和 II - 输入有序数组
class Solution(object):
def twoSum(self, numbers, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
#暴力,超出时间限制
# n = len(numbers)
# for i in range(n):
# for j in range(i,n):
# # print(j)
# if numbers[i] + numbers[j] == target and i != j:
# return [i+1,j+1]
#对撞指针,和target比较,大了右指针调小,小了左指针调大
n = len(numbers)
i = 0
j = n-1
while i < j:
if numbers[i] + numbers[j] > target:
j -= 1
elif numbers[i] + numbers[j] < target:
i += 1
elif numbers[j] == numbers[j-1] and (j-1) != i:
j -= 1
else:
return [i+1,j+1]
633. 平方数之和
给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c
。
class Solution(object):
def judgeSquareSum(self, c):
"""
:type c: int
:rtype: bool
"""
i = 0
j = int(sqrt(c))
# print(j)
while i <= j:
if i * i + j * j == c:
# print(i,j)
return True
elif i * i + j * j < c:
i += 1
else:
j -= 1
return False
345. 反转字符串中的元音字母
class Solution(object):
def reverseVowels(self, s):
"""
:type s: str
:rtype: str
"""
#字符串不可修改
# str = ['a','e','i','o','u','A','E','I','O','U']
str = 'aeiouAEIOU'
n = len(s)
ans = ['*'] * n
i = 0
j = n - 1
while i <= j:
if s[i] in str and s[j] in str:
# print('交换')
ans[j] = s[i]
ans[i] = s[j]
i += 1
j -= 1
elif s[i] not in str:
ans[i] = s[i]
i += 1
# print('i++')
elif s[j] not in str:
ans[j] = s[j]
j -= 1
# print('j--')
#列表转化成字符串的方法
ans_str = ''.join(ans)
return ans_str
本题中需要注意两个易错点:
- 字符串不能修改,故可以借助灵活的列表进行操作
- 列表转化成字符串的方法
ans_str = ''.join(ans)
680. 验证回文字符串 Ⅱ
给定一个非空字符串 s
,最多删除一个字符。判断是否能成为回文字符串。
自己最初的逻辑:
- 若两端相同,则指针往中间移动
- 两端不同时,若小下标这边的后一个元素与大下标相同,则小下表右移,且标志位置为false 模拟删除一位的操作 且仅能删除一位
- 两端不同时,若大下标这边的前一个元素与小下标相同,则大下表左移,且标志位置为false 模拟删除一位的操作 且仅能删除一位
自己写的版本的代码如下:
n = len(s)
i = 0
j = n-1
print(n)
flag = True #标记位,只删除一位
while i < j:
if s[i] == s[j]:
i += 1
j -= 1
elif s[j-1] == s[i] and flag == True:#s[i+1] == s[j] and flag == True:
# i += 1
j -= 1
print(i)
flag = False
elif s[i+1] == s[j] and flag == True:#s[j-1] == s[i] and flag == True:
i += 1
# j -= 1
print(j)
flag = False
else:
return False
return True
但是形如:aucupuumuupuca
和acpuumuupucua
,同时满足“若小下标这边的后一个元素与大下标相同”和‘大下标这边的前一个元素与小下标相同’这两种情况,大小指针哪个移动成为了问题。
和我对象交流之后,参考他的想法,不相同时,删左边一个,继续比较,若还有不相同的,退回去删右边一个,若继续后还有不相同,就说明不是回文,能够得出答案。
然后我就开始按照他的想法,设计自己的思路(太艰难了,o(╥﹏╥)o),思路如下:
- 若两端相同,则指针往中间移动,输出TRUE
- 若两端不同,则有以下情况:
- 若s[i+1]!=s[j]且s[i]!=s[j+1],则删掉一位也不满足回文,返回False
- 若s[i+1]=s[j]或s[i]=s[j+1],(此时提前设置标志位flag_L,flag_R设置初值为零)又要考虑如下情况:
- s[i+1]=s[j]时,用left_i = i+1和left_j=j来记录这部分的位置信息
- 当s[left_i ] = s[left_j],指针一直向中间移动,直到left_i >left_j,跳出循环,表明该部分满足回文特征,则整体满足回文特征,此时设置flag_L>= 1,以便后续判断
- 当s[left_i ] != s[left_j],把flag_L置为-1,跳出循环
- s[i]=s[j-1]时,用right_i = i和right_j=j-1来记录这部分的位置信息
- 当s[right_i ]=s[right_j],指针一直向中间移动,直到right_i >right_j,跳出循环,表明该部分满足回文特征,则整体满足回文特征,此时设置flag_R>= 1,以便后续判断
- 当s[right_i ]!=s[right_j],把flag_R置为-1,跳出循环
- 当flag_L或flag_R大于等于1时,整体字符满足删去一位后是回文字符串,返回TRUE
- 当flag_L和flag_R都为-1时,整体才不满足题目条件,返回False
- s[i+1]=s[j]时,用left_i = i+1和left_j=j来记录这部分的位置信息
但是上述思路并不完备,没有考虑到s[i+1] != s[j] and flag_R == -1
和s[i] != s[j-1] and flag_L == -1
这两种情况,导致程序成了死循环。此外,在进入内层的while循环前,应该利用标志位flag_L和flag_R,判断是否已经进行过这遍循环了(对应代码中elif s[i+1] == s[j] and flag_L == 0:#保证只进去一次
)
class Solution(object):
def validPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
n = len(s)
i = 0
j = n-1
flag_L = 0 #标记删除左侧一位能否满足是回文
flag_R = 0 #标记删除右侧一位能否满足是回文
while i < j:
if s[i] == s[j]:
i += 1
j -= 1
elif s[j-1] != s[i] and s[i+1] != s[j]:
return False
elif flag_L>=1 or flag_R>=1:
return True
elif flag_L==-1 and flag_R==-1:#和初始状态0区别开来
return False
elif s[i+1] == s[j] and flag_L == 0:#保证只进去一次
left_i = i + 1
left_j = j
flag_L = 1
while left_i <= left_j:
if s[left_i] == s[left_j]:
left_i += 1
left_j -= 1
flag_L += 1
else:
flag_L = -1
break
elif s[j-1] == s[i] and flag_R ==0 :
right_j = j - 1
right_i = i
flag_R = 1
while right_i <= right_j:
if s[right_i] == s[right_j]:
right_i += 1
right_j -= 1
flag_R += 1
else:
flag_R = -1
break
elif s[i+1] != s[j] and flag_R == -1:
return False
elif s[i] != s[j-1] and flag_L == -1:
return False
return True
自己的思路完全就是用了穷举法,试图遍历所有可能的情况。但是人的智慧是有限的,可能没办法想全面所有情况。而且,我对象指出,使用flag这种标志位,增加了思考的难度。下面我列出他对我的改进方法。
改进方法的思路:
- 当s[left]=s[right],指针往中间移动,返回TRUE
- 当s[left]!=s[right],设置一个标志计数器cnt=1:
- 当cnt = 0时,说明存在过删除一位的操作,且删除一位后仍不满足回文,返回False
- 当s[left] = s[right-1]且 s[left+1]=s[right]时,说明删掉左边一位或者右边一位均可满足题目要求,这里巧妙的是用了or操作,先判断删除左边一位后剩余字符串是否满足,满足则不会进行or后面的操作(即发生短路),前面不满足为真时,才会进行后面的操作,or两边全为False整体才为False。他的这个步骤直接替代了我上面的若干步骤。
- 接下来讨论其他情况:
- 当s[left] == s[right-1]时,right指针直接减1就行,然后cnt也相应减1
- 否则,当 s[left+1] == s[right]时,left指针直接加1就行,然后cnt也相应减1
- 否则,即满足s[left] != s[right-1]且 s[left+1]!=s[right],此时此字符串直接不符合回文要求,返回False
class Solution:
def validPalindrome(self, s: str) -> bool:
n = len(s)
left,right = 0,n-1
cnt = 1
def huiwen(s): #可在函数内定义函数,此处定义一个判断s是否是回文字符串的函数
i,j = 0,len(s)-1
while i <= j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
while left <= right:
if s[left]!=s[right]:
if cnt == 0:
return False
if s[left] == s[right-1] and s[left+1]==s[right]:
return huiwen(s[left:right]) or huiwen(s[left+1:right+1])
if s[left] == s[right-1]:
right -= 1
elif s[left+1] == s[right]:
left += 1
else:
return False
cnt -= 1
left += 1
right -= 1
return True
在这部分程序实现中,我不仅学到了我方法的优化方法,还学会使用函数封装一些操作(huiwen()
),使程序更简练。
然后我还学习到,python中函数内可以再定义函数,当然huiwen()
也可以定义为类函数,或者类外函数。
huiwen()
定义为类函数:
class Solution:
def huiwen(self,s:str):
i,j = 0,len(s)-1
while i <= j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
def validPalindrome(self, s: str) -> bool:
n = len(s)
left,right = 0,n-1
cnt = 1
while left <= right:
if s[left]!=s[right]:
if cnt == 0:
return False
if s[left] == s[right-1] and s[left+1]==s[right]:
return self.huiwen(s[left:right]) or self.huiwen(s[left+1:right+1])
if s[left] == s[right-1]:
right -= 1
elif s[left+1] == s[right]:
left += 1
else:
return False
cnt -= 1
left += 1
right -= 1
return True
huiwen()
也作为类外函数:
def huiwen(s):
i,j = 0,len(s)-1
while i <= j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
class Solution:
def validPalindrome(self, s: str) -> bool:
n = len(s)
left,right = 0,n-1
cnt = 1
while left <= right:
if s[left]!=s[right]:
if cnt == 0:
return False
if s[left] == s[right-1] and s[left+1]==s[right]:
return huiwen(s[left:right]) or huiwen(s[left+1:right+1])
if s[left] == s[right-1]:
right -= 1
elif s[left+1] == s[right]:
left += 1
else:
return False
cnt -= 1
left += 1
right -= 1
return True
huiwen()
定义为静态函数(静态函数关键字@staticmethod
,定义时不需要self默认参数,但是类内调用时需要self.huiwen()
):
class Solution:
@staticmethod
def huiwen(s):
i,j = 0,len(s)-1
while i <= j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
def validPalindrome(self, s: str) -> bool:
n = len(s)
left,right = 0,n-1
cnt = 1
while left <= right:
if s[left]!=s[right]:
if cnt == 0:
return False
if s[left] == s[right-1] and s[left+1]==s[right]:
return self.huiwen(s[left:right]) or self.huiwen(s[left+1:right+1])
if s[left] == s[right-1]:
right -= 1
elif s[left+1] == s[right]:
left += 1
else:
return False
cnt -= 1
left += 1
right -= 1
return True
88. 合并两个有序数组
倒着遍历就行
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
n1 = len(nums1)
n2 = len(nums2)
zong = m + n -1
i = m -1
j = n - 1
while i >= 0 and j >= 0:
if nums1[i] > nums2[j]:
nums1[zong] = nums1[i]
i -= 1
else:
nums1[zong] = nums2[j]
j -= 1
zong -= 1
while i >= 0:
nums1[zong] = nums1[i]
i -= 1
zong -= 1
while j >= 0:
nums1[zong] = nums2[j]
j -= 1
zong -= 1
还可以优化:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
n1 = len(nums1)
n2 = len(nums2)
zong = m + n -1
i = m - 1
j = n -以上是关于leecode题型整理之双指针的主要内容,如果未能解决你的问题,请参考以下文章
算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值))~盛最多水的容器~~~接雨水