只出现一次的数字
代码及思路
按位或 | ,有一为一,其他为零。(二进制位中对应运算的两个二进制位,只要有一个为1,其结果就是1)
0011 10进制3
0010 10进制2
0011 10进制3(结果)
按位与 & ,均一为一,其他为零。(对应的二进制位均为1,结果才是1)
0011 10进制3
0010 10进制2
0010 10进制2(结果)
按位异或 ^,不同为一,其他为零。(比如 (1^0) (0^1))
0011 10进制3
0010 10进制2
0001 10进制1(结果)
/**
* 这里有一个“定理” :
* 两个相同的数字进行异或,结果为零。
* 零和任何数字进行异或,结果为那个任何数字。
*
* 拓展推理:
* 二进制位进行的运算,与,异或,或,与顺序无关。
* 题目中,只有一个数字只出现一次,其他数字都出现两次。
* 由于已经知道以二进制位进行的运算和顺序无关。
* 那么最终必然为: 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 = 4。(举个栗子)
*
*/
public int singleNumber(int[] nums) {
int x = nums[0];
for( int i = 0 ; i < nums.length ; i ++ ){
x ^= nums[i];
}
return x;
}
数组中出现两次的数字
代码及思路
public int[] singleNumbers(int[] nums) {
/**
* 在已经懂上一题的情况下,这一题会变得简单一些...
* 这题的思路是让这两个不同的数字分组,让它们处在不同的组(其他相同的数字在哪一组无所谓,但是他们一定是成对在一组的),
* 这样一来不就又变成第一题了吗
*/
int res = 0;
/**
* 得到所有数字的异或结果res
* 取res上对应的二进制为1,让其转换成十进制div
* 然后让div与所有的数字进行按位于&,结果为零的为一组
* 不为零的为另外一组,具体为什么,往后看
*/
for (int i = 0; i < nums.length; i++) {
res ^= nums[i];
}
int div = 1;
/**
*
* 官方的解释是找res中二进制位为1的任意一位,让其转变成十进制div
* 举个栗子 res 0111 div 可以为 1, 2, 4(注意这里是可以随意挑选的,但是挑选的位会影响分组)
*
* 来看下为什么能达到分组的效果(首先如果懂了第一题的话,就知道异或操作,满足“交换律”等等)
*
* 先用一个例子展开
* 4,4,5,3,2,2,7,7
* 4 0011
* 5 0101 目标数
* 3 0011 目标数
* 2 0010
* 7 0111
*
* res 0110 异或结果
*
* ---------------------------------------------------------------------------
* 5 0101 目标数
* 3 0011 目标数
* div 0100 (假设用这个二进制位来进行分组,按位于&结果为零的为一组,否则为另外一组)
* (按位于&,同一位一,其他为零)
* (所以只有和div一样,在对应的位置上,该二进制位为1,结果才会为1)重点<---------
*
* **相同的两个数和div进行按位于&,结果是一样的,它们一定在一组。重点<---------
* (具体在哪一组和div的选择有关系,也和数字有关系,比如上面例子4,7就不在一组)
*
* **目标数和div按位于&,一定在不同的组(因为所有数字的异或结果,其实就是这两个不同数字异或得出的结果,异或,不同为一,其他为零)重点<---------
* 4 0011
* 2 0010
* 7 0111
*
* 用res中的任意一位为1的二进制位,与所有数字进行 按位与& 操作
* 结果为零的一组,不为零的为另外一组
* 重点来了,为什么呢??
*/
while ((div & res) == 0) {
// 乘以2
div <<= 1;
}
int a = 0, b = 0;
for (int i = 0; i < nums.length; i++) {
if ((div & nums[i]) != 0) {
a ^= nums[i];
} else {
b ^= nums[i];
}
}
int []A = {a, b};
return A;
}