【數組和字符串】(四) 雙指針技巧

目錄

四、雙指針技巧

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/ 


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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章