剑指 Offer II 070(力扣540):排序数组中只出现一次的数字(Java二分查找)

99 阅读1分钟

一、题目描述

给定一个只包含整数的有序数组 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 <= 105

0 <= nums[i] <= 105

二、思路讲解

2.1 哈希表

找个数的题目,用HashMap可以通杀。使用哈希表保存每个数字出现的次数,然后找到值为1的即可。

时间复杂度: O(N)

空间复杂度: O(N)

2.2 一次循环

因为数组本身是有序的(即使的无序的也可以给他排序),所以相等的数字必然相邻,那么我们可以一遍循环,找到一个数字,他的左边和右边跟自己都不相等,这个数字就是只出现一次的数字。

时间复杂度: O(N)

空间复杂度: O(1)

2.3 二分查找

然而,题目中要求的时间复杂度为对数级。由经验可得,有序数组的查找基本上可以使用二分查找,因此我们取左指针low指向最左,右指针high指向最右,中点mid指向中间,可以注意到:

1、当mid为偶数时:

——如果nums[mid]跟左边的数字相等,那么所求数字一定出现在mid的左边,可以直接令high = mid -1。例如:

                            1        1        2        3        3        4        4        5        5

                            low                                 mid                                   high

    ——如果nums[mid]跟右边数字相等,那么所求数字一定出现在mid右边。例如:

                            1        1        2        2        3        3        4        5        5

2、当mid为奇数时:

    ——如果nums[mid]跟左边数字相等,那么所求数字定出现在mid右边。例如:

                  1        1        2        2        3        3        4        4        5        6        6

                low                                              mid                                            high

    ——如果nums[mid]跟右边数字相等,则相反。例如:

                  1        1        2        3        3        4        4        5        5        6        6

这样我们就已经确定了二分查找的大致思路。

剩下的困难就是边界的判断问题了,我们一起看看代码里是怎么做的:

class Solution {
public int singleNonDuplicate(int\[] nums) {
if(nums.length==1 || nums\[0]!=nums\[1]) {
return nums\[0];
}
int low = 0;
int high = nums.length - 1;
while(low <= high) {
//这样写在任何情况下都不会溢出
int mid = (high - low) / 2 + low;
//如果mid达到了边界,就给left或right一个不可能的值,这样可以简化边界的判断
int left = (mid>0)? nums\[mid-1] : -1;
int right = (mid\<nums.length-1)? nums\[mid+1] : Integer.MAX\_VALUE;
//如果nums\[mid]跟两边都不相等,说明我们找的就是他
if(nums\[mid]!=left && nums\[mid]!=right) {
return nums\[mid];
}
if(mid%2 == 0) {
if(nums\[mid] == right) {
low = mid + 1;
} else{
high = mid -1;
}
} else {
if(nums\[mid] == left) {
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return nums\[low];
}
}

时间复杂度: O(logN)

空间复杂度: O(1)

2.4 位运算

利用按位异或的性质,可以减少对mid判断奇偶性的步骤,减少代码量,算得上是奇技淫巧了:

当mid为偶数时,mid + 1 = mid ^ 1;

当mid为奇数时,mid - 1 = mid ^ 1;

class Solution {
public int singleNonDuplicate(int\[] nums) {
int low = 0, high = nums.length - 1;
while (low < high) {
int mid = (high - low) / 2 + low;
if (nums\[mid] == nums\[mid ^ 1]) {
low = mid + 1;
} else {
high = mid;
}
}
return nums\[low];
}
}