双指针
双指针法,有时也叫快慢指针,在数组里是用两个整型值代表下标,在链表里是两个指针,一般能实现O(n)的时间解决问题,两个指针的位置一般在第一个元素和第二个元素或者第一个元素和最后一个元素,快指针在前“探路”,当符合某种条件时慢指针向前挪。
双指针还可以用来缩减搜索空间!!!
主要是将一头一尾的索引设为两个指针,当满足条件1时,头指针加一;当满足条件2时,尾指针减一;当满足条件3时,返回结果。多用于有序的数组(矩阵)中,可以有效降低算法的复杂度。
1.盛最多水的容器
class Solution:
def maxArea(self, height: List[int]) -> int:
i, j = 0, len(height) - 1
num = 0
while i < j:
num = max((min(height[i], height[j]) * (j - i)), num)
if height[i] <= height[j]:
i += 1
else:
j -= 1
return num
参考:双指针可用于缩减搜索空间
2.搜索二维矩阵 II
class Solution:
def searchMatrix(self, matrix, target):
if not matrix:
return False
i, j = 0, len(matrix[0]) - 1
while i < len(matrix) and j > -1:
if matrix[i][j] < target:
i += 1
elif matrix[i][j] > target:
j -= 1
else:
return True
return False
3.两数之和 II - 输入有序数组
class Solution:
def twoSum(self, numbers, target):
l, r = 0, len(numbers)-1
while numbers[l] + numbers[r] != target:
if numbers[l] + numbers[r] < target:
l += 1
else:
r -= 1
return l+1, r+1
4.删除排序数组中的重复项
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
a = 0
b = 1
while b < len(nums):
if nums[a] == nums[b]:
b += 1
else:
a += 1
nums[a] = nums[b]
return a+1
优化:
当数组无重复数字(中间多个数字无重复)时,代码else的操作会将数组复制一遍。因此可以优化如下:
#优化
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
a = 0
b = 1
while b < len(nums):
if nums[a] != nums[b]:
#将多个连续不重复的数字直接跳过赋值操作
if (b - a) > 1:
nums[a + 1] = nums[b]
a += 1
else:
b += 1
return a + 1
5.移动零
本题为简单题,主要是介绍快慢指针的思路。
在原数组上进行操作,因此可以用一个index表示不为0的下标(慢指针),对数组进行遍历(快指针),当遍历的数据不为0时,将该值赋值给index下标的元素。最后将数组其他元素置为0。
class Solution:
def moveZeroes(self, nums) -> None:
index = 0
for i in range(len(nums)):
if nums[i] != 0:
nums[index] = nums[i]
index += 1
for i in range(index, len(nums)):
nums[i] = 0
return nums
#循环一次
class Solution:
def moveZeroes(self, nums) -> None:
j = 0
for i in range(len(nums)):
if nums[i] != 0:
nums[j], nums[i] = nums[i], nums[j]
j += 1
return nums
6.三数之和
先对数组进行排序,然后用一个for循环对循环体内部应用双指针进行遍历求和,思路就是先固定前面一个数,然后对后面的数进行类似两数之和的操作,只是多了对重复数字的跳过操作(while循环操作)。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if not nums or n < 3:
return []
nums.sort()
res = []
for i in range(n-2):
if nums[i] > 0:
return res
if i > 0 and nums[i] == nums[i - 1] :
continue
l = i + 1
r = n - 1
while l < r:
sum = nums[i] + nums[l] + nums[r]
if sum == 0:
res.append([nums[i], nums[l], nums[r]])
#跳过重复的数字
while l < r and nums[l] == nums[l + 1]:
l += 1
while r > l and nums[r] == nums[r - 1]:
r -= 1
l += 1
r -= 1
elif sum > 0:
r -= 1
else:
l += 1
return res
7.四数之和
思路:
三数之和的子步骤是两数之和,同样四数之和的子步骤是三数之和,因此可以先确定两个数,然后再利用两数之和的解题框架进行编写代码。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
res = []
nums.sort()
#if n < 4 or nums[0] > target or nums[-1] < target:
if n < 4:
return res
for i in range(n - 3):
# if nums[i] > target:
# break
if i > 0 and nums[i] == nums[i - 1]:
continue
for j in range(i+1, n - 2):
# if nums[i] + nums[j] > target :
# break
if j > i + 1 and nums[j] == nums[j - 1]:
continue
l = j + 1
r = n - 1
while l < r:
sum = nums[i] + nums[j] + nums[l] + nums[r]
if sum == target:
res.append([nums[i], nums[j], nums[l], nums[r]])
while l < r and nums[l] == nums[l + 1]:
l += 1
while r > l and nums[r] == nums[r - 1]:
r -= 1
l += 1
r -= 1
elif sum > target:
r -= 1
else:
l += 1
return res
易错点!!!
与三数之和(和为0)不同,如果对代码进行剪枝,应该注意此题给的target不一定为0!!!如上代码注释掉的代码为错误代码,当target为负数时,程序运行就不能得到正确答案!如果要对程序进行剪枝,可采取如下剪枝:
###剪枝后的代码
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
quadruplets = list()
if not nums or len(nums) < 4:
return quadruplets
nums.sort()
length = len(nums)
for i in range(length - 3):
if i > 0 and nums[i] == nums[i - 1]:
continue
if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
break
if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
continue
for j in range(i + 1, length - 2):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
break
if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
continue
left, right = j + 1, length - 1
while left < right:
total = nums[i] + nums[j] + nums[left] + nums[right]
if total == target:
quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
right -= 1
elif total < target:
left += 1
else:
right -= 1
return quadruplets
剪枝后的代码运行时间可以达到80ms,而未剪枝的代码运行时间为990ms左右,可见剪枝可以很好的优化程序的运行时间。
另外,本题还可以采用递归+两数之和的方法。代码 如下:
class Solution:
def fourSum(self, nums, target):
def findNsum(nums, target, N, result, results):
if len(nums) < N or N < 2 or target < nums[0]*N or target > nums[-1]*N: # early termination
return
if N == 2: # two pointers solve sorted 2-sum problem
l,r = 0,len(nums)-1
while l < r:
s = nums[l] + nums[r]
if s == target:
results.append(result + [nums[l], nums[r]])
l += 1
while l < r and nums[l] == nums[l-1]:
l += 1
elif s < target:
l += 1
else:
r -= 1
else: # recursively reduce N
for i in range(len(nums)-N+1):
if i == 0 or (i > 0 and nums[i-1] != nums[i]):
findNsum(nums[i+1:], target-nums[i], N-1, result+[nums[i]], results)
results = []
findNsum(sorted(nums), target, 4, [], results)
return results
8.合并两个有序数组
一般而言,对于有序数组可以通过双指针法达到O(n+m)的时间复杂度。
最直接的算法实现就是将一个指针指向nums1的开头,另外一个指针指向nums2的开头,然后判断两个指针指向的数字大小先后加入一个大小为n+m的数组中,并移动相应的指针。代码实现如下:
双指针从前往后:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
p, q = 0, 0
nums1_copy = nums1[:m]
nums1[:] = [] #注意与nums1 = []的区别:没有改变旧的nums1的大小
while p < m and q < n:
if nums1_copy[p] < nums2[q]:
nums1.append(nums1_copy[p])
p += 1
else:
nums1.append(nums2[q])
q += 1
if p < m :
nums1 += nums1_copy[p:]
if q < n:
nums1 += nums2[q:]
return nums1
双指针从后往前:
class Solution:
def merge(self, nums1, m, nums2, n):
while m > 0 and n > 0:
if nums1[m-1] >= nums2[n-1]:
nums1[m+n-1] = nums1[m-1]
m -= 1
else:
nums1[m+n-1] = nums2[n-1]
n -= 1
if n > 0:
nums1[:n] = nums2[:n]
9.接雨水
class Solution:
def trap(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
res = 0
left_max, right_max = 0, 0
while l < r:
if height[l] < height[r]:
if height[l] >= left_max:
left_max = height[l]
else:
res += left_max - height[l]
l += 1
else:
if height[r] >= right_max:
right_max = height[r]
else:
res += right_max - height[r]
r -= 1
return res
上述思路可能不太好理解,另有一种思路,参考双指针解法,代码如下:
class Solution:
def trap(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
res = 0
temp_h = 0
while l <= r:
min_h = min(height[l], height[r])
if min_h > temp_h:
res += (min_h - temp_h) * (r - l + 1)
temp_h = min_h
while l <= r and height[l] <= temp_h:
l += 1
while l <= r and height[r] <= temp_h:
r -= 1
res -= sum(height)
return res