剑指Offer-42-数组中只出现一次的数字

题目

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

解析

预备知识

这个题目与另一个常规的题目很像哦,就是一个整数数组中除了一个数字出现一次以外,其他数字都出现了偶数次,请找出这个数字。
我们都知道异或操作,它属于位运算,同一位上数相同时返回0,不相同时返回1。

11=0 1 ⊕ 1 = 0

00=0 0 ⊕ 0 = 0

10=1 1 ⊕ 0 = 1

因为相同的数字,它们所有位上数字都是对应相同,所以相同的数字异或为0,而0与任何数异或还是该数。所以我们的思路就是对数组中所有的数字进行一次异或操作,由于相同的数字会成对异或为0,那么中最终异或的结果就是出现一次的数字。

    public static int FindNumsAppearOnce(int [] array) {
        if(array == null || array.length < 1) {
            return 0;
        }
        int result = 0;
        for(int i = 0; i < array.length; i++) {
            result ^= array[i];
        }
        return result;
    }
思路一

当数组中仅存在一个出现一次的数字,可以利用一次遍历异或操作找出这个数字。那么出现两次如何解决呢?
如果我们还是利用预备知识的思路对整体进行异或,那么最终得到的结果是这两个出现一次的数做异或后的结果。我们如何利用这个结果做点文章呢?
我们可以这样想,如果数组能分成2部分就好了,出现一次的数字正好分开处于这两个子数组中。接下来我们只需对这两个子数组进行异或,即可得到所要的结果。问题转换为如何使得分开的2个子数组正好分别包含出现一次的数字。
通过刚刚对整体数组异或得到是两个出现一次的数做异或后的结果,该数中所有位为1的位置表示这两个数在该位上不同,而所有位为0的位置表示这两数在该位上相同。所以我们可以根据任意一位为1的位置来把这两个数放到不同的子数组中。同时又因为成对相同的数字在该位也相同,所以我们可以确保成对相同的数字最终会放到同一个子数组中,问题最终变为2个与预备知识中题目相同的题目。

    public static void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
        if(array == null || array.length < 2) {
            return;
        }
        int xorResult = 0;
        for(int i = 0; i < array.length; i++) {
            xorResult ^= array[i];
        }
        int firstOneIndex = findFirstOne(xorResult);
        for(int i = 0; i < array.length; i++) {
            if(isOneByIndex(array[i], firstOneIndex)) {
                num1[0] ^= array[i];
            } else {
                num2[0] ^= array[i];
            }
        }
    }


    private static boolean isOneByIndex(int number, int index) {
        return ((number >> index) & 1) == 1;
    }

    private static int findFirstOne(int number) {
        int index = 0;
        while((number & 1) == 0) {
            index++;
            number >>= 1;
        }
        return index;
    }
思路二

思路一一般很难想到,因为它需要特定的技巧才可以做。
不是一般性,我们可以借助set容器来移动相同的数字,最终保留的就是2个不同的且出现一次的数字。
注:此方法纯属娱乐,只为江湖救急。

    public static void FindNumsAppearOnce2(int [] array, int num1[], int num2[]) {
        if(array == null || array.length < 2) {
            return;
        }
        Set<Integer> nums = new HashSet<>();
        for(int i = 0; i < array.length; i++) {
            if(nums.contains(array[i])) {
                nums.remove(array[i]);
            } else {
                nums.add(array[i]);
            }
        }
        Iterator<Integer> iterable = nums.iterator();
        num1[0] = iterable.next();
        num2[0] = iterable.next();
    }

总结

出现一次的数字之类可以多结合异或来做,或者考虑其他位运算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值