(2022.08.16 Tues)
双指针法采用两个指针/变量,对容器进行遍历。容器类型包括,数组、链表、字符串等。数组(不管有序无序)问题,往往可以用双指针法来解决。典型案例是merge排序对双指针的应用。
遍历方式:
- 同向遍历
- 反向/相向遍历
遍历约束条件
- 两指针相遇
- 题目约束的其他条件达成
案例
正向移动指针案例
- 将数组中的所有零元素移到数组结尾
给定一个数组,比如[1,2,0,4,5,0,10]
,将所有0元素移到数组结尾,并且其他元素的相对顺序不变。
可使用两个指针法,正向移动完成遍历。
def move_zeros(arr):
'''
Move zeros to the end of array, with dual points
'''
i, j = 0, 0
print(i, j)
result = [None] * len(arr)
while i < len(arr):
if arr[i] != 0:
result[j] = arr[i]
j += 1
i += 1
print(i, j)
while j < len(arr):
result[j] = 0
j += 1
return result
(2022.08.27 Sat)
- leetcode 1. 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
算法1:可使用双指针法解决该问题。固定左指针,从左指针的右边开始遍历。算法复杂度为。
算法2:利用哈希。遍历数组,每个遍历的值作为哈希的key其index作为哈希的value存入字典,遍历过程中计算是否在哈希中存在。算法复杂度为。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
tmp = {}
res = []
for i in range(len(nums)):
if target-nums[i] in tmp and tmp[target-nums[i]]!=i:
res = [tmp[target-nums[i]], i]
break
tmp.update({nums[i]: i}) # 在判断之后更新字典,避免出现nums中有重复值,且重复值之和为target的情况
return res
反向移动指针案例
- 判断一个字符串或数字是否为回文数(palindrome)。可以采用两个指针,从头、尾两个方向遍历容器,每次指针移动步数相同,一旦指针对应的值不同,则遍历结束,返回
Flase
。
def palindrome(al):
'''
Tell whether the input is a palindrome
Output:
True/False
'''
if isinstance(al, str):
tmp = al
elif isinstance(al, int):
tmp = str(al)
if len(tmp)< 2:
return True
p_left, p_right = 0, len(tmp)-1
while p_left <= (len(tmp) - 1)//2:
if tmp[p_left] == tmp[p_right]:
p_left += 1
p_right -= 1
continue
else:
return False
return True
- 给定一个有序递增数组和一个目标值,判断是否有数组中的两个元素之和等于该目标值。进阶:1 数组无序,2 判断三个元素之和是否等于目标值。
对于有序递增数组,可从头、尾两个方向向数组中间行进,求和判断是否符合要求。也可根据二分法,判断数据中间的值是否大于或小于目标值,在使用双向指针。对于无序数组,可选择先排序,再用相对行进的指针,复杂度,也可以用同向指针计算,复杂度。
def find_target(arr, target):
'''
tell whether the sum of any two elements in the sorted arr is equal to target
Output:
index pair
'''
if len(arr) < 2:
if sum(arr) == target:
return (len(arr))
else:
return False
pl = 0
pr = len(arr)-1
result = []
while plpl and pr > 0:
if arr[pl] + arr[pr] == target:
result.append((pl, pr))
pl += 1
continue
elif arr[pl] + arr[pr] > target:
pr -= 1
continue
elif arr[pl] + arr[pr] < target:
pl += 1
continue
return False if not result else result
双向移动
(2022.08.27 Sat)
- letcode 13. 包含有n个数的整数数组
nums
,问其中是否包含三个元素abc,使得a+b+c=0
?如果有,返回元素元组,且要求不重复。如果没有,返回空列表。
本题如果不使用双指针,一种常规的解法如下:
遍历数组,固定第一个元素i
,i
后面的数组为新数组,对其遍历,新数组第一个元素为j
,j
后面的数组为新数组,对其遍历,元素为k
。通过三层遍历,判断i+j+k==0
。该算法复杂度为。
下面使用双指针解决本题。对数组进行升序排序。遍历排序后的数组,选定元素i
,对于元素i
后面的元素形成新的序列。在新序列上使用双指针,从头、尾两个方向进行遍历。如果遍历元素求和为0,则左右两个指针向中间行进;如果和大于0,说明右指针值大,右指针向左移动;如果和小于0,说明左指针值大,左指针向右移动。考虑到不能有重复结果,在选定元素i
和左右两个指针出都需要进行去重操作。代码如下
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = sorted(nums) # 排序,nlog(n)
res = []
if len(nums) < 3 or len(nums) > 3000:
return res
for b in range(len(nums)-2):
# 对选定元素的去重操作
if b > 0 and nums[b] == nums[b-1]:
continue
# 选定左右两个指针
left, right = b+1, len(nums)-1
while left < right: # 指针对撞是结束遍历的条件
s = nums[b] + nums[left] + nums[right]
if s == 0:
# 符合条件,则保存
res.append([nums[b], nums[left], nums[right]])
# 指针值去重
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
continue
elif s > 0:
right -= 1
continue
elif s < 0:
left += 1
continue
return res
复杂度分析:使用双指针,首先做排序,复杂度为。选定了基准点,对基准点后面的数组进行遍历,复杂度为。总复杂度为,比不用排序的方法低了一个数量级。
- leetcode 881. 救生艇:给定数组 people ,people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。返回 承载所有人所需的最小船数 。条件:people[i] <= limit。
该题可首先对数组排序,排序后从头尾两端进行双指针遍历,一旦双指针对应元素之和小于limit,则最小船数加1且双指针同时向中间移动,否则船数加1且只移动右边指针。
class Solution:
def numRescueBoats(self, people: List[int], limit: int) -> int:
people = sorted(people)
res = 0
if len(people) < 2 or len(people) > 5 * 10**4:
return res
left, right = 0, len(people)-1
while left <= right:
if people[left] + people[right] <= limit:
left += 1
right -= 1
res += 1
continue
return res
Reference
1 双指针法,飞翔的猪,知乎
2 双指针法各类提醒总结,ByeGuo,知乎