数组中只出现一次的数字
次访问
剑指offer面试题
题目
一个整型数组里面除了两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现了一次的数字,要求时间复杂度为O(n),空间复杂度为O(1).
思路
这道题,初看完全没有思路。直到看了剑指offer上面的提示,才知道原来是这么回事儿。
真心叹服里面的解法。
首先思考一个问题:假如一个整型数组里面只有一个数字出现了一次,怎么找?
书中用到的思路:异或。
因为异或有这样一个性质:A^0=A; A^A=0;
除此之外,更重要的就是它满足交换律和结合律。
比如 [A B C B A C D]数组:0^A^B^C^B^A^C^D = (A^A^B^B^C^C^D) = D.
所以利用这种思路,就可以最快找出只出现一次的数字。
那么,有两个数字出现了一次怎么办?
有了上面的思路作为铺垫,可以想到,如果能把这两个数字分到两组就好了,接下来只要对每组用上面的方法就可以求出来了。
怎么分?
这才是这道题的难点所在。
书中的思路:因为如果有两个只出现了一次且不一样的数字,则按照上面的异或思路,最终结果一定不为0.如果不为0,则一定至少有一位是1.
因为其他数字都被异或为0,只有这两个数字剩下,且异或非零。那么这两个数字的相应位上,一定一个是0,一个是1(如果都是0或者都是1,则异或为0)。而这就是分组的关键。
只要把异或结果中二进制形式 1 在第几位求出来,然后按照该位上是否为1分为两组。这样一定能确保这两个数被分在不同的两组中。
疑问:会不会把一对一样的数字分在两个组里面呢?很显然不会。因为这两个数字的每一位都一样,要么为 0,要么为1,必定会在一组中。
看到这里,就两个字:服了。
思路明白,剩下的就是实现的问题了。这里有一个小技巧,刚开始我想设计两个数组,然后存放按照位分开的数字,但是这种做法的空间复杂度就不是O(1)了。必须另想办法,书中的一种办法是:直接拿来异或就行了,反正最终要的就是该组剩下的那个数字。所以,只要把这两个数字返回就行了。
代码
package com.ans;
//数组中只出现了一次的数字
public class Solution8 {
public static void seperateArr(int[] retArr, int[] arr) {
int result = 0;
for (int data : arr) {
result ^= data;
}
if (result == 0) {
System.out.println("输入的数组不满足条件");
return;
}
int bit = 0;// 第一个不为0的位
while (result % 2 == 0) {
bit++;
result /= 2;
}
for(int i = 0; i < arr.length; i++) {
if(bitIs1(arr[i], bit)) {
retArr[0] ^= arr[i];
} else {
retArr[1] ^= arr[i];
}
}
}
//判断整数num的bit位是不是1
public static boolean bitIs1(int num, int bit) {
num >>>= bit;
return num % 2 == 1;
}
public static void main(String[] args) {
int[] arr = { 1, 1, 8, 7, 7, 3, 3, 4, 5, 4, 5, 6 };
int[] result = new int[2];// save the two numbers
seperateArr(result, arr);
System.out.println("第一个数字是:" + result[0] + ",第二个数字是:" + result[1]);
}
}