1. 解析
题目大意,在(n+1)个元素(数值的范围为1-n)组成的数组中 ,查找出重复出现的元素,要求时间复杂度小于O(n^2),空间复杂度为O(1).
2. 分析
如果不限制空间复杂度,还是很容易想到一种解法,利用hashtable记录出现过的元素,很容易找出重复的元素,或者直接对数组进行排序,也很方便检测出重复出现的元素;其实,本题最本质是考察鸽巢原理,即将10个苹果放在9个空抽屉里面,则必定有一个抽屉存放至少2个苹果。蛮简单的原理,然而,我并没有想到,哈哈~~~
left表示左边的边界数值,right表示右边的边界数值,取中间值mid,遍历整个数组,cnt记录数组中小于或等于mid的元素个数,若cnt <= mid,则表示重复的元素不在[left, mid]范围内,继续从[mid + 1, right]范围内查找;反之,在[left, mid]范围内查找。鸽巢原理嘛,给你mid个抽屉,如果最多有cnt个苹果,肯定不会重复放置
class Solution {
public:
int findDuplicate(vector<int>& nums){
int left = 1, right = nums.size();
while (left < right){
int mid = (left + right) / 2, cnt = 0;
for (auto num : nums) if (num <= mid) cnt++; //提供mid个抽屉,记录能摆放下的元素个数
if (cnt <= mid) left = mid + 1; //当前抽屉数 >= 元素个数
else right = mid;
}
return right;
}
};
3. 位运算
看到这道题我的第一反应是肯定涉及位运算,可惜苦思右想,没有想到解法~~~,我更喜欢第一种解法,因为位运算如果之前没有碰到过类似的,很大程度上是想不到的~~~
简单讲解一下思想,利用32位表示元素中的每个元素,因为数组中的元素数值范围为[1, n],所以如果某个数值重复出现,那么在对应的位表示上出现1的个数肯定会大于正常情况下出现1的个数
class Solution {
public:
int findDuplicate(vector<int>& nums){
int res = 0;
for (int i = 0; i < 32; ++i){
int bit = 1 << i, cnt1 = 0, cnt2 = 0;
for (int k = 0; k < nums.size(); k++){
if (k & bit) cnt1++;
if (nums[k] & bit) cnt2++;
}
if (cnt1 < cnt2) res += bit;
}
return res;
}
};