题目
给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
你设计的解决方案必须满足 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 <= nums.length <= 1050 <= nums[i] <= 105
题解
题目要求时间复杂度为O(logN), 想到用二分查找
以 nums = [1,1,2,3,3,4,4,8,8] 为例,假设只出现一次的元素为X, 则 x = 2, 此时, x的左侧为[1,1]有2个元素,x的右侧为[3,3,4,4,8,8]有6个元素, 发现 x 的左右两侧均有偶数个元素,且数组的长度为奇数
由于数组是有序且递增的,数组中的相同元素必然相邻。 所以可以推出以下结论:
x的左边下标y, 如果nums[y] === nums[y+1], 则y为偶数x的右边下标z, 如果nums[z] === nums[z+1], 则z为奇数
由于x是相同元素的开始下标的奇偶性分界,因此可以使用二分查找寻找下标 x
初始化左右边界left = 0, right = nums.length - 1。每次取左右边界的中间值mid = Math.floor((right - left ) / 2) + left。 根据 mid的奇偶性来决定和左边还是右边的相邻元素比较:
- 如果
mid是偶数,则比较nums[mid]和nums[mid+1]是否相等 - 如果
mid是奇数,则比较nums[mid]和nums[mid-1]是否相等
如果比较的结果是相等的,则x > mid, 更新左边界left = mid, 如果是不等的,则x <= mid, 更新右边界right = mid。一直继续二分查找,直到left === right确定下标x的值,则nums[x]为只出现一次的数字
小技巧
利用位运算的按位异或性质,可以得到mid和相邻数之间的关系:
- 当
mid为偶数时,mid + 1 === mid ^ 1 - 当
mid为奇数时,mid - 1 === mid ^ 1
因此,在二分查找中,可以利用mid^1性质,不需要判断mid的奇偶性
参考代码
/**
* @param {number[]} nums
* @return {number}
*/
var singleNonDuplicate = function(nums) {
// 声明二分查找的左右指针,分别指向数组的开头和末尾
let left = 0, right = nums.length - 1;
while(left < right) {
let mid = Math.floor((right - left) / 2) + left;
// mid 为偶数, mid + 1 = mid ^ 1
// mid 为奇数, mid - 1 = mid ^ 1
// 位运算,简化奇偶判断
if (nums[mid] == nums[mid ^ 1]) {
// 说明 mid 不是只出现一次的数字, 所以往右半区搜索,更新left指针
left = mid + 1;
} else {
// 说明mid 可能是只出现一次的数字,所以往左半区搜索,更新right指针
right = mid;
}
}
return nums[right];
};