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

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 = 1, 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;
    }
};

 

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

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/287

  

類似題目:

First Missing Positive

Missing Number

Single Number

Find All Numbers Disappeared in an Array

Set Mismatch

Array Nesting

Linked List Cycle II

 

參考資料:

https://leetcode.com/problems/find-the-duplicate-number/

https://leetcode.com/problems/find-the-duplicate-number/discuss/72872/O(32*N)-solution-using-bit-manipulation-in-10-lines

https://leetcode.com/problems/find-the-duplicate-number/discuss/73045/Simple-C%2B%2B-code-with-O(1)-space-and-O(nlogn)-time-complexity

https://leetcode.com/problems/find-the-duplicate-number/discuss/72846/My-easy-understood-solution-with-O(n)-time-and-O(1)-space-without-modifying-the-array.-With-clear-explanation.

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

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



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