1. 程式人生 > >287. Find the Duplicate Number 尋找重複數

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 element 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 extra space.
  3. Your runtime complexity should be less than O(n2).

Credits:
Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.

這道題給了我們n+1個數,所有的數都在[1, n]區域內,首先讓我們證明必定會有一個重複數,這不禁讓我想起了小學華羅庚奧數中的抽屜原理(又叫鴿巢原理), 即如果有十個蘋果放到九個抽屜裡,如果蘋果全在抽屜裡,則至少有一個抽屜裡有兩個蘋果,這裡就不證明了,直接來做題吧。題目要求我們不能改變原陣列,即不能給原陣列排序,又不能用多餘空間,那麼雜湊表神馬的也就不用考慮了,又說時間小於O(n2),也就不能用brute force的方法,那我們也就只能考慮用二分搜尋法了,我們在區別[1, n]中搜索,首先求出中點mid,然後遍歷整個陣列,統計所有小於等於mid的數的個數,如果個數大於mid,則說明重複值在[mid+1, n]之間,反之,重複值應在[1, mid-1]之間,然後依次類推,直到搜尋完成,此時的low就是我們要求的重複值,參見程式碼如下:

解法一:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int low = 1, high = nums.size() - 1;
        while (low < high) {
            int mid = low + (high - low) * 0.5;
            int cnt = 0;
            for (auto a : nums) {
                if (a <= mid) ++cnt;
            }
            if (cnt <= mid) low = mid + 1;
            else high = mid;
        }
        return low;
    }
};

筆記:如果要求時間複雜度不能為O(n)或者O(n^2),可以考慮使用額外空間(雜湊表),或者採用二分查詢法(時間複雜度為O(nlog(n)))。

經過熱心網友的留言提醒還有一種O(n)的解法,並給了參考帖子,發現真是一種不錯的解法,其核心思想快慢指標在之前的題目Linked List Cycle II中就有應用,這裡應用的更加巧妙一些,由於題目限定了區間[1,n],所以可以巧妙的利用座標和數值之間相互轉換,而由於重複數字的存在,那麼一定會形成環,我們用快慢指標可以找到環並確定環的起始位置,確實是太巧妙了!

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0, t = 0;
        while (true) {
            slow = nums[slow];
            fast = nums[nums[fast]];
            if (slow == fast) break;
        }
        while (true) {
            slow = nums[slow];
            t = nums[t];
            if (slow == t) break;
        }
        return slow;
    }
};


筆記:首先確定對映關係,從而形成帶有環的連結串列。其次知道連結串列環的入口即為需要求解的重複資料,先用快慢指標找到環內某一節點,再從該節點和原點同時出發,經數學推導可知,它們首次相遇的節點即為連結串列環的入口,即為ans。