目录
- 双指针适用的题型的特点
- 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 - 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
if j >= 0:
nums1[:j+1] = nums2[:j+1]
141. 环形链表
判断双指针的一般方法:
使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
p = head
if head == None or head.next == None :
return False
q = head.next
while p != None and q != None and q.next != None:
if p == q:
return True
p = p.next
q = q.next.next
return False
524. 通过删除字母匹配到字典里最长单词
自己写法:
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
n = len(s)
def isin(content:str,s:str):
i = 0
j = 0
while i < len(s) and j < len(content):
if s[i] == content[j]:
i += 1
j += 1
else:
i += 1
if j == len(content):
return True
else:
return False
ans = {}
for idx in range(len(dictionary)):
if isin(dictionary[idx],s):
ans[dictionary[idx]]=len(dictionary[idx])
ans_strmax = max(ans.values())
if ans_strmax == '':
return ''
for i in ans.keys():
if ans[i] == ans_strmax:
return i
上述写法只是实现了找长度最长且在dictionary中下标最小的字符串,而题目要求是字典序最小,我的写法显然不符合要求。
请教了我对象,他给出以下做法:
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
# 长度降序,相同按照字典序
for d in sorted(dictionary,key=lambda x: (-len(x), x)):
i,j = 0,0
# 同向双指针
n,m = len(d),len(s)
while i < n and j < m:
if d[i] == s[j]:
i += 1
if i == n:
return d
j += 1
if i == n:
return d
return ""
其中,对sorted()函数的用法不是很熟悉
sorted(iterable\[, key][, reverse])
从
iterable
中的项目返回新的排序列表。有两个可选参数,必须指定为关键字参数。
key
指定一个参数的函数,用于从每个列表元素中提取比较键:key=str.lower
。默认值为None
(直接比较元素)。
reverse
是一个布尔值。如果设置为True
,那么列表元素将按照每个比较反转进行排序。
匿名函数lambda
的使用也不太会:
在Python中,lambda的语法是形式:
lambda argument_list: expression
lambda是Python预留的关键字,argument_list和expression由用户自定义。
这里的argument_list是参数列表。它的结构与Python中函数(function)的参数列表是一样的。
lambda函数三个特性
**lambda函数是匿名的:**所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。
**lambda函数有输入和输出:**输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。
**lambda函数一般功能简单:**单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明
lambda函数的用法扩展为以下几种:
1.将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。
执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。
2.将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。
为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。
3.将lambda函数作为其他函数的返回值,返回给调用者。
函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础
4.将lambda函数作为参数传递给其他函数。
filter函数:
此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。
sorted函数:
此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]。
map函数:
此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]。
reduce函数:
此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: ‘{}, {}’.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是’1, 2, 3, 4, 5, 6, 7, 8, 9’。
在本次所使用的sorted函数中,sorted(dictionary,key=lambda x: (-len(x), x))
,含义是将dictionary中元素按照长度len(x)降序排序,若长度相同按照x排序