leetcode 136只出现一次的数字

LeetCode 136. 只出现一次的数字

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

例如:

复制代码
输入:nums = {4, 1, 2, 1, 2}
输出:4

这道题主要有两种解法:哈希表(unordered_map)和异或运算(XOR)。

一、方法一:哈希表(unordered_map)

1. 完整代码(精简注释)

复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int,int> hash; // 数字 -> 出现次数

        // 统计每个数字的出现次数
        for(auto num : nums){
            hash[num]++;
        }

        // 找到出现次数为1的数字
        for(auto [num,count] : hash){
            if(count == 1) return num;
        }

        return -1;
    }
};

2. 核心思路

利用哈希表存储每个数字及其出现次数。

复制代码
unordered_map<int,int> hash;

其中:

  • Key:数组中的数字。

  • Value:数字出现的次数。

例如:

复制代码
nums = {4, 1, 2, 1, 2};

统计后:

Key(数字) Value(次数)
4 1
1 2
2 2

遍历哈希表,找到 count == 1 的数字并返回。

3. 重点语法

① 统计出现次数

复制代码
hash[num]++;

如果 num 不存在,哈希表会自动创建对应的键值对,将整数值初始化为 0,再执行加 1。

② 遍历键值对

复制代码
for(auto [num,count] : hash)

其中:

  • num:键(Key)。

  • count:值(Value)。

  • []:结构化绑定,用于拆分键值对。

也可以写成:

复制代码
for(auto p : hash){
    if(p.second == 1){
        return p.first;
    }
}

其中 p.first 表示 Key,p.second 表示 Value。

4. 复杂度分析

  • 时间复杂度:平均 \(O(n)\),遍历数组统计次数,再遍历哈希表。

  • 空间复杂度:\(O(n)\),需要额外的哈希表。

5. 方法总结

哈希表解法主要分为三步:

  1. 创建 unordered_map<int,int> 哈希表。

  2. 遍历数组,利用 hash[num]++ 统计次数。

  3. 遍历哈希表,返回出现次数为 1 的数字。

核心:利用 Key 存储数字,Value 存储出现次数。

二、方法二:异或运算(XOR)

1. 完整代码(精简注释)

复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0; // 初始化异或结果

        // 将所有数字依次异或
        for(auto num : nums){
            ret ^= num;
        }

        return ret; // 返回只出现一次的数字
    }
};

2. 核心思路

异或运算符为 ^。

异或规则:二进制位相同为 0,不同为 1。

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

异或具有三个重要性质:

① 相同数字异或为 0

\ a\\oplus a=0 \\

② 任何数字与 0 异或等于本身

\ a\\oplus 0=a \\

③ 异或满足交换律和结合律

\ a\\oplus b\\oplus a=(a\\oplus a)\\oplus b=b \\

因此,可以利用异或运算消除所有出现两次的数字,最终只剩下出现一次的数字。

3. 举例模拟

复制代码
nums = {4, 1, 2, 1, 2};

初始化:

复制代码
int ret = 0;

执行过程:

当前数字 num 异或操作 ret
4 0 ^ 4 4
1 4 ^ 1 5
2 5 ^ 2 7
1 7 ^ 1 6
2 6 ^ 2 4

最终返回:

复制代码
return ret; // 4

从数学上理解:

\ 4\\oplus1\\oplus2\\oplus1\\oplus2 \\

根据交换律和结合律:

\ =4\\oplus(1\\oplus1)\\oplus(2\\oplus2) \\

\ =4\\oplus0\\oplus0 \\

\ =4 \\

所有出现两次的数字都会抵消为 0,最终就是 0 与只出现一次的数字异或,结果为该数字本身。

4. 重点语法

复制代码
ret ^= num;

等价于:

复制代码
ret = ret ^ num;

其中:

  • ^:按位异或运算。

  • ^=:异或赋值运算。

注意 ^ 不是乘方运算。

5. 复杂度分析

  • 时间复杂度:\(O(n)\),只遍历数组一次。

  • 空间复杂度:\(O(1)\),只使用一个额外变量 ret。

6. 方法总结

异或解法主要分为三步:

  1. 初始化 ret = 0。

  2. 遍历数组,将每个数字与 ret 异或。

  3. 所有成对出现的数字相互抵消,最终返回 ret。

核心:相同数字异或为 0,0 与任何数字异或等于数字本身。

三、两种方法对比

对比 哈希表 异或
核心思想 统计出现次数 相同数字相互抵消
数据结构 unordered_map 不需要
时间复杂度 平均 \(O(n)\) \(O(n)\)
空间复杂度 \(O(n)\) \(O(1)\)
优点 容易理解,适用范围广 代码简洁,空间效率高
缺点 需要额外空间 依赖题目中特定的出现次数条件

四、最终记忆模板

哈希表:统计次数,找到出现一次的数字。

复制代码
unordered_map<int,int> hash;

for(auto num : nums){
    hash[num]++;
}

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

异或:相同数字抵消,剩下唯一数字。

复制代码
int ret = 0;

for(auto num : nums){
    ret ^= num;
}

return ret;

最后总结:

哈希表和异或都能解决这道题,但题目要求线性时间复杂度和常数额外空间复杂度,因此异或才是本题最优解法。

以后遇到类似题目,可以这样判断:

  • 需要统计数字出现次数:优先考虑 unordered_map。

  • 需要判断数字是否出现过:优先考虑 unordered_set。

  • 所有数字出现两次,只有一个数字出现一次:优先考虑异或运算。

相关推荐
余额瞒着我当琳5 小时前
哈希表底层原理解析,直接定址法,哈希冲突,负载因子,哈希函数,开放定址法
java·c++·哈希算法·散列表
局部有解6 小时前
LeetCode Hot100 438 找到字符串中所有字母异位词
c++·算法·leetcode
Nil2087 小时前
leetcode 75颜色分类
数据结构·算法·leetcode
aqiu1111118 小时前
CodeTop按顺序刷题0003:LeetCode 206. 反转链表(迭代法图文详解与动态演示)
算法·leetcode·链表
不会就选b9 小时前
算法日常・每日刷题--<动态规划>29
数据结构·算法·leetcode
夕除9 小时前
redis--dict rehash
算法·哈希算法
虚无的纽扣10 小时前
【力扣刷题】第五天:将 x 减到 0 的最小操作数、最大连续1的个数 III
算法·leetcode·职场和发展
hanlin0320 小时前
刷题笔记:力扣第32题-最长有效括号
笔记·算法·leetcode
All for pursuit.20 小时前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划