题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
(offer书上有要求,空间复杂度O(1),时间复杂度O(n))


很明显,如果没有额外的要求,解决的方法很多。而按照offer书上的要求,本题的考察点在位运算。

解法1:位运算

核心:两个相同的数字异或等于0。如果数组中只有一个数字只出现一次,我们从头到尾异或每个数字,那么最终的结果刚好是那个只出现一次的数字。因为那些出现两次的数字全部在异或中抵消掉了。
  如{4,7,8,7,8}显然4为所求。过程如下:{100,111,1000,111,1000} 100^111=11,11^1000=1011,1011^111=1100,1100^1000=100回到了4。
  调整异或的顺序,这样的抵消更直观:111^111=0,1000^1000=0,100^0=100,因此出现一次的数最终会变回来。
  而本题又有两个出现次数为1的。刚刚说的异或找数只适合一个序列中只有1个出现一次的数,而这样的数有2个时,则无法判断,因为最后的结果是这两个数异或后得到的数,那么我们异想天开一下,能不能分成两个数组,每个数组有这么一个数,并且其他的数都还出现两次,这样就可以分别比较出来,实际上可以做到!
分析:假设为数a和b,那么这么异或一轮后得到的结果c,c=a^b。由于a和b不同,所以c的二进制表示一定含有1。得到这个1的意思就是,a和b二进制表示时,一定有某一个位不同,比如100^101=1,4和5的二进制表示的个位,是不同的,所以可以以这个点为突破口,将所有数组的个位是否为1进行分组,这样就能保证,以这样的分组方式,4和5就不会分在同一组。4就分在了所有二进制个位为0的数组中,5就分在所有二进制个位为1的数组中。因此实际写代码时,需要找到这么一个不同的1,从而进行分组。针对这个细节,进行设计。

public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int temp = 0;
        for(int i=0; i < array.length; i++)
           temp = temp^array[i];//得到a^b=c
        int index = 1;
        while((index & temp)==0)//开始找c二进制表示时,哪个位是1
            index = index <<1;//和1相与为0,说明为0,需要继续移位比较(对index乘2)
        //来到这里,说明找到某个位上的数是1了.而获得的index最高位为1,其余位是0
        int result1 = 0;
        int result2 = 0;
        for(int i=0; i < array.length; i++){
            if((index & array[i]) == 0)
            //index只有1个位置是1,进来的数该位置也是1,就分到该组
                result1 = result1^array[i];
            //用result1来异或(一边分组,一边异或求值)
            else//到这里说明,某个数的指定位置不为1,分到另一个组
                result2 = result2^array[i];
        }
        num1[0] = result1;
        num2[0] = result2; 
    }
}
//从其他人那看到,这样做除法也是可以的。不过没太弄清index++的原理
 int index = 1;
 while((temp&1)==0){
          temp = temp>>1;  
          index++;
   }

解法2:

在不考虑诸多约束条件时,使用HashMap也不失为一个好方法。

import java.util.HashMap;
public class Solution {
    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]))
               map.put(array[i],2);
            else
                map.put(array[i],1);
        }
        boolean flag = true;
        for(int i=0; i < array.length; i++){
            if(map.get(array[i]) == 1){
                if(flag == true){
                    num1[0] =  array[i];
                    flag = false;
                }else
                    num2[0] =  array[i];
            }
        }
    }
}

在题解中,还有使用栈的,不过他调用了sort方法,尽量少使用工具类的方法。所以这里就不做相关总结了,主流解法就是第一个位运算的。