540. 有序数组中的单一元素(二分查找)

240 阅读1分钟

「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」。

每日刷题第48天 2021.02.16

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

解法

二分算法binarySearch:折半查找

  • 整体思路(三步走)
    • 预处理数据:将无序数组排列成有序的数组
    • 二分查找:使用while循环或者递归的方式对数组进行折半查找
    • 后处理:在剩余的空间中查找可行的候选者(后处理这一步也可以直接写在每次二分查找的过程中)

需要注意的点⚠️

  • 溢出问题:取lowhigh的中间值的时候,low + high可能会溢出(溢出:超过当前数据类型的最大值),可以将其修改为low + (high - low) / 2
  • 边界问题:在每次给lowhigh赋值的时候,需要想清楚边界范围。
    • 前闭后开[low,high),while循环条件low < high
    • 前闭后闭[low,high],while循环条件low <= high,因为[low.high]还存在一个数组未查找。

解题思路

  • 当有序数组中不存在不重复的数时,全部都符合偶数下标为数出现第一次,奇数下标为数出现第二次。
    • 举例:[4,4,6,6,8,8] 下标:0和1、2和3、4和5
  • 当有序数组中存在不重复的数后,上述规律被打破。出现不重复数的前半段还保留上述的规则,而不重复数的后半段变成了:奇数下标为数第一次出现,偶数下标为数第二次出现。
    • 举例:[4,4,6,6,3,8,8] 下标:0和1、2和3、4、5和6
  • 那么根据上述推导出来的规律,就可以进行判断,当前需要查找的不重复的数的区间。设需要查找的不重复的数为x
    • mid为偶数时,判断nums[mid] == nums[mid + 1]
    • mid为奇数时,判断nums[mid] == nums[mid - 1]
    • 若相等,则表示x > nums[mid](因为前半段规律并没有被破坏);否则,x < nums[mid]

小技巧(使用异或^

  • 异或:当两数相等时,异或为0;反之,为1
    • 举例:0 ^ 0 = 0 、1 ^ 1 = 0、0 ^ 1 = 1、1 ^ 0 = 1
/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNonDuplicate = function(nums) {
  // 二分查找
  let low = 0;
  let mid = 0;
  let high = nums.length - 1;
  while(low < high) {
    // ^ 1,偶数+1,奇数-1
    // 不相同,为1;相同为0
    mid = parseInt((high - low) / 2) + low;
    if(nums[mid] == nums[mid ^ 1]){
      // x > mid
      low = mid + 1;
    }else {
      // x < mid
      high = mid;
    }
  }
  return nums[low];
};