【LeetCode Hot100 刷题日记 (96/100)】136. 只出现一次的数字 —— 数组、异或位运算 🧠

4 阅读4分钟

📌 题目链接:136. 只出现一次的数字 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:数组、异或位运算

⏱️ 目标时间复杂度:O(n)

💾 空间复杂度:O(1)


🧩 题目分析

题目给出一个非空整数数组 nums,其中除了一个元素只出现一次外,其余每个元素都恰好出现两次。要求我们在线性时间复杂度(O(n))和常量额外空间(O(1))的约束下,找出那个“孤独”的数字。

乍一看,这似乎是一个统计频次的问题。但题目明确限制了不能使用哈希表、集合等 O(n) 空间结构,这就排除了常规的计数思路。
💡 关键突破口在于“其余元素均出现两次”这一对称性——这正是位运算中异或(XOR)操作的天然应用场景


⚙️ 核心算法及代码讲解:异或(XOR)位运算

🔑 异或运算的三大核心性质(必须牢记!面试高频考点)

  1. 恒等律a ⊕ 0 = a
    → 任何数与 0 异或,结果是它本身。
  2. 自反律a ⊕ a = 0
    → 任何数与自身异或,结果为 0。
  3. 交换律 & 结合律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
= 00 ⊕ ... ⊕ 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
  • 无需修改原数组:纯读取操作,安全高效。

🧭 解题思路(分步拆解)

  1. 识别问题模式
    “其他都成对,唯有一个落单” → 联想到对称抵消机制。
  2. 排除常规方法
    • 哈希表?→ O(n) 空间 ❌
    • 排序后比较?→ O(n log n) 时间 ❌
    • 数学求和?→ 可能溢出,且不通用(如负数)⚠️
  3. 锁定位运算
    异或的“自反性”完美匹配“成对抵消”需求。
  4. 实现与验证
    从 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!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!