有序数组中的单一元素

标签: 数组 二分查找

难度: Medium

给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。

请你找出并返回只出现一次的那个数。

你设计的解决方案必须满足 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

Submission

运行时间: 20 ms

内存: 22.9 MB

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        n = len(nums)
        begin,end = 0,n-1
        while begin<end:
            mid = (begin+end)//2
            if mid%2==0:
                if nums[mid]==nums[mid+1]:
                    begin = mid + 1
                else:
                    end = mid
            else:
                if nums[mid] == nums[mid-1]:
                    begin = mid + 1
                else:
                    end = mid
        return nums[end]

Explain

这个题解使用了二分查找的思路。因为数组是有序的,且除了一个元素只出现一次外其他元素都出现两次,所以可以利用元素的下标的奇偶性来缩小查找范围。如果 mid 是偶数下标,则比较 nums[mid] 和 nums[mid+1],如果两者相等,说明单一元素在 mid 之后,否则在 mid 及之前。如果 mid 是奇数下标,则比较 nums[mid] 和 nums[mid-1],如果两者相等,说明单一元素在 mid 之后,否则在 mid 及之前。最终 begin 和 end 会指向同一位置,即为单一元素的下标。

时间复杂度: O(logn)

空间复杂度: O(1)

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        n = len(nums)
        begin,end = 0,n-1
        while begin<end:
            mid = (begin+end)//2
            if mid%2==0:
                if nums[mid]==nums[mid+1]:
                    # 如果 mid 是偶数下标且 nums[mid]==nums[mid+1],说明单一元素在 mid 之后
                    begin = mid + 1 
                else:
                    # 否则单一元素在 mid 及之前
                    end = mid
            else:
                if nums[mid] == nums[mid-1]:
                    # 如果 mid 是奇数下标且 nums[mid]==nums[mid-1],说明单一元素在 mid 之后
                    begin = mid + 1
                else:
                    # 否则单一元素在 mid 及之前
                    end = mid
        # 最终 begin 和 end 会指向同一位置,即为单一元素的下标
        return nums[end]

Explore

在有序数组中,除了一个元素出现一次外,其余元素都成对出现。当`mid`是偶数时,如果`nums[mid]`和`nums[mid+1]`相等,说明`mid`和`mid+1`是一对,因此单一元素在`mid+2`及其之后的部分。反之,如果不相等,则说明单一元素在`mid`或`mid`之前的部分。当`mid`是奇数时,如果`nums[mid]`和`nums[mid-1]`相等,这也是一对,所以单一元素在`mid+1`及之后的部分。这种比较方式保证了每次都能排除至少一个已配对的元素,从而缩小查找范围。

如果`nums[mid] == nums[mid+1]`且`mid`为偶数,这意味着从数组开始到`mid`的位置,元素应该完全成对。因为成对元素的起始索引应为偶数,结尾索引为奇数,这对在`mid`和`mid+1`位置形成完整对。由此可以断定,单个出现的元素必定在`mid+2`或之后的位置。逻辑上,这确保了所有`mid`之前的元素都不可能是单一元素。

不会遗漏检查任何元素。循环条件`while begin < end`确保了只要`begin`不等于`end`,查找过程就会继续。在每次迭代中,要么`begin`增加,要么`end`减少,逐渐缩小查找范围。由于单一元素的存在,最终`begin`和`end`会聚焦在单一元素上。由于数组长度为奇数且除一元素外所有元素成对出现,最终这对元素将必然指向单一元素。

当二分查找的过程结束时,`begin`和`end`会指向同一个位置,这个位置就是未成对的单一元素。这是因为二分查找的逻辑确保每次都排除了成对的元素,并且不断缩小范围直到只剩下一个元素。由于题目假设保证有一个且只有一个单一元素,因此这个位置上的元素就是答案,无需进一步验证。