题目
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
解题思路
方法一(hashmap法):
遍历数组,将这个数组中的数字逐个存入到hash表中。
如果hashmap中,没有这个数字,就将这个数字存入到hashmap中,是将这个数值作为键,然后将它的值置为1。
如果hashmap中,已经有该数,就将这个数字移除。
那么这样得到的结果就是:以那两个仅出现了一次的数字作为键,然后其对应的值是1。
最后,申请数组空间,将这两个对应的键的数字(通过hashmap.keyset()取到),存入到数组的0和1位置上。
便可以完成操作。
方法二(异或法):
异或:两个相同的数字,进行异或,等到的结果是0。
如果这个数组里面只有1个只出现一次的数,那把这个数组里面的数字全部异或一遍,就可以得到那个只出现了一次的数字。
但是现在这个数组中有2个只出现一次的数,那怎么办呢?
我们可以想到如果能把这数组分成两部分,每一部分中都只有一个仅出现一次的数字,然后这两部分分别取异或,是不是就可以得到那两个只出现了一次的数字呢?
现在的问题是:怎么把这个数组进行分组呢?
我们从头到尾对这个数字进行异或,最后可以得到的结果就是这两个数字取异或的结果。
由于是两个不同的数字取异或,因此这些结果中一定至少有一位是1。我们把结果中是1的位置记为第n位,通过结果我们可以得知第n位上的数字一定是1和0。
接下来,我们根据数组中每个数字的第n位上的数字是否为1来进行分组。
若能将这个数组分为两个数组,其中,每一个数组中只包括一个只能出现一次的数字。然后将这两个数组分别从头到尾异或,那么就可以得到这两个数字了。
测试用例
功能测试(数组中有多对重复的数字;无重复的数字)
代码
package mySword;
import java.util.ArrayList;
import java.util.HashMap;
//一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。
// 请写程序找出这两个只出现一次的数字。
public class FindNumsAppearOnce {
//法一:
// public static void FindNumsAppearOnce(int[] array, int[] num1, int[] num2){
// ArrayList<Integer> list = new ArrayList<Integer>();
// HashMap<Integer, Integer> temp = new HashMap<Integer, Integer>();
// for(int i = 0; i < array.length; i++){
// if(temp.containsKey(array[i])){
// temp.remove(array[i]);
// }else{
// temp.put(array[i], 1);
// }
// }
//
// int[] a = new int[array.length];
// int i = 0;
// for(Integer k : temp.keySet()){
// a[i] = k;
// i++;
// }
// num1[0] = a[0];
// num2[0] = a[1];
// }
//法二:异或
// public static void FindNumsAppearOnce(int[] array, int[] num1, int[] num2){
// if(array == null || array.length < 2){
// return;
// }
//
// //逐个异或,得到最终异或的结果。
// //这个结果肯定不应该是1
// int resultExclusiveOR = 0;
// for(int i = 0; i < array.length; i++){
// resultExclusiveOR ^= array[i];
// }
//
// //这块是为了找到1的下标
// int indexOf1 = 0;
// //如果最终的结果不是0(就是该结果会和1取与为0)而且这个数组的下标应该要小于32位
// while(((resultExclusiveOR & 1) == 0) && (indexOf1 <= 4*8)){
// resultExclusiveOR = resultExclusiveOR >> 1;//那么将这个数字右移
// indexOf1 ++;//下标也跟着增加
// }
//
// //申请两个数组空间,并将它们初始化。
// num1[0] = 0;
// num2[0] = 0;
//
// //0和任何数字异或都可以得到这个数字本身
// for(int i = 0; i < array.length; i++){
// if(((array[i] >> indexOf1) & 1) == 1)
// num1[0] ^= array[i];
// else
// num2[0] ^= array[i];
// }
// }
//法二:异或
public static int[] findNumsAppearOnce(int[] data){
if(data == null || data.length < 2){
return null;
}
int result = 0;
for(int i = 0; i < data.length; i++){
result ^= data[i];
}
int indexOf1 = findFirstOf1(result);
int[] res = new int[]{0, 0};
if(indexOf1 < 0){
return res;
}
for(int i = 0; i < data.length; i++){
if((data[i] & indexOf1) == 0){
res[0] ^= data[i];
}else{
res[1] ^= data[i];
}
}
return res;
}
//怎么找?如果数字是负的,直接就不用考虑了。
//我们先令indexOf1的初值是1
//当这个数字不为0的时候,
//如果这个数与1取与结果为1的话,就可以返回,就证明它的第一个数字是1;
//如果这个数与1取与结果不为1的话,就需要将这个数字右移1位,然后将indexOf1的大小乘2.
public static int findFirstOf1(int num){
if(num < 0){
return -1;
}
int indexOf1 = 1;
while(num != 0){
if((num & 1) == 1){
return indexOf1;
}else{
num = num >> 1;
indexOf1 *= 2;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 2, 5, 2, 1, 7};
int[] res = findNumsAppearOnce(arr);
for(int num : res){
System.out.print(num + " ");
}
}
}
总结
1、判断某个数x的第n位(如第3位)上是否为1,
1)通过 x & 00000100 的结果是否为0 来判断。(不能根据是否等于1来判断)
2)通过(x>>3)&1 是否为0 来判断
2、将一组数字分为两组,可以根据某位上是否为1来进行分组,即根据和1相与(&1)的结果来进行分组。
3、将某个数x右移m位,一定要写成 x=x>>m;而不能只写成 x>>m;这个语句