目录
四、双指针技巧
4.1 反转字符串
4.2 数组拆分 I
4.3 两数之和 II - 输入有序数组
4.4 移除元素
4.5 最大连续1的个数
4.6 长度最小的子数组
文章链接:https://leetcode-cn.com/explore/learn/card/array-and-string/201/two-pointer-technique/782/
法一: 依次交换元素,复杂度 O(n)。
2020/06/08 - 89.38% - 次优速度, 最快的还是内建 str.reverse()
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
# 不可以使用 s = s[::-1], 因为 s[::-1] 将无法申请新的字符串空间!
for i in range(len(s)//2): # 如此将奇偶均可
s[i], s[-i-1] = s[-i-1], s[i] # 交换元素, 省去中间变量 temp
# s.reverse() # 最快
法一: 先将元素从小到大排序,再对偶数 index 的数字求和。复杂度 O(n)。
2020/06/08 - 99.98% - 基本就是最快的方法了,但是看不出来和双指针有什么关系?!
class Solution:
def arrayPairSum(self, nums: List[int]) -> int:
if not nums:
return 0
nums.sort() # 元素从小到大排序
# 法一
summ = 0
for i in range(len(nums)//2):
summ += nums[2*i] # 偶数 index 元素求和
return summ
#odd_slice = slice(0, len(nums), 2)
#return sum(nums[odd_slice]) # 法二
#return sum(nums[::2]) # 法三 - 消耗内存最少
法一:先找一个上限值约束范围,再进行双指针迭代查找。复杂度 O(n)。
22020/06/08 - 88.66% - 还行!然而似乎存在太多冗余操作了!!
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
former = 0
latter = len(numbers)-1
if latter == 1: # 特殊情况 [-1,0] -1
return [1, 2]
if numbers[-1] >= target: # 如果列表最大值大于 target
for index, num in enumerate(numbers): # 找到比 target 小一点的值 index
if num > target:
latter = index - 1 # 找到了后指针约束范围
while former <= latter:
while True:
if numbers[former] + numbers[latter] == target:
return [former+1, latter+1]
elif numbers[former] + numbers[latter] > target:
break
else:
former += 1 # 前指针前进一位
former = 0 # 前指针复位
latter -= 1 # 后指针回退一位
法二:参考方法,果然十分地精炼!!无论是循环条件,还是指针调整方式,都比法一要优秀不少!复杂度 O(n)。
2020/06/08 - 99.99% - 值得学习的抽象思维!这才是真正的高效双指针用法!
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i = 0 # 前指针
j = len(numbers) - 1 # 后指针
while numbers[i]+numbers[j] != target:
if numbers[i]+numbers[j] > target: # 如果和大于 target, 后指针回退一位
j -= 1
elif numbers[i]+numbers[j] < target: # 如果和小于 target, 前指针前进一位
i += 1
return [i+1, j+1]
文章链接:https://leetcode-cn.com/explore/learn/card/array-and-string/201/two-pointer-technique/786/
法一:朴素版快慢指针,复杂度 O(n)。
2020/06/14 - 95.65% - 算最简洁和高效的算法了 —— 快慢指针
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast] # 慢指针元素指向当前快指针元素
slow += 1
return slow
法二:单指针遍历和删除,复杂度 O(n)。但不明白为什么正向会出错,而反向则没问题?
2020/06/14 - 99.9% - 虽然最快,但仅适合 Python,没有算法的思想!
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
for i in range(len(nums)-1, -1, -1): # 为什么反向可以、正向删除不行?
if nums[i] == val:
del nums[i]
return len(nums)
#while val in nums: # 类似的仅追求简洁而没有灵魂的方法
# nums.remove(val)
#return len(nums)
法一:普通方法,维护两个计数器,复杂度 O(n)。
2020/06/14 - 99.50% - 基本是最快了的,实际相当于双指针
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
max_len = nums[0] # 最大值计数器 # 特殊情况 [1] or [0]
counter = 0 # 临时值计数器
for i in range(len(nums)):
if nums[i]:
counter += 1
else:
max_len = max(max_len, counter)
counter = 0
if nums[-1] == 1:
max_len = max(max_len, counter) # 特殊情况 最后一个元素是 1 需再更新一次最大值
return max_len
法一:题目要求连续子数组,故可以使用 双指针 / 对撞指针 动态处理。定义 慢指针 slow 和 快指针 fast,二者之间的数组 (切片) 相当于一个 滑块 / 滑动窗口,如此所有的子数组都会在 索引区间 [slow ... fast] 中出现。若 nums[slow : fast] 的元素之和小于目标值 s,则 fast 向后移一位,再次比较,直到大于目标值 s 后,slow 向前移动一位,缩小子数组 (切片) 长度。每当 nums[slow : fast] 的元素之和大于目标值 s 都会与上一次记录的最小连续子数组长度 min_len 比较并取最小值。当 slow 遍历至数组最末端时结束循环,若不存在符合条件连续子数组将返回 0,否则返回最小子数组长度 min_len。复杂度 O(n)。
2020/06/18 - 98.60% - 还有一点优化空间
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
if not nums: # 特殊情况
return 0
slow, fast = 0, 0 # 慢指针, 快指针
total = 0 # 连续子数组元素之和
nums_len = len(nums) # 数组长度
min_len = nums_len + 1 # 初始化最小连续子数组长度
## 关键部分
while slow < nums_len:
if (fast < nums_len) and (total < s): # 用元素累加, 而不用切片求和
total += nums[fast]
fast += 1 # 快指针后移一位
else:
total -= nums[slow]
slow += 1 # 慢指针后移一位
if total >= s:
min_len = min(min_len, fast-slow) # 每当子数组之和大于s就比较最小长度
if min_len == nums_len + 1:
return 0
else:
return min_len
''' 遍历所有情况将在数量很大的时候超时
for win in range(1, len(nums)+1): # 滑动窗口递增
for i in range(len(nums)): # 本窗口移动次数
if sum(nums[i:i+win]) >= s:
return win
return 0
'''
法二:对法一稍作优化,本质思想一致。将最小连续子数组长度 res 初始化为正无穷大,同时将快指针 end 自然地整合到 for loop 中从而减少一个类变量的调用与维护。
2020/06.18 - 99.80% - 最佳方法!
class Solution(object):
def minSubArrayLen(self, s, nums):
if not nums: # 特殊情况
return 0
nums_len = len(nums) # 数组长度
start = 0 # 慢指针
total = 0 # 连续子数组元素之和
res = float('inf') # 初始化最小连续子数组长度为﹢∞
for end in range(nums_len): # 快指针后移
total += nums[end] # 累加快指针所指元素
while total >= s: # 每当子数组之和 total > s
res = min(res, end-start+1) # 就比较一次最小长度值
total -= nums[start] # 减去慢指针所指元素
start += 1 # 慢指针后移
if res == float('inf'):
return 0
else:
return res