题目描述

✅ 136. 只出现一次的数字

image-20260928195325738

image-20260928195325739

题意分析

一个非空整数数组中,恰好有一个元素出现一次,其他每种元素都出现两次,需要返回这个只出现一次的值。重复元素不一定相邻,数组也不保证有序,元素可以是零或负数。

题目要求线性时间和常数额外空间。哈希表统计次数会占用额外线性空间,先排序再寻找则需要额外排序时间;应利用“其他元素成对出现”这个条件直接消去重复值。

解法:异或抵消重复元素

核心思路

[!blue]

异或 ^ 按二进制位运算:同一位相同得到 0,不同得到 1。因此,一个数与自身异或会让每一位都变成零,即 x ^ x = 0;一个数与零异或不改变任何位,即 x ^ 0 = x。

异或还满足交换律和结合律。把数组所有元素依次异或后,可以在推理中把相同值放到一起,让它们两两抵消;实际计算无需调整顺序,也无需真的排序。所有出现两次的数都抵消为零后,只剩下那个出现一次的数。

用 ans = 0 开始累积。每处理一个元素,执行 ans ^= num,此时 ans 就是已处理前缀的异或值;遍历结束后,它就是整份数组抵消重复元素后的结果。负数同样具有确定的二进制表示,相同负数的每一位也会抵消,因此不用取绝对值或单独处理符号。

解题步骤

  1. 初始化 ans = 0,使第一个元素与它异或后保持原值。
  2. 遍历数组,将每个元素恰好异或进 ans 一次。
  3. 返回 ans;所有成对元素已经抵消,结果就是唯一出现一次的值。

代码实现

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
}

复杂度分析

设数组长度为 $n$。

  • 时间复杂度:$O(n)$,每个元素执行一次固定整数位宽的异或运算。
  • 空间复杂度:$O(1)$,只维护一个累积变量,不修改输入数组。

关键点总结

[!green]

  • 成对出现的相同数异或为零,零不会改变其余数的异或结果。
  • 交换律和结合律使抵消过程不依赖元素相邻或数组有序。
  • 这个结论直接依赖“恰好一个单次元素、其他元素成对出现”的条件。

易错点总结

[!yellow]

  • 若把 ans 初始化为 nums[0],遍历就必须从下标 1 开始;否则首元素会被重复异或,破坏出现次数。
  • 不要先取绝对值,异或本身支持负数,取绝对值反而会改变题目中的值及重复关系。
  • 答案可能为 0,不能因为累积结果为零就认为没找到目标。
  • 如果其他元素出现三次,或有多个元素只出现一次,全部异或就不再能直接得到题目所需的单个答案。

相似题目

题目 难度 关联与区别
137. 只出现一次的数字 II 中等 原题其他数出现三次,普通异或无法抵消,需按位模3或有限状态处理。
260. 只出现一次的数字 III 中等 原题有两个只出现一次的数,需先用异或结果的某一不同位把它们分到两组。
268. 丢失的数字 简单 维护异或抵消关系定位少数异常值;本题其他值成对抵消后留下唯一值,该题将下标与数值异或以找缺失值。
389. 找不同 简单 维护异或抵消关系定位少数异常值;本题其他值成对抵消后留下唯一值,该题异或两个字符串以找新增字符。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/84719035
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!