「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。
题目
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。
示例 1:
输入:nums = [1,1,2,3,3,4,4,8,8]
输出:2
示例 2:
输入:nums = [3,3,7,7,10,11,11]
输出:10
思路
挺有意思的题目,一看到的时候,想起了有一题返回的只出现1次数字,可以一直用异或,相等的数字刚好可以异或消掉。不过这样的时间复杂度是O(n),既然要求是 O(log n),肯定不能全部遍历一遍。看到这个时间复杂度,再加上有序数组,联想到二分查找。
假设这个唯一的数字下标是index,首先index一定是偶数,因为在num[index]之前,所有的数字都是成双成对出现,所以index一定是偶数。另外,在num[index]之后,所有的数字也是成双成对出现,所以存在:
- 那么在index之前,x是偶数时,num[x]==num[x+1]
- 那么在index之前,x是奇数时,num[x]==num[x+1] 所以,我们可以按照如下做二分法:
- 因为index一定是偶数,所以mid只对偶数来看就好,如果mid是奇数,那就看mid-1
- 如果num[mid]==num[mid+1],那么说明index一定在mid后面,用mid+2更新left;否则,说明index一定在mid前面,用mid更新right 另外,在题解里面,看到一个挺巧妙的处理。上面说过,index一定是偶数,所以mid只查看偶数的就好,那么(left+right)/2是奇数的时候,我们用mid-1。下面的代码非常简洁就达到了这个点:
mid -= mid & 1;
- mid是偶数的时候,mid & 1 = 0
- mid是奇数的时候,mid & 1 = 1 所以上面的代码就刚好表达了偶数不变,奇数-1的效果。
Java版本代码
class Solution {
public int singleNonDuplicate(int[] nums) {
int left = 0;
int right = nums.length-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];
}
}