双指针模式指使用两个一前一后的指针遍历数据结构,直到某个指针触发停止条件。该模式常用于在有序数组或链表中搜索元素对。使用双指针的好处在于和单指针相比,不用去连续遍历整个数组来找出答案,可以带来更好的时间或空间复杂度。
在以下场景中,我们可能会用到双指针:
给定一个整数数组
nums
和一个目标值target
,请你在该数组中找出和为目标值的那「两个」整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
「示例」:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
这道题官方题解的解法是通过构造数据结构表示数组中元素与其索引的对应关系,然后检查数组中是否存在目标元素即可。对于 python,可以使用「字典」进行表示。该方法的时间复杂度和空间复杂度均为
。
class Solution(object):
def twoSum(self, nums, target):
buff_dict = {}
for i,num in enumerate(nums):
complement = target - num
if complement in buff_dict: # 判断键是否存在于字典中
return [buff_dict[complement], i]
buff_dict[num] = i
由于给定的数组「有序」,我们还可以使用「双指针」来解决这个问题。将双指针初始化在数组的两侧,计算元素之和,如果大于目标值则将右指针左移,小于目标值则将左指针右移,如下图所示:
该方法对应的 python 实现如下,时间复杂度为
,空间复杂度缩减为
:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
left, right = 0, len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right]
if target > current_sum:
left += 1
else:
right -= 1
return [-1, -1]
给你一个包含 n 个整数的数组
nums
,判断nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
「示例」:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
这道题也是双指针的经典题目。首先为了保证三元组不重复,我们先对数组进行排序,保证三元组中的数按从小到大的顺序排列。然后开始通过循环对数组进行遍历,找出可行解。这里为了减少复杂度,在第一个元素固定的情况下,对于后两个元素我们可以使用双指针,如果两者之和大于目标值,则将左移右指针,如果小于目标值,则右移左指针。注意在移动时要去除重复(即相邻两次移动对应的值不能相同)。该方法的 python 实现如下:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if not nums or n < 3: return []
nums.sort() # 排序数组
ans = list()
for first in range(n):
if nums[first] > 0: # 如果第一个元素大于0,由于数组有序,后面必不存在可行解,直接返回即可
return ans
if first > 0 and nums[first] == nums[first - 1]:
continue # 如果和上一次相同则跳过
second = first + 1
third = n - 1
target = -nums[first]
while second < third:
if nums[second] + nums[third] == target:
while second < third and nums[second] == nums[second + 1]:
second += 1 # 重复则跳过,下同(注意只需要在满足条件时跳过即可)
while second < third and nums[third] == nums[third - 1]:
third -= 1
ans.append([nums[first], nums[second], nums[third]])
second += 1 # 添加完可行解后继续遍历
third -= 1
elif nums[second] + nums[third] < target:
second += 1
else:
third -= 1
return ans
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
这道题可以通过多种方法求解,这里仅介绍较为巧妙的双指针解法。首先需要明确,对于某一列来说,其能接雨水的充要条件是该列的高度小于其左侧和右侧最大高度的最小值,以第五列为例,其高度为 1,而左侧最大高度为 2,右侧最大高度为 3,较小值为 2,则将其与当前列高度比较,由于当前列高度较小,所以可以装下 2-1=1 个单位的雨水。
基于上述思路,对于当前列来说,我们只需要找出其左侧和右侧的最大高度(不包括当前列),即可计算其能接的雨水量。基于双指针的思想,我们设置一左一右两个指针,分别从左到右和从右到左遍历找出最大高度,记为 left_max
和 right_max
,如下图所示(考自 LeetCode 评论区大神):
right_max
left_max __
__ | |
| |__ __?????????????????????? | |
__| |__| __| |__
left right
可以看到,如果 left_max < right_max
,则对于左指针 left
对应的列而言,其左边的最大高度必为 left_max
,而右边的最大高度必大于等于 right_max
(因为中间部分未知),所以偏小的最大高度一定位于左侧(left_max < right_max <= true_right_max
),这时我们处理左指针,并将其右移。反之,我们则需要去处理右指针,并将其左移。该思路对应的 python 实现如下:
class Solution:
def trap(self, height: List[int]) -> int:
left = 0
right = len(height) - 1
left_max = right_max = 0 # 表示左指针左侧的最大高度和右指针右侧的最大高度
ans = 0
while left <= right:
if left_max < right_max:
ans += max(0, left_max - height[left])
left_max = max(left_max, height[left]) # 计算下一次循环用的最大高度值
left += 1
else:
ans += max(0, right_max - height[right])
right_max = max(right_max, height[right])
right -= 1
return ans
其他类似的题目列表: