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:
- You must not modify the array (assume the array is read only).
- You must use only constant, O(1) extra space.
- Your runtime complexity should be less than O(n2).
- 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
類似題目:
Find All Numbers Disappeared in an Array
參考資料:
https://leetcode.com/problems/find-the-duplicate-number/