题目描述

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
示例1
输入:[1,4,1,6]
返回值:[4,6]
说明:返回的结果中较小的数排在前面

题目分析

计算数组中出现的数字次数的题,都可以使用hashmap来统计,最后遍历hashmap来获取满足要求次数的数字;
因为题目的其他数字都只出现两次,而相同的数异或结果为0,0与任何数相与仍然是原数,如果只有一个数不同的话,可以直接将所有的数异或即可;但是有两个数的话,所有数的异或结果相当于这两个数的异或结果,可以通过这个结果,来将数组分为两个部分,分别异或。

解题思路

方法1:使用hashmap统计数字出现的个数
1.使用hashmap,第一次出现的数,设置value为1,第二次出现,设置value为2;
2.通过map.entrySet()来遍历map中的数据,判断value是否为1,纪录结果;

方法2:使用异或运算,分开统计两个数字
1.首先将数组中所有数的进行异或,保存异或结果 x;
2.判断异或结果 x (二进制)第一次出现1的位置,对应的值为 mid,也即这两个数不同的地方;
3.通过判断和 mid相与是否为0,可以区分两个数字到不同的结果中,其他出现两次的数,一定会被分配到同一个部分;
4.对这两个部分分别进行异或,得到的两个结果即为目标数字。

代码实现

方法1:使用hashmap统计数字出现的个数

    public int[] FindNumsAppearOnce (int[] array) {
        int[] res = new int[2];
        // 借助map来统计数字出现的次数
        HashMap<Integer, Integer> map = new HashMap<>();
        // 结果下标
        int index = 0;
        for(int i=0;i<array.length;i++){
            if(map.containsKey(array[i])){
                // 出现两次
                map.put(array[i], 2);
            }else{
                // 首次出现
                map.put(array[i], 1);
            }
        }
        for(Map.Entry e:map.entrySet()){
            // 遍历map中的数据
            if(e.getValue().equals(1)){
                // 找到出现1次的放到jied
                res[index++] = (int)e.getKey();
            }
        }
        // 让两个数有序
        if(res[0] > res[1]){
            int tmp = res[0];
            res[0] = res[1];
            res[1] = tmp;
        }
        return res;
    }

时间复杂度:O(n),需要遍历两次数组,花费时间为2*n,时间复杂度O(n);
空间复杂度:O(n),需要使用hashmap来保存出现的数字,花费的空间主要为 (n-2)/2 + 2,对于前面的算式,因为出现的数字除了两个数字是出现一次,其他的数字是出现了两次所以其他数字消耗的空间为 (n-2)/2 ,再加上这两个数字,总共消耗 (n/2 + 1),空间复杂度为O(n);

方法2:使用异或运算,分开统计两个数字

    public int[] FindNumsAppearOnce (int[] array) {
        // write code here
        if(array == null || array.length <= 1) return new int[2];
        // 获取两个数字的异或值,其他相同的异或为0
        int x = 0;
        for(int i=0;i<array.length;i++){
            x ^= array[i];
        }
        int mid = 1;
        // 找到异或值第一次为1的位置,即这两个数字第一次不同的位置
        while((mid & x)==0){
            mid = mid<<1;
        }
        // 设置两个结果值
        int res1 = 0;
        int res2 = 0;
        for(int i=0;i<array.length;i++){
            // 通过不同位置的值来区分这两个数
            if((mid&array[i]) == 0){
                res1 ^= array[i];
            }else{
                res2 ^= array[i];
            }
        }
        // 让两个数有序
        if(res1 > res2){
            int tmp = res1;
            res1 = res2;
            res2 = tmp;
        }
        return new int[]{res1, res2};
    }

时间复杂度:O(n),需要遍历两次数组,和找到第一次出现1的位置(最多32次,属于常数),花费时间为2*n,时间复杂度O(n);
空间复杂度:O(1),需要使用常量个数的变量保存结果和中间结果,空间复杂度为O(1);