描述
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。
思路
题目指出重复的数字只会出现两次,且数组有序,故含有单一元素的部分必然含有 2n - 1 个数,再如下分析:
说明:当 mid % 2 = 0 时,更新规则为left = mid (或right = mid) 也可(后面为了契合改进的代码,使用 left = mid (right = mid) 更新规则);
边界分析:
按图中的更新规则,范围会朝含有单一元素的部分缩小,边界情况下,会出现abb或bba的情况,此时再更新会将范围缩小至 a,所以在下一次迭代时判断是否为边界值 (0 或 nums.size()- 1)即可;
代码
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int n = nums.size() - 1;
int left = 0, right = n, mid;
while (left <= right) {
mid = (left + right) / 2;
if (mid == 0 or mid == n)
return nums[mid];
if (nums[mid] == nums[mid + 1])
if (mid % 2 == 1)
right = mid - 1;
else
left = mid;
else if (nums[mid] == nums[mid - 1])
if (mid % 2 == 1)
left = mid + 1;
else
right = mid;
else
return nums[mid];
}
return 0;
}
};
改进(参考LeetCode官方):
利用按位异或的性质,可以得到 mid 和相邻的数之间的如下关系,其中 ⊕ 是按位异或运算符:
当 mid 是偶数时,mid + 1 = mid ⊕ 1;
当 mid 是奇数时,mid − 1 = mid ⊕ 1。
且 nums[mid] = nums[mid ^ 1] 时都是更新 left,不过偶数情况下是 left = mid,奇数情况下是 left = mid + 1,此时可以利用与运算更新:
left = mid + (mid & 1);
改进后的代码:
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int n = nums.size() - 1;
int left = 0, right = n, mid;
while (left <= right) {
mid = (left + right) / 2;
if (mid == 0 or mid == n or (nums[mid] != nums[mid - 1] and nums[mid] != nums[mid + 1]))
return nums[mid];
if (nums[mid] == nums[mid ^ 1])
left = mid + (mid & 1);
else
right = mid - (mid & 1);
}
return 0;
}
};