题目
数组Nums, 只有一个数只出现一次,其余的数都会出现3次。
例如:
输入: Nums[2,2,3,2]。
输出:3
考察知识点
位运算
- 异或
异或有一个特性,即相同的数之间异或后,结果是0。有一个题目与此题目类似。它是数组中除了一个数字只出现一次,其余数字都出现两次。 对于那个题目,我们可以采用数字之间异或的特性。数组内的整数,依次异或,最后运算的结果,即为数组中只出现一次的数。例如:
输入:nums[1, 0, 2, 1, 0]
输出:2
二进制形式的位运算推到过程如下:
两两异或:1 ^ 0 ^ 2 ^ 1 ^ 0
0001
^ 0000
= 0001
0001
^ 0010
= 0011
0011
^ 0001
= 0010
0010
^ 0000
= 0010 => 最终结果为数组中只出现一次的整数2
- 右移
有符号整数规定如下:
- 正数的二进制位右移1位,最左边补上0。
- 负数的二进制位右移1位,则最左边补上1.
- 左移
与上面同理,有符号整数里,
- 正数的二进制位左移1位,最右边补上0。
- 负数的二进制位左移1位,最右边补上0.
//负数左移1位的代码如下:
int i = -1;
int b = i << 1; //左移1位,最右位补0
System.out.println(" b = " + b); //b = -2
- 与(&1)
整数&1,即可把整数除最右边的数位,都设置为0. 可用来取整数二进制形式中最右边的一位。
解题思路
public int getNumber(int[] nums) {
//1. 数组sumBits用来保存数组nums中所有整数的二进制形式中第i个数位之和。
int[] sumBits = new int[32];
for(int num: nums) {
for (int i = 0; i < 32; i++) {
sumBits[i] += ((num >> (31 - i)) & 1); //2.用来得到整数num的二进制形式中从左数起第i个数位。
}
}
//此时每个数位的和已经求出来了
//我们再来计算每一个数位整数3的余数,来确认最终数字二进制形式每一个位结果。
int result = 0;
for(int i = 0; i <32; i++) {
result = (result << 1 )+ sumBits[i] %3; //左移一位,补上最新结果。
}
return result;
}
总结
求数组中只出现一次的整数,主要利用一个整数是由32个0或1组成,而数组中重复出现3次的整数,其二进制形式中相同的数位相加的和,整除3后的余数是0,而只出现一次的数,整除3取余后,刚好是余数1。那么这样只出现一次的任意第i个数位可以由数组中所有数字的第i个数位之和推算出来。
- 取整数num的二进制形式中的从左到右算,数位i的值时,可以套用公式:
(num >> (32 - i) ) & 1
- 任何整数异或它自己结果是0。
扩展:举一反三
假设数组Nums中,只有一个整数出现m次,其他数都出现n次,求唯一出现m次的那个数字。m不能被n整除。
解答思路
数组中,对于出现n次的整数,它的二进制形式中的数位i的和一定能被n整除。那么出现m次数的数字的数位i一定是0,否则一定是1.
(参考上面题目思路,解决同类型问题。余数是出现m次的数字相位之和, 我们只要把余数除以m,等于1,如果余数是0
,则所求数字的相位i为0。