常用的快慢指針背後的原理——龜兔賽跑算法(Floyed Cycle Detection Algorithm)詳解!

在刷LeetCode時,我們經常用到快慢指針。那麼它背後有怎樣的數學原理呢,今天就給大家獻醜了。

龜兔賽跑簡述

Floyd判圈算法(Floyd Cycle Detection Algorithm),又稱龜兔賽跑算法(Tortoise and Hare Algorithm),是一個可以在有限狀態機、迭代函數或者鏈表上判斷是否存在環,以及判斷環的起點與長度的算法。

原理

如圖,A爲出發點,B爲環的入口點。快慢指針都從A點出發,其中快指針的速度是慢指針的2倍。
在這裏插入圖片描述

判斷是否有環

如果存在環,快慢指針必定相遇;否則,慢指針走到結尾還未相遇則不存在環。

求環的長度

假設兩者在C點第一次相遇。再讓兩指針繼續前進,下次相遇時快指針比慢指針多走了一圈。

求環的起點

假設AB的長度爲m,BC的長度爲n,環的周長爲p。在相遇時,我們假設慢指針移動的距離爲s,有:
s = m + n + ap
而快指針的移動距離爲:
2s = m + n + bp

兩式相減有:
s = (b - a)p
也就是說慢指針走過的距離爲環長的整數倍。

在第一次相遇後,讓慢指針從C點出發,另一個指針從A點出發,都以慢指針的速度移動。則它們必定在B點相遇。因爲當從A點出發的指針走過m的距離到達B點時,慢指針已經走過了s+m的距離(也是從A點出發)。其中S爲環長的整數倍,那麼慢指針也必定再B點,所以相遇點爲環的入口點B點。

應用

LeetCode 142題

這是求環形鏈表的入口,代碼如下:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if not head:
            return None
        
        slow, fast = head, head.next
        while slow != fast:
            slow = slow.next if slow else None
            fast = fast.next.next if fast and fast.next else None
            
        if not slow:
            return None
            
        entry = head
        slow = slow.next
        while entry != slow:
            entry = entry.next
            slow = slow.next
            
        return entry

LeetCode 287

這是數組形式的尋找起點:

class Solution:
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) <= 0:
            return -1
        
        slow = nums[0]
        fast = nums[nums[0]]
        while slow != fast:
            slow = nums[slow]
            fast = nums[nums[fast]]
            
        entry = nums[0]
        slow = nums[slow]
        while slow != entry:
            entry = nums[entry]
            slow = nums[slow]
            
        return entry         
發佈了103 篇原創文章 · 獲贊 153 · 訪問量 50萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章