剑指offer打卡Day14:数组中只出现一次的数字
题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
解析:
-
看到题目的时候确实 有点轻敌了:
-
乍一看题目,哟吼,这不是就简单的数组计数器就可以搞定了吗?
-
然后一顿哐哐操作写好算法与测试类:
@Test public void ttest1() { int arr[] = {1, 1, 2, 2, 3, 4, 4, 5}; System.out.println(Arrays.toString(FindNumsAppearOnce_MyTest(arr))); } //num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 public int[] FindNumsAppearOnce_MyTest(int[] array) { int[] ints = new int[array.length]; int[] result = new int[2]; int count = 0; for (int anArray : array) { ints[anArray]++; } for (int i = 0; i <array.length; i++) { if (ints[i]==1){ result[count++]= i; } } return result; } /* 输出结果: [3, 5] 稳了,没毛病。 */
-
结果将算法稍作改写丢到牛客网上:
//num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { int[] ints = new int[array.length]; int count = 0; for (int i = 0; i < array.length; i++) { ints[array[i]]++; } for (int i = 0; i <array.length; i++) { if (ints[i]==1){ count++; if (count == 1) { num1[0] = i; } else { num2[0] = i; } } } } }
顿时报错:
大意了啊😓 实在妹又想到还有这么大的数字,这对int[]
数组来说确实是不可行的。也就是说用数组计数器的方法不够全面。
-
-
不能用数组计数器做那得咋整啊?
-
不过遍历多次总感觉可以继续优化,查阅资料:
-
对于查找只出现一次的问题可以用
^ 异或
运算进行解决异或的性质:对于整数a,有 (1)a^a=0 (2)a^0=a (3)a^b^c=a^(b^c)=(a^c)^b
-
因此首先将数组异或运算一次后会出现一个结果:
根据上述公式(3)是只出现一次的两个数(A^B的结果),这个结果的二进制中的1,表现的是A和B两个数字在二进制上的不同位。
int result = 0; for (int i = 0; i < arr.length; i++) { result ^= arr[i]; } System.out.println(result);
-
接下来就是如何将(A^B)的结果拆分为A和B
- (A^B)表现的是A和B的不同的位
- 我们就取第一个1所在的位数(可以左移)
- 假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。
- 遍历一次后,由于相同数字所有位都相同,所以相同的数肯定在一个组,而不同的数,肯定不在一组。
- 最后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
-
解答:
//解答一:哈希算法
//在Map中用value标识出现的次数,用key存储元素
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i=0; i < array.length; i++){
if(map.containsKey(array[i]))
//如果出现过一次则直接存入{`出现两次的元素`:2}
map.put(array[i],2);
else
map.put(array[i],1);
}
int count = 0;
for(int i=0; i < array.length; i++){
if(map.get(array[i]) == 1){
if(count == 0){
num1[0] = array[i];
count++;
}else
num2[0] = array[i];
}
}
}
//解答2:运用位移和^运算
public String FindNumsAppearOnce_withOperation(int [] array) {
int num1[] = new int[1];
int num2[] = new int[1];
int xor1 = 0;
for(int i=0; i < array.length; i++){
xor1 = xor1^array[i];
}
//在xor1中找到第一个不同的位对数据进行分类,分类为两个队列对数据进行异或求和找到我们想要的结果
int index = 1;
while((index & xor1)==0)
//因为可能有多个位为1所以需要求一下位置
index = index <<1;
int result1 = 0;
int result2 = 0;
for(int i=0; i < array.length; i++){
if((index & array[i]) == 0)
result1 = result1^array[i];
else
result2 = result2^array[i];
}
num1[0] = result1;
num2[0] = result2;
return num1[0]+","+num2[0];
}