LeetCode 287. Find the Duplicate Number (找到重復的數字)


Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Note:

  1. You must not modify the array (assume the array is read only).
  2. You must use only constant, O(1) extra space.
  3. Your runtime complexity should be less than O(n2).
  4. There is only one duplicate number in the array, but it could be repeated more than once.

 


 

題目標簽:Array, Binary Search, Two Pointers

  題目給了我們一個nums array, 讓我們找到其中的重復數字。因為這一題有4個條件,所以有難度。1. 要求我們不能改動array;2. 只能用O(1)空間;3. 時間要小於O(n^2);4. 只有一個重復的數字,但是它可以出現最少1次。

  方法1:O(logn * n)

    利用binary search。

    題目給的數字是在 [1, n] 之間, array 的size 是 n+1。所以肯定有一個數字會至少出現2次。

    分析一下:  

      如果n 是5,那么就會有1 2 3 4 5 一共5個數字的可能,而array size 是6,那么其中一個數字肯定會至少出現兩次。

      如果沒有重復的數字,小於等於1的數字 出現的次數 等於 1;

                小於等於2的數字 出現的次數 等於 2;

                  ... 同理3;4;5。

      如果有重復的數字,如果重復的是1,那么 小於等於1的數字 出現的次數 肯定大於1;

      基於這個理論,我們可以在1 2 3 4 5 選出一個 mid, 遍歷array來count 小於等於mid 的數字個數 小於等於 它自己mid 還是 大於 mid?

          如果count 小於等於mid, 說明 1 到 mid 這些數字 沒有重復項, 重復項在 右半邊 mid 到n, 所以縮小到右半邊繼續搜索;

          如果count 大於mid, 說明  1 到 mid 這些數字中 有重復項,縮小到 左半邊繼續搜索。

 

 

Java Solution:

Runtime beats 28.13% 

完成日期:09/13/2017

關鍵詞:Array, Binary Search

關鍵點:如果從1到n中 有重復項,那么必然有一個數字出現的次數至少是2次

 

 1 class Solution 
 2 {
 3     public int findDuplicate(int[] nums) 
 4     {
 5         /* Solution 1: binary search */
 6         int low = 1, high = nums.length - 1;
 7         
 8         while(low <= high)
 9         {
10             int mid = low + (high - low) / 2;
11             int cnt = 0;
12             
13             for(int a: nums)
14             {
15                 if(a <= mid)
16                     cnt++;
17             }
18             
19             if(cnt <= mid) // meaning duplicate is on the right side
20                 low = mid + 1;
21             else // if cnt > mid, meaning duplicate is on the left side
22                 high = mid - 1;
23                 
24         }
25         
26         return low;
27     }
28 }

參考資料:

http://bookshadow.com/weblog/2015/09/28/leetcode-find-duplicate-number/

 

  方法2:O(n),利用slow 和fast 雙指針,這個方法太巧妙了,有好幾個點比較難理解。

    利用slow 和 fast 指針,經歷兩次 相遇,來找到重復的數字。

    相遇1: slow 一次走一步;fast 一次走兩步。

        當它們相遇的時候,讓slow 保留在相遇的點上。

    相遇2:讓fast 從起點開始走,一次走一步;slow 在上一次相遇的點上開始走,一次走一步。

        當它們相遇的時候,相遇的點就是重復的數字。

    

    如何讓slow fast 走步呢,利用slow = nums[slow]; fast = nums[nums[fast]]. 把index 所指的數字值 替換index 來模仿走步數。

    

    來結合圖片分析幾個關鍵點:

    

    S 為起始點,O 為圓的入口,M1 為第一次相遇的地方,x 為S到O的長度, y 為 O到M1的長度, z 為 M1到O的長度。

    1. 第一次相遇是為了找到它們的相遇點, 第二次相遇是為了找到圓的入口 O。

    2. 路程會有一段path 接着和一個circle:

      舉例子來看一下:

       0 1 2 3 4 5 6 7

      [4,6,2,1,7,1,3,5]

      n = 7, 數字都是 1 ~ 7 中的選擇, array size = 8, 肯定會有一個數字,至少出現2次。

      按照我們的方法來走一下:0 -> 4 -> 7 -> 5 -> 1 -> 6 -> 3 -> 1 -> 6 -> 3 -> 1 -> 6 -> 3 -> 1 ... 一直重復 1 6 3

      紅色部分可以看作為S - O path; 綠色部分可以看作為 O點; 藍色部分看作為 圓。

    3. 為什么會有一段path:

      因為數字只能是 1 到 n, 所以index 0 肯定只能走過一次, 那么肯定有一段path, path 長度得看情況。

    4. 為什么會有一個circle:

      因為index 除了0, 有1到n(而且唯一不重復); 數字也是1到n, 所以每一次 index 指向數字 時候, 那個數字肯定能成為新的和唯一的 index,一直循環。

    5. 為什么O 點一定是 重復的那個數字?

      我們來看之前的例子: index array 里,紅色的兩列是重復的數字。3 能走到 1, 5 也能走到1, 3和5 代表着兩條路,所以當兩條路匯合的地方,就是重復的數字1。

    6. 為什么x = z?

      換句話說,為什么第二次它們能夠相遇。這應該是最麻煩的地方。

      方法1: 驗證一下

        第一次相遇后: slow 走的路程 = x + y;fast 走的路程 = x + y + z + y;fast = x + 2y + z。

                  因為第一次是slow 走一步,fast 走兩步,所以 2slow = fast。

               代入公式:2x + 2y = x + 2y + z

                       x = z

        方法2:我們想象一下,

          情況1:如果slow 和fast 都從O點出發,slow 一次走一步,fast 一次走兩步,那么當它們相遇的時候,slow 走了一圈,fast 走了兩圈,而且它們一定相遇在O點;(可以在圖上畫刻度,自己走走看)

          情況2:如果我們多加一段path, 讓它們從S點出發,slow 一次走一步,fast 一次走兩步, 那么它們什么時候能相遇呢? 情況1中它們相遇在O點,那么多加一段path(x 是path的長度),它們會在O點的基礎上,提早x 的距離相遇,因為它們多走了一段x的path,所以fast 會在O之前就趕上slow。所以 x 肯定等於 z。

    7. 如果不太明白的話,可以畫一個類似的圖,再畫上刻度,自己多走幾次,感受一下。

    

Java Solution:

Runtime beats 51.39% 

完成日期:09/14/2017

關鍵詞:Array, Two Pointers

關鍵點:快慢指針相遇的點就是重復的數字

 

 1 class Solution 
 2 {
 3     public int findDuplicate(int[] nums) 
 4     {
 5         /* Solution 2: */
 6         int slow = 0, fast = 0;
 7         // fist meeting
 8         do
 9         {
10             slow = nums[slow]; // slow takes one step each time
11             fast = nums[nums[fast]]; // fast takes two steps each time
12             
13         }while(slow != fast);
14         
15         // second meeting
16         fast = 0;
17         
18         while(slow != fast)
19         {
20             slow = nums[slow]; // both slow and fast take one step each time
21             fast = nums[fast];
22         }
23         // when they meet, the place must be entry of circle, and must be duplicate
24         return slow;
25     }
26 }

 參考資料:

http://blog.csdn.net/monkeyduck/article/details/50439840

           

LeetCode 題目列表 - LeetCode Questions List

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM