题目
一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。
解析
预备知识
这个题目与另一个常规的题目很像哦,就是一个整数数组中除了一个数字出现一次以外,其他数字都出现了偶数次,请找出这个数字。
我们都知道异或操作,它属于位运算,同一位上数相同时返回0,不相同时返回1。
因为相同的数字,它们所有位上数字都是对应相同,所以相同的数字异或为0,而0与任何数异或还是该数。所以我们的思路就是对数组中所有的数字进行一次异或操作,由于相同的数字会成对异或为0,那么中最终异或的结果就是出现一次的数字。
public static int FindNumsAppearOnce(int [] array) {
if(array == null || array.length < 1) {
return 0;
}
int result = 0;
for(int i = 0; i < array.length; i++) {
result ^= array[i];
}
return result;
}
思路一
当数组中仅存在一个出现一次的数字,可以利用一次遍历异或操作找出这个数字。那么出现两次如何解决呢?
如果我们还是利用预备知识的思路对整体进行异或,那么最终得到的结果是这两个出现一次的数做异或后的结果。我们如何利用这个结果做点文章呢?
我们可以这样想,如果数组能分成2部分就好了,出现一次的数字正好分开处于这两个子数组中。接下来我们只需对这两个子数组进行异或,即可得到所要的结果。问题转换为如何使得分开的2个子数组正好分别包含出现一次的数字。
通过刚刚对整体数组异或得到是两个出现一次的数做异或后的结果,该数中所有位为1的位置表示这两个数在该位上不同,而所有位为0的位置表示这两数在该位上相同。所以我们可以根据任意一位为1的位置来把这两个数放到不同的子数组中。同时又因为成对相同的数字在该位也相同,所以我们可以确保成对相同的数字最终会放到同一个子数组中,问题最终变为2个与预备知识中题目相同的题目。
public static void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
if(array == null || array.length < 2) {
return;
}
int xorResult = 0;
for(int i = 0; i < array.length; i++) {
xorResult ^= array[i];
}
int firstOneIndex = findFirstOne(xorResult);
for(int i = 0; i < array.length; i++) {
if(isOneByIndex(array[i], firstOneIndex)) {
num1[0] ^= array[i];
} else {
num2[0] ^= array[i];
}
}
}
private static boolean isOneByIndex(int number, int index) {
return ((number >> index) & 1) == 1;
}
private static int findFirstOne(int number) {
int index = 0;
while((number & 1) == 0) {
index++;
number >>= 1;
}
return index;
}
思路二
思路一一般很难想到,因为它需要特定的技巧才可以做。
不是一般性,我们可以借助set容器来移动相同的数字,最终保留的就是2个不同的且出现一次的数字。
注:此方法纯属娱乐,只为江湖救急。
public static void FindNumsAppearOnce2(int [] array, int num1[], int num2[]) {
if(array == null || array.length < 2) {
return;
}
Set<Integer> nums = new HashSet<>();
for(int i = 0; i < array.length; i++) {
if(nums.contains(array[i])) {
nums.remove(array[i]);
} else {
nums.add(array[i]);
}
}
Iterator<Integer> iterable = nums.iterator();
num1[0] = iterable.next();
num2[0] = iterable.next();
}
总结
出现一次的数字之类可以多结合异或来做,或者考虑其他位运算。