046 · Single Number

algorithm
Published

June 26, 2026

My First Thoughts

嗯,这道题刚看到会想到前面的 Majority Element,因为它好像也是一个“抵消”的问题。

Majority Element 里可以维护一个候选值和一个计数器。因为多数元素超过一半,所以不同元素互相抵消以后,多数元素还能剩下来。

这题也有类似的味道:所有数字都成对出现,只有一个数字不是成对的。

最直接的粗暴方法是先遍历一遍,计算每个数字出现的次数。然后再遍历 counts.items(),找到次数等于 1 的那个数字:

def singleNumber(nums):
    counts = {}

    for num in nums:
        counts[num] = counts.get(num, 0) + 1

    for key, value in counts.items():
        if value == 1:
            return key

这个写法是对的。它把问题翻译成了:

谁出现了一次,就返回谁。

然后还能想到一个更贴近“抵消”的做法:用一个 set

遍历数组时:

  • 如果当前数字不在 set 里,就把它加入
  • 如果当前数字已经在 set 里,说明这是第二次遇到它,就把它移除

代码大概是:

def singleNumber(nums):
    seen = set()

    for num in nums:
        if num in seen:
            seen.remove(num)
        else:
            seen.add(num)

    return seen.pop()

这个也能工作。因为出现两次的数字会经历:

第一次遇到 -> 加入 set
第二次遇到 -> 从 set 移除

最后 set 里只会剩下那个出现一次的数字。

到这里其实已经抓住了这题最关键的结构:

相同的数字应该互相抵消。

只是还有一个问题:这里是整数。既然所有数都成对出现,只有一个不是,那么整数本身有没有什么运算可以天然表达这种“抵消”?

如果不知道这个运算符,就很难继续想到最优解。


Why That Is Not Enough

哈希计数和 set 抵消都是正确解法。

它们的问题不是逻辑错,而是都需要额外空间:

  • 哈希计数要记录每个数字出现了几次
  • set 要保存当前还没有被抵消掉的数字

这题还能继续优化,是因为它有一个非常强的条件:

除了一个数字以外,其他数字都恰好出现两次。

如果有一个运算能做到:

x 和 x 运算 -> 0
x 和 0 运算 -> x
运算顺序不影响结果

那就不需要 counts,也不需要 set。只要把所有数字一路运算过去,成对出现的数字会自动变成 0,最后只剩那个单独的数字。

这个运算就是 XOR,也叫“异或”。在 Python 里写作:

^

Final Idea

XOR 是一个按位运算。它比较两个整数的二进制位:

两个 bit 相同 -> 0
两个 bit 不同 -> 1

也就是:

0 ^ 0 = 0
1 ^ 1 = 0
0 ^ 1 = 1
1 ^ 0 = 1

所以它有两个特别适合这题的性质:

x ^ x = 0
x ^ 0 = x

比如 5 ^ 5

5 = 101
5 = 101
-------
    000

所以相同数字可以被 XOR 抵消掉。

再看题目的例子:

nums = [4, 1, 2, 1, 2]

如果把所有数字都 XOR 起来:

4 ^ 1 ^ 2 ^ 1 ^ 2

因为 XOR 的顺序不影响结果,可以理解成:

4 ^ (1 ^ 1) ^ (2 ^ 2)

成对的数字抵消成 0

4 ^ 0 ^ 0

最后剩下:

4

所以我们只需要维护一个变量 result。一开始设成 0,然后遍历数组,每次把当前数字 XOR 进去:

result = result ^ num

遍历结束后,result 就是只出现一次的数字。


Why It Works

题目保证除了一个数字以外,其他数字都出现两次。

对于任何出现两次的数字 x,在整体 XOR 结果里都会有:

x ^ x = 0

所以这些成对数字都会被抵消掉。

对于那个只出现一次的数字 single,它没有另一个相同数字可以抵消。

当所有成对数字都变成 0 以后,整体结果就等价于:

0 ^ 0 ^ ... ^ single

而:

0 ^ single = single

所以最后返回的就是只出现一次的数字。


Code

def singleNumber(nums):
    result = 0

    for num in nums:
        result = result ^ num

    return result

也可以写成更短的形式:

def singleNumber(nums):
    result = 0

    for num in nums:
        result ^= num

    return result

这里的:

result ^= num

等价于:

result = result ^ num

Complexity

Time \(O(n)\) - 只遍历数组一次
Space \(O(1)\) - 只使用一个 result 变量

Takeaway

当题目里出现“其他元素都成对出现,只有一个元素落单”时,可以先想到哈希计数或 set 抵消;如果元素是整数,再进一步想 XOR。x ^ x = 0 正好表达“相同元素抵消”,x ^ 0 = x 正好保留最后落单的元素。


Quiz