题目描述

✅ 540. 有序数组中的单一元素

image-20260928213933998

题意分析

数组已经有序,只有一个元素出现一次,其余元素都出现两次,所以相同元素一定相邻,数组长度也一定是奇数。要求在 $O(\log n)$ 时间、$O(1)$ 额外空间内返回这个单次元素。

线性扫描或异或都要访问整个数组。要使用二分,需要找到一种能判断答案在中点哪一侧的规律;这里的规律是相邻元素的配对位置。

解法:偶数下标二分

核心思路

[!blue]

设单次元素的下标为 p。它前面的元素全部两两配对,因此 p 必为偶数。在 p 之前,每一对占据「偶数下标、下一个奇数下标」;越过 p 后,后面的配对整体错开一位,改为从奇数下标开始。

因此,对于一个偶数下标 mid,只需比较 nums[mid] 与 nums[mid + 1]:

  • 相等:这仍是一对正常配对的元素,说明 mid < p。这两个位置都不是答案,可以令 left = mid + 2。
  • 不等:配对已在这里或更早的位置错开,说明 p <= mid。答案可能恰好在 mid,所以令 right = mid。

始终把答案保留在闭区间 [left, right] 内。初始的两端都是偶数;每轮把中点向下调整为偶数,更新后的两端也仍为偶数。只要 left < right,区间至少有三个元素,调整后的 mid <= right - 2,所以访问 mid + 1 不会越界。

两个分支都会缩小区间,又不会丢掉答案。最终 left == right 时,剩下的唯一位置就是 p。

解题步骤

  1. 初始化 left = 0、right = n - 1,在 left < right 时循环。
  2. 计算中点;若 mid 为奇数,将其减一,确保它是一个候选数对的起点。
  3. 若 nums[mid] == nums[mid + 1],说明答案在这对元素之后,令 left = mid + 2。
  4. 否则令 right = mid,保留可能就是答案的 mid。
  5. 区间收敛后返回 nums[left]。

代码实现

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int left = 0;
        int right = nums.length - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;

            // 先把中点对齐到偶数下标,才能按配对是否错位判断方向。
            if (mid % 2 == 1) {
                mid--;
            }

            if (nums[mid] == nums[mid + 1]) {
                // mid 与 mid+1 成对,单一元素在右侧。
                left = mid + 2;
            } else {
                right = mid;
            }
        }

        return nums[left];
    }
}
func singleNonDuplicate(nums []int) int {
    left := 0
    right := len(nums) - 1
    for left < right {
        mid := left + (right-left)/2
        // 先把中点对齐到偶数下标,才能按配对是否错位判断方向。
        if mid%2 == 1 {
            mid--
        }
        if nums[mid] == nums[mid+1] {
            // 左侧配对完整,答案位于 [mid+2,right],包含 mid+2。
            left = mid + 2
        } else {
            right = mid
        }
    }
    return nums[left]
}

复杂度分析

  • 时间复杂度:$O(\log n)$,每轮排除约一半区间。
  • 空间复杂度:$O(1)$,只使用常数个下标变量。

关键点总结

[!green]

  • 有序性让相同元素相邻,单次元素让后续配对的起点由偶数变成奇数,形成可二分的分界。
  • 偶数 mid 与下一位相等,答案就在右侧;不等,答案就在 mid 或左侧。
  • 区间始终包含答案且两端都是偶数;单元素数组直接返回,答案位于首尾也无需特判。

易错点总结

[!yellow]

  • 不把 mid 对齐到偶数下标,会让「相等」的方向含义反转。
  • 将 mid 向上调整可能使 mid + 1 越界;应在奇数时执行 mid--。
  • 相等分支写成 left = mid + 1 会保留这一对的后一项,破坏偶数边界及后续判断依据。
  • 不等时写 right = mid - 1 可能跳过恰好位于 mid 的答案。
  • 循环条件必须是 left < right;单点区间继续访问 mid + 1 会越界。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/00553978
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!