找单独的数
问题描述
在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。
要求:
- 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
- 尽量减少额外空间的使用,以体现你的算法优化能力。
测试样例
样例1:
输入:
cards = [1, 1, 2, 2, 3, 3, 4, 5, 5]
输出:4
解释:拿到数字 4 的同学是唯一一个没有配对的。
样例2:
输入:
cards = [0, 1, 0, 1, 2]
输出:2
解释:数字 2 只出现一次,是独特的卡片。
样例3:
输入:
cards = [7, 3, 3, 7, 10]
输出:10
解释:10 是班级中唯一一个不重复的数字卡片。
约束条件
- 1 ≤ cards.length ≤ 1001
- 0 ≤ cards[i] ≤ 1000
- 班级人数为奇数
- 除了一个数字卡片只出现一次外,其余每个数字卡片都恰好出现两次
解决思路与启发
这道题主要有两种解决思路,第一种解决思路需要采用哈希集(HashSet)的数据结构来进行记录;第二种解决思路则是运用异或运算的性质来得到结果。
1.哈希集(HashSet)
由于哈希集是不包含任何重复元素的无序集合。故我们只需要在哈希集不存在当前数组内数字时添加它,当哈希集存在当前数字时,移除它,最后即可得到那个独一无二的数字。
2.异或运算
采用该解决思路,由于问题中"除了一个数字卡片只出现一次外,其余每个数字卡片都恰好出现两次"这一条件,故我们只需遍历数组的同时,对数组内数字进行异或的累次运算即可。代码如下:
public static int solution(int[] inp) {
// Edit your code here
int ans = 0;
for(int num:inp){
ans ^= num;
}
return ans;
}
异或运算
在逻辑学中,逻辑算符异或(exclusive or)是对两个运算元的一种逻辑析取类型,符号为 XOR 或 EOR 或 ⊕(编程语言中常用^)。但与一般的逻辑或不同,异或算符的值为真仅当两个运算元中恰有一个的值为真,而另外一个的值为非真。转化为命题,就是:“两者的值不同。”或“有且仅有一个为真。”
| y | a | b |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
异或运算性质即
- 异或满足交换律和结合律:a^b=b^a (a^b)^c=a^(b^c)
- 任何数与0异或不变:a^0=a
- 任何数异或自己为零:a^a=0
异或运算常用的场景有:
1.加密与解密:
异或运算可以用于简单的对称加密算法。一个明文通过与密钥进行异或运算可以得到密文,同样地,密文通过与相同的密钥进行异或运算可以还原为明文。
2.数据校验:
异或运算常用于奇偶校验,例如在网络通信过程中,每个字节的数据都计算一个校验位,数据和校验位一起发送出去,接收方可以根据校验位判断接收到的数据是否有误。
3.交换两个变量的值:
不使用临时变量交换两个变量的值:
int x = 2;
int y = 3;
x = x ^ y; // x = (2 ^ 3)
y = x ^ y; // y = (2 ^ 3) ^ 3 = 2
x = x ^ y; // x = (2 ^ 3) ^ 2 = 3
通过异或运算性质即可实现两变量值的交换,但此处存在一个非常隐蔽的陷阱,在进行数组两个位置数字进行交换时(num[n],i,j)当i和j指向同一个数组对应地址时,最终只会得到0的结果,这等价于自己和自己做了三次异或。
4.位翻转:
异或运算可以用于翻转特定位,例如翻转二进制数 10100001 的第 6 位,可以将该数与 00100000 进行按位异或运算:10100001 ^ 00100000 = 10000001。
5.数据恢复与备份
异或运算同样可以用于数据的恢复与备份。类如当你有A,B,C这三组数据文件时,我们可以通过异或运算生成一个冗余数据文件D(D = A ^ B ^ C)
如果其中一个数据文件丢失,可以通过剩余的数据文件和冗余数据文件 D 来恢复丢失的数据文件。
比如我们丢失了A数据文件,根据异或运算的逻辑 A = D ^ B ^ C ,即可恢复得到A数据文件。