1. 程式人生 > >Leetcode 287:尋找重複數(最詳細的解法!!!)

Leetcode 287:尋找重複數(最詳細的解法!!!)

給定一個包含 n + 1 個整數的陣列 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。

示例 1:

輸入: [1,3,4,2,2]
輸出: 2

示例 2:

輸入: [3,1,3,4,2]
輸出: 3

說明:

  1. 不能更改原陣列(假設陣列是隻讀的)。
  2. 只能使用額外的 O(1) 的空間。
  3. 時間複雜度小於 O(n2) 。
  4. 陣列中只有一個重複的數字,但它可能不止重複出現一次。

解題思路

我們首先想到的解法就是現將陣列nums排序,建立precur

兩個指標,分別指向前面一個元素和當前元素,然後對於排序好的nums從頭到尾的遍歷,判斷nums[pre]==nums[cur],如果成立,返回nums[pre]即可,否則的話繼續遍歷。

class Solution:
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        nums.sort()
        for i in range(len(nums)-1):
            if nums[i] ==
nums[i+1]: return nums[i]

但是我們這種做法改變了原有的陣列,所以這種做法就被我們排除了。由於是查詢問題,我們不難想到通過二分搜尋法,但是這裡我們要將原來的二分搜尋法做一些調整。我們首先計算mid,然後我們統計陣列中小於等於mid元素的個數k,如果k<=mid的話,那麼說明重複值在[mid+1,n]之間,否則的話重複值在[1,mid]之間。為什麼這樣子做是合理的呢?實際上這個問題的思想來源於抽屜原理,我們將包含重複元素的所有數放到一個可以容納所有數的陣列中去,如下

1  2  3  4
__________
1  2  3  4
   2

如果採用下整除策略,此時我們的mid=2,我們發現此時左邊元素個數大於右邊元素個數,這就是由於重複元素造成的兩邊不平衡,如果沒有重複元素,兩邊數的個數應該是一樣的。我們現在再回到我們的演算法,看它是怎麼和抽屜原理聯絡起來的。

1  3  4  2  2
mid=2

我們此時的mid=2(也就是中間抽屜的位置),我們接著統計小於等於2的數的個數(統計抽屜左邊元素個數),也就是k=3,而3>2,所以我們在[1,2]這個區間中找重複元素(抽屜左邊找重複元素),再次計算mid=1,我們發現k=1<=1,此時我們就知道了2即為重複元素。

class Solution:
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        low, high = 1, len(nums)-1
        
        while low < high:
            mid = (high+low)//2
            count = sum(num <= mid for num in nums)
            if count <= mid:
                low = mid+1
            else:
                high = mid
        return low

這個問題有一個非常巧妙的解法,我們看上面的例子

1  3  4  2  2

如果我們使用t=nums[t]這樣的前進方式,我們首先令t=0

t=nums[0]=1
t=nums[1]=3
t=nums[3]=2
t=nums[2]=4
t=nums[4]=2
t=nums[2]=4
...

我們發現進入了迴圈。我們再使用t=nums[nums[t]]這樣的前進策略

t=nums[nums[0]]=3
t=nums[nums[3]]=4
t=nums[nums[4]]=4
....

其實就是比前面快了一倍。這就變成了之前Leetcode 142:環形連結串列 II(最詳細的解法!!!)的問題,我們可以使用快慢指標解決。

class Solution:
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) > 1:
            slow = nums[0]
            fast = nums[nums[0]]
            while slow != fast:
                slow = nums[slow]
                fast = nums[nums[fast]]
                
            entry = 0
            while entry != slow:
                entry = nums[entry]
                slow = nums[slow]
                
            return entry
        
        return -1

講道理,這種洞察力真的太厲害了。

reference:

http://keithschwarz.com/interesting/code/?dir=find-duplicate

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!