算法模板-双指针
簡介
在很多數組問題中,雙指針是一個反復被提及的解法。所謂雙指針,指的是在對象遍歷的過程中,并非單個指針進行訪問,而是使用兩個同向(快慢指針)或者反向(對撞指針)來進行掃描,從而達到相應的目的。也有時候,為了處理多數組問題使用多個指針,稱為分離指針。雙指針利用了數組是順序存儲這一特性,在某些情況下可以簡化運算并表現得很優雅。
快慢指針
快慢指針指的是兩個指針從數組同一側開始向另一側遍歷(即同向),將這兩個指針分別定義為快指針(fast)和慢指針(slow),兩個指針以不同的策略移動,直到兩個指針的位置重合(或者其他終止條件),比較典型的例子是快指針每次移動兩步,慢指針每次移動一步。
快慢指針的經典例題為環形鏈表,題目和示例如下。
給定一個鏈表,判斷鏈表中是否有環。如果鏈表中存在環,則返回 true 。 否則,返回 false 。
示例
輸入:head = [3,2,0,-4], pos = 1 輸出:true 解釋:鏈表中有一個環,其尾部連接到第二個節點。這道題雙指針的思路來源于Floyd 判圈算法(也叫龜兔賽跑算法),簡述如下。
若是兩個人從同一地點起跑,一個運動員速度快一個速度慢。那么當跑道有環的時候,跑得快的一定會比跑得慢的先進入環內并在環內移動,等到跑的慢的進入環里面的時候,由于跑的快的那個人速度快,他一定會在某個時刻與跑的慢的相遇,即套了跑的慢的若干圈。若跑道沒有環,顯然兩人永遠不會相遇。
借用上面的思路,我們可以定義一快一慢兩個指針,慢指針每次移動一步,快指針每次移動兩步。一開始這兩個指針都指向head,如果移動過程中快指針反過來追上了慢指針,那么一定存在環形鏈表,否則快的指針會到達鏈表尾部(鏈表無環)。
上述解法的Python代碼如下。
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = Noneclass Solution:def hasCycle(self, head: ListNode) -> bool:p1, p2 = head, headwhile p2 != None and p2.next != None:p1 = p1.nextp2 = p2.next.nextif p1 == p2:return Truereturn False對撞指針
對撞指針指的是兩個指針從數組的不同側向另一側移動(即反向),其中左側的稱為左指針(left),右側的稱為右指針(right)。需要注意的是,對撞指針適用于有序數組,當你遇到題意為有序數組時應當首先考慮對撞指針。
對撞指針的經典例題為兩數之和 II - 輸入有序數組,題目和示例如下。
給定一個已按照 非遞減順序排列 的整數數組 numbers ,請你從數組中找出兩個數滿足相加之和等于目標數target 。函數應該以長度為 2 的整數數組的形式返回這兩個數的下標值。numbers 的下標 從 1 開始計數 ,所以答案數組應當滿足 1 <= answer[0] < answer[1] <= numbers.length。你可以假設每個輸入 只對應唯一的答案 ,而且你 不可以 重復使用相同的元素。
示例
輸入:numbers = [2,7,11,15], target = 9 輸出:[1,2] 解釋:2 與 7 之和等于目標數 9 。因此 index1 = 1, index2 = 2 。初始時兩個指針分別指向第一個元素位置和最后一個元素的位置。每次計算兩個指針指向的兩個元素之和,并和目標值比較。如果兩個元素之和等于目標值,則發現了唯一解。如果兩個元素之和小于目標值,則將左側指針右移一位。如果兩個元素之和大于目標值,則將右側指針左移一位。移動指針之后,重復上述操作,直到找到答案。由于題目說明答案唯一,因此一定可以找到唯一解。
上述解法的Python代碼如下。
class Solution:def twoSum(self, numbers: List[int], target: int) -> List[int]:left = 0right = len(numbers) - 1while left < right:if numbers[left] + numbers[right] < target:left += 1elif numbers[left] + numbers[right] > target:right -= 1else:return [left+1, right+1]分離指針
分離指針在兩個不同的數組中,使用兩個不同的指針來進行遍歷。分離指針適用于雙數組或者多數組問題。
分離指針的經典例題為兩個數組的交集,題目和示例如下。
給定兩個數組,編寫一個函數來計算它們的交集。
示例
輸入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 輸出:[9,4]這道題的雙指針解法為將兩個數組排序,然后兩個指針遍歷兩個數組。具體來看,兩個指針分別指向兩個數組的頭部,每次比較兩個指針指向位置的數字。若不等,則較小數字位置的指針右移;若相等且該數字不等于上次加入交集的值pre,則將該數字添加到交集中并更新pre,兩個指針都右移。當其中一個指針到達數組末端,停止遍歷。
上述解法的代碼如下。
class Solution:def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:nums1, nums2 = sorted(nums1), sorted(nums2)m, n = len(nums1), len(nums2)intersection = []i, j = 0, 0while i < m and j < n:num1, num2 = nums1[i], nums2[j]if num1 == num2:if not intersection or num1 != intersection[-1]:intersection.append(num1)i += 1j += 1elif num1 < num2:i += 1else:j += 1return intersection練習題
對雙指針感興趣的可以訪問力扣上的雙指針專題,上一節以141題和167題為例簡單理解了快慢指針和對撞指針。
-
快慢指針的題還有26. 刪除有序數組中的重復項、27. 移除元素、283. 移動零等。
-
對撞指針的題還有15. 三數之和、16. 最接近的三數之和、881. 救生艇等。
-
分離指針的題還有88. 合并兩個有序數組、350. 兩個數組的交集 II等。
補充說明
遇到數組的問題,應當首先想到使用雙指針進行解題,因為兩個指針的同時遍歷策略會大大減少時間和空間復雜度。
總結
- 上一篇: 算法模板-对称性递归
- 下一篇: ByteTrack实时多目标跟踪