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

[LeetCode] 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.

Example 1:

Input: [1,3,4,2,2]
Output: 2

Example 2:

Input: [3,1,3,4,2]
Output: 3

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),也就不能用brute force的方法,那我們也就只能考慮用二分搜尋法了,我們在區間[1, n]中搜索,首先求出中點mid,然後遍歷整個陣列,統計所有小於等於mid的數的個數,如果個數小於等於mid,則說明重複值在[mid+1, n]之間,反之,重複值應在[1, mid-1]之間,然後依次類推,直到搜尋完成,此時的low就是我們要求的重複值,參見程式碼如下:

解法一:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size();
        while (left < right){
            int mid = left + (right - left) / 2, cnt = 0;
            for (int num : nums) {
                if (num <= mid) ++cnt;
            }
            if (cnt <= mid) left = mid + 1;
            else right = mid;
        }    
        return right;
    }
};

經過熱心網友的留言提醒還有一種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;
    }
};

這道題還有一種位操作Bit Manipulation的解法,也十分的巧妙。思路是遍歷每一位,然後對於32位中的每一個位bit,我們都遍歷一遍從0到n-1,我們將0到n-1中的每一個數都跟bit相‘與’,若大於0,則計數器cnt1自增1。同時0到n-1也可以當作nums陣列的下標,從而讓nums陣列中的每個數字也跟bit相‘與’,若大於0,則計數器cnt2自增1。最後比較若cnt2大於cnt1,則將bit加入結果res中。這是為啥呢,因為對於每一位,0到n-1中所有數字中該位上的1的個數應該是固定的,如果nums陣列中所有數字中該位上1的個數多了,說明重複數字在該位上一定是1,這樣我們把重複數字的所有為1的為都累加起來,就可以還原出了這個重複數字,參見程式碼如下:

解法三:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int res = 0, n = nums.size();
        for (int i = 0; i < 32; ++i) {
            int bit = (1 << i), cnt1 = 0, cnt2 = 0;
            for (int k = 0; k < n; ++k) {
                if ((k & bit) > 0) ++cnt1;
                if ((nums[k] & bit) > 0) ++cnt2;
            }
            if (cnt2 > cnt1) res += bit;
        }
        return res;
    }
};

類似題目:

參考資料: