1. 程式人生 > >287. Find the Duplicate Number

287. Find the Duplicate Number

you 原理 mat 行動 row dup pac dex strong

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.

題意:有n+1個數字,範圍從1到n,其中有一個數字會重復多次,用低於O(n2)的時間復雜度算法找出重復的數字,空間復雜的為O(1)。

解法一:思路是采用了二分法+抽屜遠離。首先解釋一下為什麽用二分法,因為O(n2)時間復雜度不能A,所以往下應該是n*logn,很容易聯想到二分法,因為其復雜度為logn。

抽屜原理是說假設你有11個蘋果,要放進10個抽屜,那麽至少有一個抽屜裏是有兩個蘋果的。

對應到這題,1~n的n+1個數字,有1個數字會至少重復兩次。

比如取數組為{1,2,2,3,4,5},一共6個數,範圍是1~5,其中位數應該是(5+1)/2 = 3,那麽,如果小於等於3的數的個數如果超過了3,那麽重復的數字一定出現在[1,3]之間,否則出現在[4,5]之間。以該數組為例,中位數為3,小於等於3的數一共有4個,大於3的數有兩個,所以重復的數字在[1,3]之間。

 1 int findDuplicate(vector<int>& nums)
 2 {  
 3     int
low = 1, high = nums.size()-1; //low和high為數字的取值範圍 4 5 while(low<high) 6 { 7 int cnt = 0; //cnt為不大於中位數的數字個數 8 int mid = (low + high)/2; 9 for(int i=0;i<nums.size();i++) 10 { 11 if(nums[i] <= mid) 12 cnt++; 13 } 14 if(cnt>mid) 15 { 16 high = mid; //如果不大於mid的數字個數比mid多的話,則重復數字應該出現在[low, mid]之間 17 } 18 else 19 low = mid+1; //如果不大於mid的數字個數比mid少的話,說明重復的數字出現在後半段中[mid+1,high] 20 } 21 return low; 22 }

方法二:檢測環路法

基本思想是將數組抽象為一條線和一個圓環,因為1~n 之間有n+1個數,所以一定有重復數字出現,所以重復的數字即是圓環與線的交匯點。然後設置兩個指針,一個快指針一次走兩步,一個慢指針一次走一步。當兩個指針第一次相遇時,令快指針回到原點(0)且也變成一次走一步,慢指針則繼續前進,再次回合時即是線與圓環的交匯點。

把數組抽象成線和圓環,舉例來說,假設我們有一個數組是nums[]=[1,2,3,4,5,5,6,7],pf代表快指針,ps代表慢指針,初始ps指向nums[0],即1,pf指向nums[nums[0]],即2,行動一次後,ps指向nums[1],即2,pf指向nums[nums[2]],即4,再動一次,ps指向nums[2],即3,pf則指向了nums[nums[4]],即5;可以發現pf一旦指向5後便不會再動,因為nums[5]一直為5,直到ps慢慢追上,然後令pf從頭開始,ps一直在5處停留,最後定會相遇在這裏,而這裏就是重復數字。這裏舉了個最簡單的例子,是為了方便大家理解,實際上實際的圓環順序與數組的順序是沒有關系的,不信可以自己在紙上畫一畫,當數組變成nums[]=[4,6,5,1,3,2,5,7]的樣子,你會更加理解這個算法的!

 1         if (nums.length > 1) {
 2             int slow = nums[0];
 3             int fast = nums[nums[0]];
 4             while (slow != fast) {
 5                 slow = nums[slow];
 6                 fast = nums[nums[fast]];
 7             }
 8 
 9             fast = 0;
10             while (fast != slow) {
11                 fast = nums[fast];
12                 slow = nums[slow];
13             }
14             return slow;
15         }
16         return -1;

方法三:遍歷過得元素置為負數。因為是從頭到尾順序遍歷的,如果再次指向某個負數的時候,表示前面一定出現過同樣的元素值導致此位置變成了負數

 1         int n = nums.length;
 2         if(n == 0) return -1;
 3         for (int i=0;i<nums.length;i++)
 4         {
 5             int index = Math.abs(nums[i])-1;
 6             if (nums[index] < 0 )
 7             {
 8                 return Math.abs(index+1);
 9             }else
10             nums[index] = -nums[index];
11         }
12         return -1;

287. Find the Duplicate Number