面试官想要的复杂度:时间O(N),空间O(1)
在看答案前,我只知道只有一个数字出现的求法,就是把所有数组异或一下,a^a=0,a^0=a,相同的数字会被抵消成0,所以最后就剩下出现一次的数字
实在想不出来O(1)的空间,两个要怎么求
书上是这么解释的,依然用异或的做法,如果能把数组分成两组,两个只出现一次的数字分在两个数组,其余相同的数字在同一个数组里,比如题目的[1,2,3,3,4,4],如果能分成[1,3,3]和[2,4,4] 或者 [1,3,3,4,4]和[2]就好了,这样两个数组分别求异或就是答案1和2了。
那么这两个数组要怎么分呢
书上是这么做的,先对原数组求异或,那么结果就是1和2的异或结果011对吧,并且结果一定不为0,即结果的二进制里面至少有一个1,这个1是因为两个只出现一次的数字在这一位上不同造成的,1是001,2是010,在最右边一位不一样,所以结果的最后一位是1。
因此我们可以通过最后一位是1还是0,将原数组分成两组,这样1和2必定分开,并且其他相同的数字一定在同一组,结果就是[1,3,3]和[2,4,4];如果按照倒数第二位分,也可以分成[1,4,4]和[2,3,3],异或后的结果是一样的,只要我们能找到原数组异或结果二进制中某一个1的位置,这里我们选择从右边开始选,为什么不从左边开始呢,因为右边方便计算
所以解题的步骤为
1、原数组异或一下
2、求异或结果中1的位置(从右开始数,从0开始)
3、对原数组中的每一个数字判断内个位置上是不是1,分别异或
class Solution {
public int[] findNumsAppearOnce(int[] nums) {
int[] res=new int[2];
int xor=0;
for(int i:nums){
xor^=i;
}
int index=getFirst1(xor);
for(int i:nums){
if(match1(i,index)){
res[0]^=i;
}else{
res[1]^=i;
}
}
return res;
}
//从右边数,第一个为1的下标,从0开始
int getFirst1(int n){
for(int i=0;i<32;i++){
if((n&1)==0){
n>>=1;
}else{
return i;
}
}
return 0;
}
//从右数第index个位是不是1
boolean match1(int n,int index){
n>>=index;
return (n&1)==1;
}
}