前言:
里面提到了异或运算:
异或运算是什么,我真是不怎么知道。
在这里补一下。
异或,英文为exclusive OR,缩写成xor
它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0
让我们先来考虑一个比较简单的问题:
如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?
答案很简单:全员进行异或操作即可。考虑异或操作的性质:对于两个操作数的每一位,相同结果为 00,不同结果为 11。那么在计算过程中,成对出现的数字的所有位会两两抵消为 00,最终得到的结果就是那个出现了一次的数字。
a^a=0
a^0=a
接下来是&运算
运算规则:
0&0=0;0&1=0;1&0=0;1&1=1
即:两个同时为1,结果为1,否则为0
例如:3&5
十进制3转为二进制的3:0000 0011
十进制5转为二进制的5:0000 0101
那么这个思路也就是接下来找到,两个单独数字的思路了~
题目:
一个整型数组
nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1]示例 2:
输入:nums = [1,2,10,4,1,4,3,3] 输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
解题思路:
4 ^ 1 ^ 4 ^ 6 => 1 ^ 6
6 对应的二进制: 110
1 对应的二进制: 001
1 ^ 6 二进制: 111
此时我们无法通过 111(二进制),去获得 110 和 001。
那么我们如果能够把这两个数分开组就好了~。
那么怎么分组呢?
因为两个数,二进制的话,至少有一位不一样,
根据上图,进行异或运算。
最终的得到的数字 1 ^ 6 二进制: 111 (不一样有值,那么下面用最低位 001其实他们两个就是不一样的)
相当于一个mask(用于分组)。
那么根据这个mask的最低位,我们就可以做与运算的时候
比如说这两个数字,就是最低位是1,那么说明这两个数字
比如所有的数字跟001进行与运算的时候,如果最低位为1,那么就与运算就有值,否则没有,那么肯定能把这两个数字弄到不同的分组。
那么初始化一个a和b做两个数字的计数。
if ((num & mask) == 0) {
a ^= num;
} else {
b ^= num;
}
与运算与这个mask的时候,等不等于0就可以区分这两个数字了。
代码:
public class SingleNumbers {
/**
* 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。
* 要求时间复杂度是O(n),空间复杂度是O(1)。
* 输入:nums = [4,1,4,6]
* 输出:[1,6] 或 [6,1]
* <p>
* 主要是对两个不同数字的分组
* <p>
* 奇偶运算:& 1 操作, 当我们对奇偶分组时,容易地想到 & 1,即用于判断最后一位二进制是否为 1。来辨别奇偶。
*/
public int[] singleNumbers(int[] nums) {
//用于将所有的数异或起来
int k = 0;
for (int num : nums) {
k ^= num;
}
//获得k中最低位的1
int mask = 1;
//mask = k & (-k) 这种方法也可以得到mask
//那么当k & mask != 0的时候,那么说明找到了这个不一样的一位了
while ((k & mask) == 0) {
//mask = mask << 1;
mask <<= 1;
}
int a = 0;
int b = 0;
//这部分操作是什么呢?相当于循环一次数组
//与运算只有都为1在某一位,才有值,否则都为0,那么这样一定会把这两个数分开。最后能求出来a和b
for (int num : nums) {
//mask与数字做异或
if ((num & mask) == 0) {
a ^= num;
} else {
b ^= num;
}
}
return new int[]{a, b};
}
}
感谢题解: