【力扣100题】87.只出现一次的数字

一、题目描述

给你一个 非空 整数数组 nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现一次的元素。

要求:线性时间复杂度,常量额外空间。

示例 1:

复制代码
输入: nums = [2,2,1]
输出: 1

示例 2:

复制代码
输入: nums = [4,1,2,1,2]
输出: 4

示例 3:

复制代码
输入: nums = [1]
输出: 1

二、解题思路总览

方法 时间复杂度 空间复杂度 说明
异或运算 O(n) O(1) 利用相同数字异或为0的特性
哈希表 O(n) O(n) 统计每个数字出现次数
排序后遍历 O(n log n) O(1) 排序后相邻比较

异或运算是最优解,同时满足 O(n) 时间和 O(1) 空间。


三、方法一:异或运算(推荐)

3.1 核心思想

利用异或运算的两个性质:

  • 相同数字异或结果为 0a ^ a = 0
  • 任何数字与 0 异或结果为 本身a ^ 0 = a
  • 满足交换律和结合律a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b

所有数字异或在一起,重复出现的数字互相抵消,最后剩下的就是只出现一次的数字。

3.2 算法流程图

复制代码
数组: [4, 1, 2, 1, 2]

异或运算过程:

初始: ans = 0

+------------------------------------------------------------+
|  第1步: ans = 0 ^ 4 = 4                                     |
|         ans = 4                                            |
+------------------------------------------------------------+

+------------------------------------------------------------+
|  第2步: ans = 4 ^ 1 = 5                                     |
|         ans = 5                                            |
+------------------------------------------------------------+

+------------------------------------------------------------+
|  第3步: ans = 5 ^ 2 = 7                                     |
|         ans = 7                                            |
+------------------------------------------------------------+

+------------------------------------------------------------+
|  第4步: ans = 7 ^ 1 = 6                                     |
|         ans = 6                                            |
|         (注意:这里 7 ^ 1,7 的二进制包含第一个 1 的信息)      |
+------------------------------------------------------------+

+------------------------------------------------------------+
|  第5步: ans = 6 ^ 2 = 4                                     |
|         ans = 4                                             |
|         (重复的 1 和 2 被抵消,只剩下只出现一次的 4)           |
+------------------------------------------------------------+

结果: ans = 4

异或抵消的直观理解:

复制代码
原始数组: [4, 1, 2, 1, 2]
按顺序异或:

  0 ^ 4 = 4
  4 ^ 1 = 5     (4 和 1 合并结果)
  5 ^ 2 = 7     (加入了 2)
  7 ^ 1 = 6     (1 和前面的 1 抵消)
  6 ^ 2 = 4     (2 和前面的 2 抵消)

最终 = 4

3.3 完整代码

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;

        for (int num : nums) {
            ans ^= num;
        }

        return ans;
    }
};

3.4 复杂度分析

复杂度 说明
时间 O(n) 遍历数组一次
空间 O(1) 只用一个变量

四、方法二:哈希表

4.1 核心思想

遍历数组,将每个数字的出现次数存入哈希表,最后找到计数为 1 的数字。

4.2 算法流程图

复制代码
数组: [4, 1, 2, 1, 2]

遍历并统计:

+------------------------------------------------------------+
|  遍历数组,插入哈希表                                       |
+------------------------------------------------------------+

  插入 4: map = {4: 1}
  插入 1: map = {4: 1, 1: 1}
  插入 2: map = {4: 1, 1: 1, 2: 1}
  插入 1: map = {4: 1, 1: 2, 2: 1}   (1 已存在,计数+1)
  插入 2: map = {4: 1, 1: 2, 2: 2}   (2 已存在,计数+1)

+------------------------------------------------------------+
|  查找计数为 1 的元素                                        |
+------------------------------------------------------------+

  map[4] = 1 -> 答案 = 4

结果: 4

4.3 完整代码

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> freq;

        for (int num : nums) {
            freq[num]++;
        }

        for (auto& [num, count] : freq) {
            if (count == 1) {
                return num;
            }
        }

        return 0;  // 不会走到这里
    }
};

4.4 复杂度分析

复杂度 说明
时间 O(n) 遍历数组两次
空间 O(n) 哈希表存储所有数字

五、方法三:排序后遍历

5.1 核心思想

将数组排序后,相同的数字会相邻排列。遍历数组,每两个一组比较,如果两个数字不相等,则第一个就是只出现一次的元素。

5.2 算法流程图

复制代码
数组: [4, 1, 2, 1, 2]  ->  排序后: [1, 1, 2, 2, 4]

+------------------------------------------------------------+
|  从左到右,每两个一组检查                                     |
+------------------------------------------------------------+

索引 0-1: nums[0]=1, nums[1]=1  -> 相等,跳过下一组
索引 2-3: nums[2]=2, nums[3]=2  -> 相等,跳过下一组
索引 4:   遍历结束,没找到相等的 -> nums[4]=4 是答案

结果: 4

5.3 完整代码

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());

        for (int i = 0; i < nums.size() - 1; i += 2) {
            if (nums[i] != nums[i + 1]) {
                return nums[i];
            }
        }

        return nums[nums.size() - 1];
    }
};

5.4 复杂度分析

复杂度 说明
时间 O(n log n) 排序的时间复杂度
空间 O(1) 若原地排序

六、方法对比总结

方法 时间复杂度 空间复杂度 推荐指数
异或运算 O(n) O(1) ★★★★★
哈希表 O(n) O(n) ★★★☆☆
排序后遍历 O(n log n) O(1) ★★☆☆☆
复制代码
为什么异或是最优解?

1. 时间 O(n),满足要求
2. 空间 O(1),满足要求
3. 代码极简,一行搞定
4. 充分利用数学性质

异或的物理意义:
- 相同为 0,不同为 1
- 出现两次的数字,每一位都被抵消
- 只剩出现一次的那个数字

七、面试追问 FAQ

问题 回答
Q: 为什么异或能消除重复出现的数字? 因为 a ^ a = 0,且异或满足交换律结合律,所以 a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b
Q: 初始 ans 为什么设为 0? 因为 a ^ 0 = a,任何数字与 0 异或不变,0 是异或的单位元
Q: 如果有三个相同的数字呢? 不能用简单异或,需要用其他方法(如哈希表或位运算统计每一位出现次数)
Q: 异或和加法减法有什么区别? 异或不会进位,不会有进位溢出问题。对于只出现两次的场景,异或更直接
Q: 负数能用异或吗? 能,负数在计算机中以补码形式存储,异或操作对补码同样有效
Q: 这种题还有变体吗? 有,比如:只出现一次的其他数字(2个)、其他数字出现3次等,需要用位运算统计每一位

八、相关题目

题目 难度 关键点
136. 只出现一次的数字 简单 异或运算
137. 只出现一次的数字 II 中等 位运算统计每一位
260. 只出现一次的数字 III 中等 分组异或
268. 丢失的数字 简单 异或 / 数学公式
287. 寻找重复数 中等 环形链表 / 二分

九、总结

要点 说明
核心思想 异或运算:相同为0,不同为1
关键性质 a ^ a = 0,a ^ 0 = a,交换律/结合律
为什么有效 出现两次的数字互相抵消
时间复杂度 O(n),遍历一次
空间复杂度 O(1),只用一个变量
适用条件 只有一个数字出现一次,其余出现偶数次

相关推荐
HZ·湘怡1 小时前
排序算法之希尔排序(2)--菜鸟先飞
数据结构·算法·排序算法·希尔排序
乐观勇敢坚强的老彭1 小时前
2026全国青少年信息素养大赛(Python小学组)复赛复习讲义
python·算法·数学建模
林间码客1 小时前
02数据挖掘:数据属性、类型与相似性度量
人工智能·算法·机器学习
阿标在干嘛1 小时前
从“拍脑袋”到“数据驱动”:政策平台的A/B测试实践
大数据·人工智能·算法·ab测试
iningwei1 小时前
[数据结构]细说0xFFFFFFFFL
数据结构
实在智能RPA1 小时前
气象预警Agent等级判定算法:2026年AI驱动的概率集合预报与自动化闭环实践
人工智能·算法·ai·自动化
风筝在晴天搁浅2 小时前
LeetCode CodeTop 82.删除排序链表中的重复元素Ⅱ
算法·leetcode·链表
189228048612 小时前
NV114固态MT29F16T08EWLEHD6-MES:E
人工智能·算法·缓存·性能优化
不会就选b2 小时前
数据结构之链表OJ题(下)
数据结构·链表