目錄
四、雙指針技巧
情景一:
文章鏈接:https://leetcode-cn.com/explore/learn/card/array-and-string/201/two-pointer-technique/782/
4.1 反轉字符串
4.1.1 問題描述
4.1.2 求解過程
法一: 依次交換元素,複雜度 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() # 最快
4.2 數組拆分 I
4.2.1 問題描述
4.2.2 求解過程
法一: 先將元素從小到大排序,再對偶數 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]) # 法三 - 消耗內存最少
4.3 兩數之和 II - 輸入有序數組
4.3.1 問題描述
4.3.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/
4.4 移除元素
4.4.1 問題描述
4.4.2 求解過程
法一:樸素版快慢指針,複雜度 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)
4.5 最大連續1的個數
4.5.1 問題描述
4.5.2 求解過程
法一:普通方法,維護兩個計數器,複雜度 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
4.6 長度最小的子數組
4.6.1 問題描述
4.6.2 求解過程
法一:題目要求連續子數組,故可以使用 雙指針 / 對撞指針 動態處理。定義 慢指針 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