[路飞]_leetcode 540. 有序数组中的单一元素

192 阅读1分钟

「这是我参与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];
};