「这是我参与2022首次更文挑战的第27,活动详情查看:2022首次更文挑战」
540. 有序数组中的单一元素
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 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
分析
首先题目要求我们时间复杂度是O(log n),只要看到log n我们就要想到二分查找。
在题目中只有一个元素是单个的,其他元素都是成对出现的。也就是说明数组长度必然是奇数,数组最后一个元素下标必然是偶数;单一元素的左右俩测的元素个数必须也都是偶数。当然单一元素的下标必须是偶数。
我们结合二分查找进一步分析,在最一开始左边界是 0,右边界是数组的最大下标。我们每次都去找左边界与右边界的中间值mid进行判断。我们可以想象一下所有元素都是成对的前提下mid为偶数时nums[mid]与nums[mid+1]相等,mid为奇数时nums[mid]与nums[mid-1]相等。
这要符合上述分析就是说明单一元素的下标比mid大,否者就是比mid小。单一元素的下标比mid大时我们调整右边界的值,单一元素的下标比mid小时我们调整左边界的值。
我们可以通过例子来分析一下:
当有序数组为nums = [1,1,2,3,3,4,4,8,8]时,初始左边界 low = 0,初始右边界high = 8。
nums = [1,1,2,3,3,4,4,8,8]
low = 0 high = 8
计算中间值的时候我们需要加上左边界偏移量。
mid = (high - low) / 2 + mid
计算出现中间下标为 mid=4 此时中间下标为偶数,我们判断nums[mid]与nums[mid+1]是否相等。
nums[4] = 3
nums[5] = 4
显然时不相等的按照我们之前的说法单一元素元素的下标是比 mid 小的。我们将右边界的值直接调整为mid并再次计算中间值
low = 0 high = 4
mid = 2
计算出现中间下标为 mid=2 此时中间下标为偶数,我们判断nums[mid]与nums[mid+1]是否相等。
nums[2] = 2
nums[3] = 3
显然时不相等的按照我们之前的说法单一元素元素的下标是比 mid 小的。我们将右边界的值直接调整为mid并再次计算中间值。
low = 0 high = 2
mid = 1
计算出现中间下标为 mid=1 此时中间下标为奇数,我们判断nums[mid]与nums[mid-1]是否相等。
nums[1] = 1
nums[0] = 1
相等的按照我们之前的说法单一元素元素的下标是比 mid 大的。我们将左边界赋值为mid+1。
low = 2 high = 2
此时我们会发现左边界与右边界相等了这就是我们要找的单一元素。
num[2] = 2
逐步实现
首先我们先定义low为左边界初始值为0,high为右边界初始值为 nums.length - 1。
let low = 0
let high = nums.length - 1
当左边界与右边界相等时就说明单一元素就是我需要单一元素。
while (low === high) {
}
return nums[low];
不相等时我们通过之前分析的情况不断计算,这里我们可以通过位运算 >> 1 来计算中间值; 通过位运算 ^ 1来简化奇偶判断。
while (low !== high) {
const mid = ((high - low) >> 1) + low;
if (nums[mid] === nums[mid ^ 1]) {
low = mid + 1;
} else {
high = mid;
}
}
return nums[low];
代码实现
/**
* @param {number[]} nums
* @return {number}
*/
var singleNonDuplicate = function(nums) {
let low = 0
let high = nums.length - 1
while (low < high) {
const mid = ((high - low) >> 1) + low;
if (nums[mid] === nums[mid ^ 1]) {
low = mid + 1;
} else {
high = mid;
}
}
return nums[low];
};