LeetCode 287. Find the Duplicate Number (python 判斷環,時間複雜度O(n))

LeetCode 287. Find the Duplicate Number

暴力解法

時間 O(nlog(n)),空間O(n),按題目中Note「只用O(1)的空間」,照理是過不了的,可是可能判題並無卡空間複雜度,因此也能AC。python

class Solution:
    # 基本思路爲,將第一次出現的數字
    def findDuplicate(self, nums: List[int]) -> int:
        s = set()
        for i in nums:
            a = i in s
            if a == True:
                return i
            else:
                s.add(i)

雙指針判斷環

時間O(n),空間O(1),思路十分巧妙,可是使用條件比較苛刻。根據題目給出的條件,剛好能用這種解法,這應該也是出題人推薦的解法。指針

題意分析:

  1. 輸入的序列有n+1個數字,每一個數字在1~n之間取,這爲構成數字環創造了條件。
  2. 只有一個數字有重複,因此只可能構成一個環。

注:上面所說的環是指1->2->3->1code

以樣例1爲例:[1,3,4,2,2]blog

0 1 2 3 4
1 3 4 2 2

以下圖所示,其中2->4->2構成環,入環點爲2
leetcode

解題思路

由題意分析可知,每一個樣例均可以畫成這樣一張圖,咱們只須要找出圖中的環,並找出入環點,即爲所求的重複數字key,下面都用key表示所求的重複數字。io

爲何一定存在環

以樣例1爲例,圖中出現了5個點0-4,圖中存在5根指針線,5個點5根線,一定存在環。
n個點,點的範圍去0~n-1,n根線,一定存在環。(n-1根線是剛好無環的狀況,本身畫圖可知)table

找環的方法

設置一個慢指針slow,一個快指針fast。slow每次走一步,fast每次走兩步,若是slow與fast能相遇,說明圖中存在環,而且相遇點必定存在於環中。ast

爲何key必定爲入環點?

有題意分析中的表可知,key的入度必定大於1,即不止一個點能夠直接到key。而key必定存在於環中,因此key必定爲入環點。樣例1中3,4均可到達2,2的入度2,2爲入環點,即爲所求的key。class

怎麼找入環點key?

slow和fast相交的點記爲相遇點P。
slow和fast從起點0到相遇點P運行步驟以下:
List

這個相遇點P與起點0到達入環點key的步數 差距爲環L的整數倍,故設置slow2從起點0開始,每次走一步,slow從相遇點P開始,每次走一步,slow和slow2必定會相遇在入環點key。

咱們能夠有一個小小的證實,以下圖

設起點0到達入環點key的步數爲x,相遇點P到達入環點key的步數爲y。
設slow指針走到相遇點P的步數爲t,fast走到相遇點P的步數爲2*t。
設走完環一圈的步數爲L

2 * t - x + y = M * L(一)
t - x + y = N * L (二)
fast指針在環中走的步數2t-x,此時到達相遇點P,key->P->key步數爲2t-x+y = M * L,正好爲L的M倍,M爲常數。(一)式
slow指針在環中走的步數t-x,此時到達相遇點P,key->P->key步數爲t-x+y = N * L,正好爲L的N倍,N爲常數。(二)式

2倍(二)式 減 (一)式
y-x = (2N-M) * L
因此y與x的步數差距爲L倍的環。
得證。

如何肯定起點0必定會進入包含key的環?

假設存在不包含key的環,起點0在不包含key的環中繞圈。
|0|a1|a2|a3|a4|a5|a6|
|-|-|-|-|-|-|-|
|b1|b2|b3|b4|b5|b6|b7|
按題意不包含環,b[i]與b[j]必定不相等(i != j)
因爲b1~b7從1開始,因此b[i]只能從a[j]中取(1<=i<=7,1<=j<=6)
從6個數字的集合a中取7個數字,因此假設不成立,一定存在相同數字b[k],即爲key。

代碼以下

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        # 若是隻有兩個元素,第一個元素必定是重複元素
        if len(nums) == 2:
            return nums[0]
        
        # fast每次走兩步,slow每次走一步,起始點能夠爲任意位置
        fast = 0
        slow = 0
        # python沒有do while,因此在循環外寫了一遍
        slow = nums[slow]
        fast = nums[nums[fast]]
        while slow != fast:
            slow = nums[slow]
            fast = nums[nums[fast]]
        
        # fast從起點每次走一步,必定會與slow相遇,此時slow可能在環中走了多倍的L步。
        # L爲環一圈的步數
        fast = 0
        while fast != slow:
            slow = nums[slow]
            fast = nums[fast]
        return fast
相關文章
相關標籤/搜索