剑指 Offer II 070. 排序数组中只出现一次的数字
首先这个题目O(N)的做法很显然了,因为其他数字都出现两次,那么两两异或就是0,而0异或任意数字都不变,因此我们可以遍历一趟,逐个异或得到最终的答案,但是这个题目的数组还有个性质,也就是已经排好序了,这是一个强烈的暗示---二分。
对于二分我们要找一个二段性,也就是依据什么可以把数组分成两部分,我们可以研究这个数组,假设x是那个我们要找的数字,那么对于x前面的数字,[a, a+1]相等的话
a是奇数,a+1是偶数,对于x后面的数字,[a, a + 1]a是偶数,a+1是奇数,因此我们有
mid如果是奇数:
与[mid+1]相同,答案在前半截
与[mid-1]相同,答案在后半截
mid如果是偶数:
与[mid+1]相同,答案在后半截
与[mid-1]相同,答案在前半截
这里我们其实已经可以直接开始讨论了,但是这里题解给出了一个很巧妙的统一方法:
对于一个奇数,odd ^ 1 = odd - 1;
对于一个偶数,even ^ 1 = even + 1;
我们惊奇的发现,对于mid,我们只要判断nums[mid ^ 1] ==? nums[mid]就可以知道taget是不是在当前mid的后半截了
用代码来描述就是
if(nums[mid] == nums[mid ^ 1] {
修改左区间 l = mid // 依据定义修改
}
如果我们定义考虑的是区间(l, r]
代码就是这样:
int l = 0, r = nums.size() - 1;
while(l < r) {
int mid = l+ + (r - l)/2;
if(nums[mid] == nums[mid ^ 1]) {
l = mid + 1;
}
else {
r = mid;
}
}
return nums[l];
完整代码:
```cpp
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while(left < right) {
//(l, r]
int mid = left + (right - left)/2;
if(nums[mid] == nums[mid ^ 1]) {
/*mid如果是奇数:
与[mid+1]相同,答案在前半截
与[mid-1]相同,答案在后半截
mid如果是偶数:
与[mid+1]相同,答案在后半截
与[mid-1]相同,答案在前半截*/
left = mid + 1;
}
else {
right = mid;
}
}
return nums[left];
}
};

浙公网安备 33010602011771号