4.7 KiB
4.7 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-05-16 14:30 |
96-只出现一次的数字
题面
给你一个非空整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
要求:
- 线性时间复杂度
O(n) - 常量额外空间
O(1)
示例 1:
输入:
nums = [2, 2, 1]→ 输出:1
示例 2:
输入:
nums = [4, 1, 2, 1, 2]→ 输出:4
示例 3:
输入:
nums = [1]→ 输出:1
思路
先思考一个问题 🤔
如果不用任何算法,你会怎么找到那个唯一的数字?
[!tip] 直觉方案 遍历数组,对每个数字统计出现次数,然后找出出现一次的——但这需要哈希表或排序,不满足
O(1)空间的要求。
核心洞察:异或运算的性质 💡
回忆一下**异或(XOR)**的三条基本性质:
| 性质 | 表达式 | 含义 |
|---|---|---|
| 归零律 | a ^ a = 0 |
相同数字异或为 0 |
| 恒等律 | a ^ 0 = a |
任何数与 0 异或等于自身 |
| 交换律 & 结合律 | a ^ b ^ c = a ^ (b ^ c) = (a ^ c) ^ b |
异或的顺序不影响结果 |
[!question] 启发式提问 如果一个数组中除了一个数字外,其余都恰好出现两次,把这些数字全部异或起来会发生什么?
根据归零律,每对相同的数字异或后变为 0;再根据恒等律,所有的 0 异或起来还是 0;最后剩下的就是那个只出现一次的数字!
流程演示
以 nums = [4, 1, 2, 1, 2] 为例:
flowchart LR
A["开始: result = 0"] --> B["result ^= 4 → 4"]
B --> C["result ^= 1 → 5"]
C --> D["result ^= 2 → 7"]
D --> E["result ^= 1 → 6"]
E --> F["result ^= 2 → 4"]
F --> G["result = 4 ✅"]
style A fill:#e1f5fe
style G fill:#c8e6c9
更直观的理解(利用交换律和结合律重新排列):
4 \oplus 1 \oplus 2 \oplus 1 \oplus 2 = 4 \oplus (1 \oplus 1) \oplus (2 \oplus 2) = 4 \oplus 0 \oplus 0 = 4
[!info] 关键结论 不需要关心顺序——异或的交换律保证了无论成对的数字在数组中的位置如何,它们最终一定会被配对抵消。
代码提示
伪代码
初始化 result = 0
遍历数组中的每个数字 num:
result = result XOR num
返回 result
复杂度分析
| 指标 | 结果 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) |
只需遍历数组一次 |
| 空间复杂度 | O(1) |
只使用了一个变量 result |
完美满足题目要求!
技巧
1. 异或找唯一元素的通用模式
[!summary] 模板记忆法
ans := 0 for _, v := range nums { ans ^= v } return ans看到「除一个元素出现一次/奇数次外,其余出现偶数次」→ 优先考虑异或。
2. 异或的小应用清单
这些面试高频技巧都源于异或的相同两条性质:
| 应用场景 | 方法 | 核心原理 |
|---|---|---|
| 找唯一元素 | 全数组异或 | a ^ a = 0, a ^ 0 = a |
| 判断两个数是否相等 | a ^ b == 0 |
相等则结果为 0 |
| 交换两个变量(经典面试题) | a ^= b; b ^= a; a ^= b |
三步异或完成交换,无需临时变量 |
| 翻转指定位 | num ^= mask |
mask 中为 1 的位被翻转 |
3. 变体题延伸 🚀
| 题目 | 变化点 | 思路升级 |
|---|---|---|
| 136. Single Number(本题) | 其余出现两次 | 直接全异或 ✅ |
| 137. Single Number II | 其余出现三次 | 按位统计每个位上 1 的出现次数,%3 剩余的拼接 |
| 260. Single Number III | 有两个只出现一次的数 | 先全异或得到 x ^ y,找最低位的 1 分组,再分别异或 |
[!note] 思考方向 当「其余元素出现 k 次」时,本质上是把逐位计数的模运算从 %2 推广到 %k。异或是最自然的 %2 计数器。
代码
// SingleNumber finds the element that appears exactly once
// while all other elements appear exactly twice.
func singleNumber(nums []int) int {
// result 初始化为 0(异或的单位元)
// 根据恒等律:a ^ 0 = a
result := 0
// 遍历所有数字,依次异或
// 根据归零律:a ^ a = 0,成对的数字会相互抵消
// 根据交换律:无论顺序如何,结果不变
for _, num := range nums {
result ^= num
}
// 此时 result 就是那个只出现一次的数字
return result
}
Go 运行细节
[!example] Go 中异或运算符 Go 语言使用
^作为按位异或运算符:
5 ^ 3 = 0b0101 ^ 0b0011 = 0b0110 = 6- 注意区分逻辑异或
!=—— 这里用的是按位异或