剑指 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];
    }
};

posted @ 2022-09-25 22:47  ccnju  阅读(35)  评论(0)    收藏  举报