题目描述

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

image-20260929005619251

题意分析

升序数组中,只有一个元素出现一次,其余元素都恰好出现两次,要求返回这个单独元素的值。进阶要求时间为 $O(\log n)$、额外空间为 $O(1)$,需要利用有序性定位它。

排序使相同元素相邻。单独元素之前的元素能够完整分成若干对,所以单独元素的下标必为偶数,整个数组长度必为奇数;答案可能在任意一端,也可能就是长度为一的数组中的唯一元素。

解法:按奇偶配对关系二分

核心思路

[!blue]

从下标零开始,原本应当把相邻的偶数下标与奇数下标配成一组。单独元素出现之前,真实相等对正好符合这个分组;单独元素占去一个位置之后,后续相等对便整体错位,变成“奇数下标在前、偶数下标在后”。

因此只需检查某个位置是否仍与原分组中的伙伴相等。用 mid ^ 1 得到这个伙伴:偶数下标加一,奇数下标减一。若两值相等,mid 仍在单独元素左侧,答案必在更右边;若不等,说明已经到达或越过错位点,答案在 mid 或其左侧。

维护闭区间 [left,right] 始终包含答案。匹配时令 left = mid+1,排除已确认完整配对的左侧;不匹配时令 right = mid,保留可能就是答案的中点。区间不断缩小,最终只剩一个位置。

配对访问安全的原因是循环条件 left < right:向下取整的中点始终满足 mid < right <= n-1。偶数中点的伙伴 mid+1 不超过最后下标;奇数中点至少为一,伙伴 mid-1 也不会为负。并不是靠强行调整中点奇偶来避免越界。

解题步骤

  1. 初始化 left = 0、right = n-1,将两端可能的答案都纳入候选。
  2. 当 left < right 时,取下中点,比较 nums[mid] 与 nums[mid ^ 1]。
  3. 相等就排除中点及其左侧;不相等就把中点保留为右边界。
  4. 两端相等后返回 nums[left],题目要的是元素值。

长度为一时不执行配对访问,直接返回唯一元素。若答案在最后一位,二分会逐步将左边界推到最后,但不会拿最后的偶数下标去访问它右边不存在的伙伴。

代码实现

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

        while (left < right) {
            int mid = (left + right) >> 1;

            if (nums[mid] != nums[mid ^ 1]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }

        return nums[left];
    }
}
func singleNonDuplicate(nums []int) int {
    left, right := 0, len(nums)-1
    for left < right {
        mid := (left + right) >> 1
        if nums[mid] != nums[mid^1] {
            right = mid
        } else {
            left = mid + 1
        }
    }
    return nums[left]
}

复杂度分析

  • 时间复杂度:$O(\log n)$,每轮只做一次配对比较,并将候选数量缩小约一半。
  • 空间复杂度:$O(1)$,只维护二分下标。

关键点总结

[!green]

  • 二分依据是配对关系在唯一元素处发生错位,而不是元素与某个已知目标的大小关系。
  • mid ^ 1 只是按原来的偶奇分组寻找伙伴,真正的边界安全来自下中点严格小于右端点。
  • 不匹配时中点仍可能是答案,需要保留;匹配时中点已经确定在答案左侧,可以排除。

易错点总结

[!yellow]

  • 把循环写成 left <= right 后仍直接访问配对伙伴,会在只剩最后一个位置时越界。
  • 匹配时令 left = mid,两候选区间可能无法继续缩小。
  • 不匹配时令 right = mid-1,可能丢掉单独元素本身。
  • 返回下标或随意调整中点奇偶,却没有同步修改区间更新逻辑。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/22806144
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!