力扣136.只出现一次的数字
【LeetCode题解】只出现一次的数字------Set解法和神奇的位运算
一、题目描述
给你一个 非空整数数组 nums
,其中除了某个元素只出现一次以外,其余每个元素均出现两次。
请你找出那个只出现了一次的元素。
要求:
- 时间复杂度为
O(n)
- 空间复杂度为
O(1)
示例:
输入:nums = [2,2,1]
输出:1
输入:nums = [4,1,2,1,2]
输出:4
输入:nums = [1]
输出:1
二、思路分析
这道题一看就让人想到用哈希结构来记录出现次数,但题目明确要求常量额外空间,所以我们要换个角度思考。
常见思路有两个:
- HashSet:存进去、出现两次就移除,最后剩下的就是答案。
- 位运算(异或 XOR):充分利用数学性质,优雅解决问题。
三、解法一:HashSet(直观思路)
我们先说最容易想到的办法。
- 遍历数组,把数字加入 Set。
- 如果已经存在,就移除它。
- 遍历结束后,Set 里只会留下那个只出现一次的数。
代码
java
import java.util.HashSet;
class Solution {
public int singleNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
if (!set.add(num)) {
set.remove(num);
}
}
return set.iterator().next();
}
}
复杂度
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
虽然简单直观,但没有满足题目的空间要求。
四、解法二:位运算(异或 XOR)
这才是本题的核心与精华。
异或运算的性质
a ^ a = 0
a ^ 0 = a
- 交换律、结合律
结合这三个性质,可以看出:
- 两个相同的数会互相抵消变成
0
- 所有数异或一遍,剩下的就是只出现一次的那个数
举例
nums = [4,1,2,1,2]
4 ^ 1 ^ 2 ^ 1 ^ 2
= (1 ^ 1) ^ (2 ^ 2) ^ 4
= 0 ^ 0 ^ 4
= 4
代码
java
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for (int num : nums) {
ans ^= num;
}
return ans;
}
}
复杂度
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
这是题目的最优解。
五、方法对比
方法 | 时间复杂度 | 空间复杂度 | 思路 |
---|---|---|---|
HashSet | O(n) | O(n) | 简单直观,适合入门 |
XOR位运算 | O(n) | O(1) | 优雅高效,面试必杀 |
六、进阶拓展
如果你觉得本题太简单,可以思考以下变种:
-
每个数字出现三次,只有一个数字出现一次
- 可以用位运算统计每一位上
1
出现的次数,对 3 取模即可得到答案。
- 可以用位运算统计每一位上
-
有两个数字各出现一次,其他数字出现两次
- 先整体异或得到
x ^ y
,再找出一个不为 0 的二进制位,将数组划分为两组,分别异或即可得到x
和y
。
- 先整体异或得到
这些变种同样考验对位运算的理解,适合进一步练习。
七、总结
- 如果不考虑空间复杂度,
Set
是最容易想到的方案。 - 如果追求最佳解,位运算 XOR 是唯一王道。
- 理解 XOR 的数学本质,可以帮助你秒杀一类经典问题。