问题描述
在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。
要求:
- 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
- 尽量减少额外空间的使用,以体现你的算法优化能力。
约束条件
- 1 ≤ cards.length ≤ 1001
- 0 ≤ cards[i] ≤ 1000
- 班级人数为奇数
- 除了一个数字卡片只出现一次外,其余每个数字卡片都恰好出现两次
首先想到的思路是利用哈希表
思路一:使用哈希表
原理
- 遍历数组并使用哈希表记录每个数字出现的次数。
- 遍历哈希表,找到出现次数为 1 的数字。
时间复杂度和空间复杂度
- 时间复杂度:O(n)(遍历数组一次,哈希表查询 O(1))
- 空间复杂度:O(n)(额外使用一个哈希表存储所有数字)
#include <vector>
#include <unordered_map>
#include <iostream>
int solution(std::vector<int> cards) {
std::unordered_map<int, int> count;
for (int num : cards) {
count[num]++; // 记录出现次数
}
for (const auto& entry : count) {
if (entry.second == 1) { // 出现次数为 1
return entry.first;
}
}
return -1; // 如果没有找到(输入保证唯一数字存在)
}
但是空间会超过题目设定,转而思考推数学公式求解。
思路二:数学
原理
- 假设数组为
nums,有S1 = sum(nums)(数组所有元素之和)。 - 计算数组中去重后的数字和
S2 = sum(set(nums))。 - 唯一出现一次的数字为
unique = 2 * S2 - S1。
时间复杂度和空间复杂度
- 时间复杂度:O(n)(遍历数组两次)
- 空间复杂度:O(n)(额外使用一个集合存储去重的数字)
#include <iostream>
#include <vector>
#include <unordered_set>
int solution(std::vector<int> cards) {
std::unordered_set<int> uniqueNums;
int S1 = 0, S2 = 0;
for (int num : cards) {
S1 += num; // 累加所有数字
uniqueNums.insert(num); // 插入集合
}
for (int num : uniqueNums) {
S2 += num; // 累加去重后的数字
}
return 2 * S2 - S1; // 计算唯一数字
}
空间还是大的,进而考虑到使用位运算,利用二进制的思想。
思路三:使用 XOR(异或)操作
原理
-
异或操作的性质:
x ^ x = 0(一个数与自身异或结果为 0)。x ^ 0 = x(一个数与 0 异或结果为它本身)。- 异或满足 交换律 和 结合律,即
(a ^ b) ^ c = a ^ (b ^ c)。
-
算法核心:
- 将数组中的所有数字依次异或。两次出现的数字会因为
x ^ x = 0消去,最终只剩下那个唯一的数字。
- 将数组中的所有数字依次异或。两次出现的数字会因为
时间复杂度和空间复杂度
- 时间复杂度:O(n)(遍历数组一次)
- 空间复杂度:O(1)(只需常量存储变量)
XOR 方法图解
假设数组为 [2, 1, 4, 5, 2, 4, 1],唯一的数字是 5。
- 初始:
result = 0 - 逐步异或:
result = 2 ^ 1 = 3
result = 3 ^ 4 = 7
result = 7 ^ 5 = 2
result = 2 ^ 2 = 0
result = 0 ^ 4 = 4
result = 4 ^ 1 = 5
最终:result = 5。
#include <iostream>
#include <vector>
int findUniqueNumber(const std::vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或操作
}
return result;
}