📌 题目链接:136. 只出现一次的数字 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:数组、异或位运算
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
🧩 题目分析
题目给出一个非空整数数组 nums,其中除了一个元素只出现一次外,其余每个元素都恰好出现两次。要求我们在线性时间复杂度(O(n))和常量额外空间(O(1))的约束下,找出那个“孤独”的数字。
乍一看,这似乎是一个统计频次的问题。但题目明确限制了不能使用哈希表、集合等 O(n) 空间结构,这就排除了常规的计数思路。
💡 关键突破口在于“其余元素均出现两次”这一对称性——这正是位运算中异或(XOR)操作的天然应用场景!
⚙️ 核心算法及代码讲解:异或(XOR)位运算
🔑 异或运算的三大核心性质(必须牢记!面试高频考点)
- 恒等律:
a ⊕ 0 = a
→ 任何数与 0 异或,结果是它本身。 - 自反律:
a ⊕ a = 0
→ 任何数与自身异或,结果为 0。 - 交换律 & 结合律:
a ⊕ b = b ⊕ a,(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)
→ 异或运算顺序不影响最终结果。
🎯 这些性质组合起来,就形成了“成对抵消,单者留存”的魔法效果!
📐 数学推导(面试时可口头阐述)
设数组中有 2m + 1 个元素:
m对重复数字:a₁, a₁, a₂, a₂, ..., aₘ, aₘ- 1 个唯一数字:
x
对整个数组做异或:
(a₁ ⊕ a₁) ⊕ (a₂ ⊕ a₂) ⊕ ... ⊕ (aₘ ⊕ aₘ) ⊕ x
= 0 ⊕ 0 ⊕ ... ⊕ 0 ⊕ x
= x
✅ 最终结果就是那个只出现一次的数字!
💻 C++ 核心算法代码(带逐行注释)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0; // 初始化结果为0,因为 a ⊕ 0 = a
for (auto e : nums) // 遍历数组中每一个元素
ret ^= e; // 累积异或:成对的会抵消为0,最后只剩唯一数
return ret; // 返回最终结果
}
};
✅ 为什么这个解法满足题目所有要求?
- 时间复杂度 O(n):仅需一次遍历。
- 空间复杂度 O(1):只用了一个额外变量
ret。- 无需修改原数组:纯读取操作,安全高效。
🧭 解题思路(分步拆解)
- 识别问题模式:
“其他都成对,唯有一个落单” → 联想到对称抵消机制。 - 排除常规方法:
- 哈希表?→ O(n) 空间 ❌
- 排序后比较?→ O(n log n) 时间 ❌
- 数学求和?→ 可能溢出,且不通用(如负数)⚠️
- 锁定位运算:
异或的“自反性”完美匹配“成对抵消”需求。 - 实现与验证:
从 0 开始累积异或,利用交换律保证顺序无关,最终得到答案。
📊 算法分析
| 维度 | 分析 |
|---|---|
| 时间复杂度 | O(n) —— 单次遍历数组 |
| 空间复杂度 | O(1) —— 仅使用一个整型变量 |
| 稳定性 | ✅ 不受数据范围、正负号影响 |
| 扩展性 | 若有两个唯一数字(其余成对),可用类似思想+分组异或解决(LeetCode #260) |
| 面试价值 | ⭐⭐⭐⭐⭐ —— 考察位运算理解、数学思维、边界条件处理 |
💡 面试加分点:
- 能清晰说出异或三性质
- 能对比其他 O(n) 空间解法并说明为何不符合要求
- 能延伸讨论“两个唯一数”变种题(#260)
💻 完整可运行代码
C++ 版本
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto e: nums) ret ^= e;
return ret;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
vector<int> nums1 = {2,2,1};
cout << sol.singleNumber(nums1) << "\n"; // 输出: 1
vector<int> nums2 = {4,1,2,1,2};
cout << sol.singleNumber(nums2) << "\n"; // 输出: 4
vector<int> nums3 = {1};
cout << sol.singleNumber(nums3) << "\n"; // 输出: 1
return 0;
}
JavaScript 版本
/**
* @param {number[]} nums
* @return {number}
*/
var singleNumber = function(nums) {
let ret = 0;
for (const e of nums) ret ^= e;
return ret;
};
// 测试
console.log(singleNumber([2,2,1])); // 1
console.log(singleNumber([4,1,2,1,2])); // 4
console.log(singleNumber([1])); // 1
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!