今天的两道题目来源于《剑指 offer》,是对之前很熟悉的题目只出现一次的数字的扩展,十分巧妙的用二进制进行了分组。妙,太妙了!
题目一 数组中只出现一次的两个数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。
请写程序找出这两个只出现一次的数字。
你可以假设这两个数字一定存在。
数据范围
数组长度 [1,1000]。
样例
输入:[1,2,3,3,4,4]
输出:[1,2]
解答
根据找唯一只出现一次的经验,会想到用异或运算,这里异或运算会将所有重复的数计算掉,只剩下只出现一次的两个数字的异或 x^y,如何分别求得 x 和 y 呢?
我们知道, x^y 中一位为 1,说明 x 和 y 的这一位一定一个为 1,另一个为 0。
根据这点将 nums 分为两簇,一簇是这一位为 1 的,一簇是这一位为 0 的。就将问题转化为了在两簇内分别求唯一的只出现一次的数字。妙!!!
int temp = 0;
for (int num : nums) {
temp ^= num;
}
// 找temp二进制 中 第一个为1的位
int k = 0;
while (((temp >> k) & 1) == 0) k++;
int[] result = new int[2];
result[0] = 0;
// 根据 这一位是不是 1 来分组
for (int num : nums) {
if (((num >> k) & 1) == 1) result[0] ^= num;
}
result[1] = result[0] ^ temp;
return result;
题目二 137. 只出现一次的数字 II
这道题的不同在于重复的数字会出现三次而不是两次。因此不能用异或运算一轮将重复的全部清除。
- 但是仍然可以通过统计每一位上 1 出现的次数,如果是重复的数,这一位的 1 的次数一定是 3 的倍数,如果是 3*n + 1,那就是独立数字在这一位是 1。
- 本题还需要注意的是,可能出现负数,因此我们用到的是无符号的右移。避免负数在右移的过程中高位补 1,影响计数。
class Solution {
public int singleNumber(int[] nums) {
int result = 0;
// 统计每一位 1 出现的次数(无符号右移)
int[] cnts = new int[32];
for (int i = 0; i < 32; i++){
for (int num : nums) {
cnts[i] += (num >>> i) & 1;
}
}
// 复现出结果
for (int i = 31; i >= 0; i--) {
result <<= 1;
result |= cnts[i] % 3;
}
return result;
}
}