题目
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。
解法一:二分查找
因为本题目有时间复杂度的要求,因此使用二分查找的方法解决。
当取得mid索引后有三种情况:
- nums[mid-1]!=nums[mid]且nums[mid+1]!=nums[mid],mid即为单一元素。
- nums[mid-1]==nums[mid]
此时需要判断mid与right之间的元素个数right-mid,如果right-mid为偶数,则单一元素在left与mid-1之间。 - nums[mid+1]==nums[mid]
此时需要判断mid与left之间的元素个数mid-left,如果mid-left为偶数,则单一元素在mid+1与right之间。
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]==nums[mid+1]){
if((mid-left)%2==0){
left=mid+2;
}else
right=mid-1;
}else if(nums[mid]==nums[mid-1]){
if((right-mid)%2==0){
right=mid-2;
}else
left=mid+1;
}else
return nums[mid];
}
return nums[right];
}
};
解法二:位运算的二分查找
假设x为单一元素,则x左右两边都有偶数个元素。当nums[mid]为x左边元素时需要满足的条件为:
- 当mid为偶数时,nums[mid]==nums[mid+1]
- 当mid为奇数时,nums[mid]==nums[mid-1]
因此,当nums[mid]满足以上条件时,说明单一元素在mid的右边,否则在mid的左边。
实际上并不需要判断mid的奇偶性,根据异或的性质,我们可以得到:
当 m i d 为 偶 数 时 , m i d + 1 = m i d ⊕ 1 当mid为偶数时,mid+1=mid\oplus1 当mid为偶数时,mid+1=mid⊕1
当 m i d 为 奇 数 时 , m i d − 1 = m i d ⊕ 1 当mid为奇数时,mid-1=mid\oplus1 当mid为奇数时,mid−1=mid⊕1
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]==nums[mid^1])
left=mid+1;
else
right=mid;
}
return nums[left];
}
};
解法三:偶数下标的二分查找
假设x为单一元素,则x左边有偶数个元素,如果mid为偶数,则nums[mid]==nums[mid+1]成立。
我们只需要对偶数下标进行二分查找,初始left=0,right=nums.size()-1;
当二分查找过程中mid为奇数时,mid-=1保证mid为偶数。如果满足nums[mid]==nums[mid+1],则x在mid的右边,否则x在mid的左边。
在查找过程中,并不需要对mid的奇偶性进行进行判断,当mid为偶数时,mid&1=0,当mid为奇数时,mid&1=1。
因此在得到mid值之后,mid减去mid&1即可保证mid为偶数,如果mid为偶数,mid-mid&1值不变;如果mid为奇数,mid-mid&1将mid减一变为偶数。
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
mid-=mid&1;
if(nums[mid]==nums[mid+1])
left=mid+2;
else
right=mid;
}
return nums[left];
}
};