LeetCode T136 ---> 只出现一次的数字【简单题】
题目描述
【题目描述】一个数组中其他元素都出现了两次,然而只有一个元素出现了一次,我们需要找到这个特殊的数字。
看到题目,我们当然很好想到使用HashMap来遍历整个数组就好了。当然,小白这里就不介绍这种解法了,换一个舒服的解法吧!
我们学过《数字电路》的小伙伴应该都知道异或运算符。相同的两个数字异或结果为0,任何数字与0异或的结果都为该数字本身,且异或运算具有交换律。即:
a ^ a = 0
a ^ 0 = a
a ^ b ^ a = a ^ a ^ b = 0 ^ b = b
由上面的基本规则,我们便可推导出本题的解法啦~
在本题中,给定了一个数组,里面有一个元素只出现了1次,其他的元素都出现了2次。所以我们根据上面异或运算的结论,将其应用到本题中,我们直接遍历整个数组,将所有的元素都使用异或运算,相同的元素经过异或运算后,结果为0,之后只剩下了0和出现一次的数字。异或结果就是这个只出现一次的数字啦~
public int singleNumber(int[] nums) {
int ans = 0;
for (int i = 0 ; i < nums.length ; i++ ){
ans = ans ^ nums[i];
}
return ans;
}
LeetCode T260 ---> 只出现一次的数字III【中等题】
题目描述
这个升级版是在上一道的题基础上增加了一个新的条件,整个数组中有两个只出现1次的元素,其他的元素都出现了两次。
在这种情况下,如果我们再将所有的元素异或,得到是这两个只出现一次的数字异或的结果,并无法得到这两个数字。但是隐隐约约的能够感受到,这道题还是可以使用上一道题的解法的(哈哈,这可能就是直觉吧!)
我们再来分析一下无法直接使用上面一道题的解法的原因:问题在于,给定的数组中包括了两个只出现一次的元素,所以我们无法异或运算。可是,如果我们将这两个数字划分到两个不同的数组中,这样,每一个子数组就仅仅包含一个只出现一次元素了~
OK!分析到这里,我们下一个问题来了,如何将这两个元素划分到不同的数组中呢?这里就是本题的亮点啦!
我们知道,任何两个不同的数字二进制表示中,一定会至少有某一位是不同的。如果我们找到这两个数字不同的二进制位,那么就可以根据这个不同的二进制位,将两个数字分开啦~同时也可以根据这个不同的位,来将其他的所有元素区分开。而我们将所有元素异或之后,得到的结果,恰巧就是这两个数字不同的二进制位。
我们举个例子吧!元素数组nums = [1,1,2,2,3,4]
将nums中所有元素异或
ans = 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 4 = 0 ^ 0 ^ 3 ^ 4 = 3 ^ 4
十进制 3 ^ 4 = 7
二进制 011 ^ 100 = 111
如上所示,我们当我们将所有元素异或运算之后,我们就可以得到一个ans
,这个结果就代表这两个数字不同的二进制位。假如我们取ans
的最低位的1,那么,我们就可以根据这个二进制位将所有的元素划分为两组了。由于数组中的其他元素都是重复出现的,所以根据这个二进制位的划分,会将相同的元素划分到相同的分组中。比如:在这个例子中,我们根据ans
的最低位1,也就是数字1来划分的话,就可以看到下面的结果
第一组:1 , 1 , 3
第二组:2 ,2 ,4
其中,第一组的最低位都是1,第二组的最低位都是0,此时我们的目标元素3和4都已经在不同的分组中了,所以我们直接对两组元素异或操作,即可得到最后的结果了~
【注意】在此处我们需要说明一点,之所以我们是根据最低位来将所有元素分为两组,是基于两个只出现一次的元素(3和4)的最低位是不同的。如果这两个元素的最低位相同,则需要在两个数字的二进制位上继续向前探索,查看任何一个不同的二进制位即可。
public int[] singleNumbers(int[] nums) {
int k = 0;
for(int num : nums){//获取两个只出现一次的数字的异或结果
k = k^num;
}
int target = 1;
while((target & k) == 0){//寻找异或结果K的不为0的最低位
target = target << 1;
}
int a = 0 , b = 0;
for(int num:nums){//对两个数字进行分组,并且在不同的分组当中
if((num & target) == 0){
a = a^num;
}else{
b = b^num;
}
}
return new int[]{a, b};
}