leetcode-有序数组中的单一元素

144 阅读2分钟

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

题目

给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 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次数字,可以一直用异或,相等的数字刚好可以异或消掉。不过这样的时间复杂度是O(n),既然要求是 O(log n),肯定不能全部遍历一遍。看到这个时间复杂度,再加上有序数组,联想到二分查找。
假设这个唯一的数字下标是index,首先index一定是偶数,因为在num[index]之前,所有的数字都是成双成对出现,所以index一定是偶数。另外,在num[index]之后,所有的数字也是成双成对出现,所以存在:

  • 那么在index之前,x是偶数时,num[x]==num[x+1]
  • 那么在index之前,x是奇数时,num[x]==num[x+1] 所以,我们可以按照如下做二分法:
  • 因为index一定是偶数,所以mid只对偶数来看就好,如果mid是奇数,那就看mid-1
  • 如果num[mid]==num[mid+1],那么说明index一定在mid后面,用mid+2更新left;否则,说明index一定在mid前面,用mid更新right 另外,在题解里面,看到一个挺巧妙的处理。上面说过,index一定是偶数,所以mid只查看偶数的就好,那么(left+right)/2是奇数的时候,我们用mid-1。下面的代码非常简洁就达到了这个点:
mid -= mid & 1;
  • mid是偶数的时候,mid & 1 = 0
  • mid是奇数的时候,mid & 1 = 1 所以上面的代码就刚好表达了偶数不变,奇数-1的效果。

Java版本代码

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int left = 0;
        int right = nums.length-1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            mid -= mid & 1;
            if (nums[mid] == nums[mid+1]) {
                left = mid + 2;
            } else {
                right = mid;
            }
        }
        return nums[left];
    }
}