java算法面试题:奇偶数问题(2) 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,寻找这两个数


问题定义

问题一:在一个数字序列中,有一个数出现了奇数次,其他数都出现了偶数次,找到这个奇数。传送通道
问题二:在一个数字序列中,有两个数出现了奇数次,其他数都出现了偶数次,寻找这两个数。

一、背景知识

在解答这个问题之前,我们先介绍一些背景知识:异或运算
简单来讲,就是相同为0,不同为1,如下所示
  1^1=0
  0^0=0
  1^0=1
  0^1=1
此外,异或运算符合两个性质:交换律和结合律,也就是说
a ^ b = b ^ a
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

小技巧: 寻找一个二进制数在最右侧的1,不太好描述,你可能听起来有点懵,看下面例子就明白了。
c = 0110100
从右往左看,c在倒数第三位上的数字为1,没错,我们就是要找这个数0000100,怎么做到呢?记住下面的这个公式:
r e s u l t = c & ( ! c + 1 ) ) result = c \&(!c + 1)) result=c&(!c+1))
!c = 1001011
!c + 1 = 1001100
c&(!c+1) = 0000100
记住这个算式即可

二、问题分析

看过问题一的小伙伴都知道了异或运算的原理,在这里就不多说了。
我们还是将这个序列的所有数都异或一下,假设这两个奇数是a和b,那么就得到了一个数a^b.
那么问题来了,如果知道a和b分别是多少呢?
根据题干我们知道,a和b肯定不相等,因此,a^b != 0,既然它不等于0,那么在二进制上肯定有一位为1,我们根据上面提到的公式提取得到最右侧为1的那个数c。

此外,a^b != 0,且最右侧有一位为1,只有0和1才能异或出来,那么我们可以断定,a和b在这一位上,肯定是一个为0,一个为1.

我们都知道,这个数c有一个特征,只有一位为1,其他位置都为0.

现在我们让c和数组arr中的所有元素进行相与,只有两个位置都为1,才能相与出来1。因此,这样我们可以把数组序列分为两类,一类是在这一位上为0的值,一类是在这一位上为1的值。

最后,我们让(a^b)得到的值分别与这两类的值进行异或,消除掉那些偶数的值,剩下的就是这两个数。

三、代码示例

/**
     * 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,寻找这两个数
     */
    public static void problem2(int[] arr){
        int res1 = 0;
        int res2 = 0;
        for(int value:arr){
            res1 ^= value;
        }
        //保留res1二进制下某一位为1的值(任何为1的位置都可以,这里选择最右边的1)
        int rightOne = res1 & (~res1 + 1);
        for(int value:arr){
            if((value & rightOne) != 0) {
                res2 ^= value;
            }
        }
        System.out.println(res2+" " +(res1^res2));
    }

尾注

留下笔记,只为方便回忆,如有侵权,还请联系,如有错误,恳请纠正。陆续更新,还请关注。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值