大家好,我是程序员小熊,来自大厂的程序猿。了解二分查找的童鞋,都知道二分查找常用于在有序数组中查找某一特定元素,而且很多童鞋也都知道二分查找的模板该怎么写。
今天小熊带来一道亚马逊的面试题,也就是力扣540. 有序数组中的单一元素,这道题难度为中等,采用“二分查找 + 动图”的方式深入剖析,供大家参考,希望对大家有所帮助。
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度
和 O(1)空间复杂度中运行。
本题如果不要求解法的时间复杂度为 O(log n) 的话,就跟力扣136. 只出现一次的数字差不多,只是后者不要求数组是有序的,但解法一样,可以通过异或去做,因为一个数字跟自身相异或,结果为 0;但异或 0,结果为自身,因此让数组中所有元素都相互异或即可得到结果,但时间复杂度为 O(n)。
由于题目明确要求解法的时间复杂度为 O(log n),对二分查找有所了解的童鞋,很自然地会想到需要采用二分查找法去做。
那具体如何通过二分查找去做呢?见下面例子。
举例
以数组 [3,3,7,7,10,11,11] 为例子,如下图示。
示例
二分查找一般通过数组的中间元素 nums[mid] 判断 target 的位置(在 mid 位置,亦或是在 mid 的左侧或右侧),本题也不例外。
确定中间元素
由题意可知,数组长度一定为奇数,因此可以进行如下操作:
如下图示:
1、判断 nums[mid] == nums[mid - 1];
2、移除 nums[mid] 和 nums[mid - 1];
3、判断拆分后的两数组的长度,并移除偶数长度子数组;
4、在奇数长度的子数组中重复前1、2、3步;
动态如下:
C
int singleNonDuplicate(int* nums, int numsSize){
int left = 0, right = numsSize - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
/* 判断(子)数组的长度,进而确认 target 位置 */
bool halvesAreEven = (right - mid) % 2 == 0;
/* 中间元素等于右边元素 */
if (nums[mid + 1] == nums[mid]) {
/* halvesAreEven 为偶数,mid 右侧存在 target */
if (halvesAreEven) {
left = mid + 2;
/* halvesAreEven 为奇数,mid 左侧存在 target */
} else {
right = mid - 1;
}
/* 中间元素等于左侧元素 */
} else if (nums[mid - 1] == nums[mid]) {
/* halvesAreEven 为偶数,mid 左侧存在 target */
if (halvesAreEven) {
right = mid - 2;
/* halvesAreEven 为奇数,mid 右侧存在 target */
} else {
left = mid + 1;
}
/* 中间元素跟左右侧元素都不等,中间元素是target */
} else {
return nums[mid];
}
}
return nums[left];
}
上面的代码略显冗余,if 跟 else if 中均需要先判断 nums[mid] 与两侧的元素是否相等,再判断 halvesAreEven 是否为奇数,然后决定在 mid 的左侧还是右侧查找,有没有简便一点的方法?
答案是有的。
举例
以数组 [1,1,2,3,3,4,4] 为例子,如下动图示。
示例动图
C++
int singleNonDuplicate(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int temp = mid ^ 1;
if (nums[mid] == nums[temp]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
Golang
func singleNonDuplicate(nums []int) int {
left, right := 0, len(nums) - 1
for left < right {
mid := left + (right - left) / 2;
temp := mid ^ 1;
if nums[mid] == nums[temp] {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left]
}
Python3
def singleNonDuplicate(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (right + left) // 2;
temp = mid ^ 1;
if nums[mid] == nums[temp]:
left = mid + 1
else:
right = mid
return nums[left]
复杂度分析
空间复杂度:O(1),未开辟额外存储空间。
时间复杂度:O(logn),n 为数组长度。