题目
一个整型数组里除了两个数字之外,其他的数字都出现了两次。
请写程序找出这两个只出现一次的数字。
看到这个问题我们可以想到的最简单的方法就是遍历数组,统计出现的次数,但是这样时间复杂度比较高。
可以使用异或来解决。
异或的性质:
1.对于任何数同自己异或为0,同0异或为自己。
2.连续和同一因子异或结果为自己。
例如:
a ^ 0 = a;
a ^ a = 0;
a ^ b ^ a = b;
接下来看一下解题思路:
思路一:
可以借助辅助空间来提高时间复杂度。
这里可以使用HashSet集合,遍历数组将值存到set里面如果已经存在就删除集合里的元素,最后集合里剩余的就是只出现一次的数字。
HashSet集合为了避免元素重复,在添加时会进行判断:
若集合里面不存在该元素,存入并返回true;
若已经存在该元素,则返回false;
代码示例:
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
if (array == null || array.length < 2) {
return;
}
Set<Integer> set = new HashSet<>();
for (int i = 0; i < array.length; i++) {
if (!set.add(array[i])) {
set.remove(array[i]);
}
}
Object[] result = set.toArray();
num1[0] = (int) result[0];
num2[0] = (int) result[1];
}
总结
除了可以使用HashSet还可以使用LinkedList或者ArrayList。
思路二
用位运算实现,如果将所有所有数字相异或, 则最后的结果肯定是那两个只出现一次的数字异或的结果,所以根据异或的结果1所在的最低位,把数字拆分成两半,每一半里都还有只出现一次的数据和成对出现的数据(这样就演变为在一个数组里面找只出现一次的数字)继续对每一半相异或则可以分别求出两个只出现一次的数字
代码示例:
public void FindNumsAppearOnce2(int [] array, int[] num1, int[] num2) {
if (array == null || array.length < 2) {
return;
}
int result = 0;
num1[0] = 0;
num2[0] = 0;
for (int i = 0; i < array.length; i++) {
result ^= array[i];
}
//找出最低位的1在第几位
int offset = 0;
while ((result & 1) == 0 && offset < Integer.SIZE) {
result >>= 1;
offset++;
}
for (int i = 0; i < array.length; i++) {
if(IsBit(array[i], offset)) {
num1[0] ^= array[i];
} else {
num2[0] ^= array[i];
}
}
}
/**
* 判断一个数的某一位是不是1
* @param num
* @param offset
* @return
*/
private boolean IsBit(int num, int offset) {
num >>= offset;
return (num & 1) == 1;
}
演化
其实这道题还可以进一步演化。
数组中只有一个数字出现了1次,其他的都出现3次。
也可以用到位运算的方法来解决:每个bit的出现次数应该是3的倍数,
如果某个bit出现的次数不是3的倍数,说明只出现一次的数字在该bit处是1。统计每个数的每个位1出现的次数,不是3的倍数的就是只出现一次的数。
代码示例:
public int FindNumsAppearOnce3(int[] array) {
if (array == null || array.length < 1) {
return 0;
}
int[] bits = new int[Integer.SIZE];
for (int num : array) {
//统计某一位是1的个数
for (int i = 0; i < bits.length; i++) {
if ((num & 1) == 1) {
bits[i]++;
}
//更新num的值
num >>>= 1;
}
}
int result = 0;
for (int i = 0; i < bits.length; i++) {
//如果某一位不是3的倍数,说明这一位为1的这个数只出现了一次
if ((bits[i] % 3) != 0) {
result |= (1 << i);
}
}
return result;
}
总结
这种思路适用于数组里面数字比较多的,因为嵌套在里面的for循环最多只循环32次;但是对于数组大小小于32的就不适合用这种方法了。可以使用遍历数组的方法,或者通过辅助空间解决。