目录

题目描述

136. 只出现一次的数字

image-20250420073851591

题意分析

数组中除某个元素只出现一次以外,其余每个元素均出现两次,要求找出那个只出现一次的元素并返回。

约束信号很硬:题目要求线性时间复杂度,且只使用常数级额外空间。这两条合在一起,直接排除了「哈希表计数」(空间 $O(n)$)和「先排序再找落单」(时间 $O(n \log n)$)这两条最顺手的路。

边界上注意:数组至少含一个元素,单元素数组直接返回它本身;元素可以为负数;答案保证存在且唯一。

解法:异或抵消重复元素

核心思路

问题关键:题目同时要求 $O(n)$ 时间和 $O(1)$ 额外空间,因此排序的 $O(n \log n)$ 和哈希表的 $O(n)$ 空间都不满足要求。我们只需消掉成对元素,不需要保存每个元素的次数。

为什么选异或:异或满足 a ^ a = 0a ^ 0 = a,并且具有交换律和结合律。把数组全部异或后,所有出现两次的数都能两两抵消,最终只剩出现一次的数。

不变量与正确性:遍历过程中,ans 始终等于已处理前缀所有元素的异或结果。遍历结束后,可借助交换律将相同元素放在一起,每一对都变为 0,因此 ans = 0 ^ single = single。负数使用补码参与按位异或,不需要特殊处理。

解题步骤

  • 初始化 ans = 0,因为 0 是异或运算的单位元。
  • 遍历数组,执行 ans ^= num
  • 返回 ans。例如 [4,1,2,1,2] 可重排理解为 4 ^ (1 ^ 1) ^ (2 ^ 2),结果为 4。

代码实现

class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for (int num : nums) {
            ans ^= num;
        }
        return ans;
    }
}
func singleNumber(nums []int) int {
    ans := 0
    for _, num := range nums {
        ans ^= num
    }
    return ans
}

复杂度分析

  • 时间复杂度:$O(n)$,每个元素只参与一次异或。
  • 空间复杂度:$O(1)$,只使用一个累积变量。

关键点总结

  • “其余元素恰好出现两次”是异或能直接抵消的前提。
  • 交换律和结合律保证重复元素无需相邻,也无需排序。
  • 若其余元素出现三次,要改用逐位计数模 3;若有两个落单元素,要先异或再按不同位分组。

易错点总结

  • ans = nums[0] 后仍从下标 0 遍历:首元素会被异或两次;[4,1,2,1,2] 将错误返回 0。
  • 先取绝对值[-1,2,2] 应返回 -1,补码异或本来就支持负数。
  • 照搬到其余元素出现三次的题目[2,2,3,2] 全部异或得到 1,不是 3。
  • 使用哈希表或排序:结果可以正确,但分别违反 $O(1)$ 空间或 $O(n)$ 时间要求,面试中不算最优解。

相似题目

题目 难度 考察点
137. 只出现一次的数字 II 中等 其余元素出现三次,异或失效,改为逐位求和后模 3
260. 只出现一次的数字 III 中等 两个数各只出现一次,按最低置位分组后各自异或
LCR 004. 只出现一次的数字 II 中等 与 137 同型,检验位计数写法的熟练度
剑指 Offer 56 - I. 数组中数字出现的次数 中等 与 260 同型,重点在「选哪一位分组」的推导
剑指 Offer 56 - II. 数组中数字出现的次数 II 中等 与 137 同型,可推广为「其余元素出现 m 次」的位统计