剑指offer-40-数组中只出现一次的数

题目

一个整型数组里除了两个数字之外,其他的数字都出现了两次。
请写程序找出这两个只出现一次的数字。

    看到这个问题我们可以想到的最简单的方法就是遍历数组,统计出现的次数,但是这样时间复杂度比较高。
    可以使用异或来解决。
异或的性质:
1.对于任何数同自己异或为0,同0异或为自己。
2.连续和同一因子异或结果为自己。

例如:
a ^ 0 = a;
a ^ a = 0;
a ^ b ^ a = b;

接下来看一下解题思路:

思路一:

    可以借助辅助空间来提高时间复杂度。
这里可以使用HashSet集合,遍历数组将值存到set里面如果已经存在就删除集合里的元素,最后集合里剩余的就是只出现一次的数字。
    HashSet集合为了避免元素重复,在添加时会进行判断:
    若集合里面不存在该元素,存入并返回true;
    若已经存在该元素,则返回false;
代码示例:

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if (array == null || array.length < 2) {
            return;
        }

        Set<Integer> set = new HashSet<>();

        for (int i = 0; i < array.length; i++) {
            if (!set.add(array[i])) {
                set.remove(array[i]);
            }

        }
        
        Object[] result = set.toArray();

        num1[0] = (int) result[0];
        num2[0] = (int) result[1];
    }
总结

    除了可以使用HashSet还可以使用LinkedList或者ArrayList。

思路二

    用位运算实现,如果将所有所有数字相异或, 则最后的结果肯定是那两个只出现一次的数字异或的结果,所以根据异或的结果1所在的最低位,把数字拆分成两半,每一半里都还有只出现一次的数据和成对出现的数据(这样就演变为在一个数组里面找只出现一次的数字)继续对每一半相异或则可以分别求出两个只出现一次的数字

代码示例:

public void FindNumsAppearOnce2(int [] array, int[] num1, int[] num2) {
        if (array == null || array.length < 2) {
            return;
        }

        int result = 0;
        num1[0] = 0;
        num2[0] = 0;

        for (int i = 0; i < array.length; i++) {
            result ^= array[i];
        }
        //找出最低位的1在第几位
        int offset = 0;
        while ((result & 1) == 0 && offset < Integer.SIZE) {
            result >>= 1;
            offset++;
        }

        for (int i = 0; i < array.length; i++) {
            if(IsBit(array[i], offset)) {
                num1[0] ^= array[i];
            } else {
                num2[0] ^= array[i];
            }
        }
    }

    /**
     * 判断一个数的某一位是不是1
     * @param num
     * @param offset
     * @return
     */
    private boolean IsBit(int num, int offset) {
        num >>= offset;

        return (num & 1) == 1;
    }

演化

    其实这道题还可以进一步演化。
数组中只有一个数字出现了1次,其他的都出现3次。
也可以用到位运算的方法来解决:每个bit的出现次数应该是3的倍数,
如果某个bit出现的次数不是3的倍数,说明只出现一次的数字在该bit处是1
。统计每个数的每个位1出现的次数,不是3的倍数的就是只出现一次的数。
代码示例:

public int FindNumsAppearOnce3(int[] array) {
        if (array == null || array.length < 1) {
            return 0;
        }

        int[] bits = new int[Integer.SIZE];

        for (int num : array) {
            //统计某一位是1的个数
            for (int i = 0; i < bits.length; i++) {
                if ((num & 1) == 1) {
                    bits[i]++;
                }
                //更新num的值
                num >>>= 1;
            }
        }
        int result = 0;
        for (int i = 0; i < bits.length; i++) {
            //如果某一位不是3的倍数,说明这一位为1的这个数只出现了一次
            if ((bits[i] % 3) != 0) {
                result |= (1 << i);
            }
        }

        return result;
    }
总结

     这种思路适用于数组里面数字比较多的,因为嵌套在里面的for循环最多只循环32次;但是对于数组大小小于32的就不适合用这种方法了。可以使用遍历数组的方法,或者通过辅助空间解决。

你可能感兴趣的:(算法,Java,算法)