异或 ^ :数字进行异或运算时,数字相同为false,数字不同为true。二进制位运算中同理。
题I
给你一个非空整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
class Solution {
public int singleNumber(int[] nums) {
int single = 0;
for(int num: nums){
single ^= num;
}
return single;
}
}
首先要明确异或运算的一些性质,比如 0a=a,aa=0,同时异或运算是满足交换律和结合律的,原因后文说明。
因此,本题中除了所需元素外,其它元素均出现两次,那么根据交换律和结合律,出现两次的元素是可以进行异或运算的,且结果为0,最后只剩下出现一次的元素,与0进行异或运算得到原数。那么这道题就可以遍历数组 nums 将所有元素都进行异或运算,最后结果为只出现一次的元素。
题II
其他条件同题I,有两个元素只出现一次,找出只出现了一次的那两个元素。
class Solution {
public int[] singleNumber(int[] nums) {
int eor = 0;
for(int num : nums){
eor ^= num;//eor是只出现一次的两个元素的异或运算的结果
}
int rightOne = eor & (~eor + 1);
int onlyOne = 0;
for(int cur : nums){
if((cur & rightOne) == 0){
onlyOne ^= cur;
}
}
int[] res = {onlyOne, eor ^ onlyOne};
return res;
}
}
本题有两个元素都是只出现了一次(在代码中为 onlyOne 和 eor ^ onlyOne ,下文会使用这两个变量表示两个只出现一次的元素),所以仅用上一道题的做法只能得到这两个元素异或的结果,即 eor.
由于这两个数并不相同,所以 eor 不可能为0,那么 eor 的二进制中必然会出现至少一个1,1所在的位置就是 onlyOne 和 eor ^ onlyOne 的二进制中不相同的那一位,那么就可以根据这一位将两个数区分开来。
接下来去找这个必然会出现的1,我们从 eor 的二进制的最右边开始,从右向左找第一个出现的1(找一个1就可以区分两个数了),使用代码int rightOne = eor & (~eor + 1);
即可找到。该代码的原理如下:
然后再次遍历数组,但是这次添加了条件,一但有数和 rightOne 进行与操作得到0,才进行异或运算。这是因为在数组中的数有两种,一种在 rightOne 的二进制位为1的位置是0,另一种是1,通过判断和 rightOne 进行与操作,可以将他们分开,而出现两次的数是相同的,会被分到同一边,异或操作后仍然为0,所以不会产生任何影响。但是这样可以将 onlyOne 和 eor ^ onlyOne 完美区分开。
至于判断条件,可以是与操作得到0,也可以是与操作得到1,效果都是一样的,都是得到 onlyOne 和 eor ^ onlyOne 其中之一,因为他俩在 rightOne 的二进制位为1的位置一个是1,一个是0。
遍历结束后得到 onlyOne,另一个结果明显是 eor ^ onlyOne.
题III
给你一个整数数组 nums ,除某个元素仅出现一次外,其余每个元素都恰出现三次 。请你找出并返回那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
class Solution {
public int singleNumber(int[] nums) {
int a = 0, b = 0;
for(int num : nums){
b = ~a & (b ^ num);
a = ~b & (a ^ num);
}
return b;
}
}
本题中,用a,b记录元素出现的次数,即 a,b 的某个二进制位组合为(0,0),(0,1),(1,0)。分别代表元素出现的次数从0到2,次数为3时回到(0,0),在进行运算后,b的某个二进制位为0即可代表元素出现了三次,而为1代表元素仅出现一次,那么b的二进制中的1就是出现一次的元素的对应二进制位上的1,那么b就等于出现一次的这个元素,最后结果返回b.
要知道,异或运算:x ^ 0 = x , x ^ 1 = ~x
列出状态转换:
n | a | b | a(转换后) | b(转换后) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 0 | 1 | 0 |
1 | 0 | 0 | 0 | 1 |
1 | 0 | 1 | 1 | 0 |
1 | 1 | 0 | 0 | 0 |
将 a 分为1和0进行讨论,发现 a 为1时b转换后恒为0,a 为0时 b 转换后为 b 与 n 的异或运算结果。
根据状态转换就可以得到b = b ^ n & ~a
转换后再列出转换图,发现 a 和 b 的状态转换是等价的,可以用同样的公式计算 a.
以上为对一位的运算,扩展到多位同样生效。