力扣剑指offer之数组中孤独的数字

关于数组中孤独的数字,是指在一个数组内,大部分数字都是成对出现,只有一个或者两个是孤单的。现在让你去找到这孤单的一个或者两个数字。

我们先解决简单的问题:假设一个数组内只出现一个孤单的数字,让你找出来?
例如:【1,2,1,2,3】 让你找出这个3来。

思路解析:两两成对出现,只有一个元素落单,那么我们很容易想到将数组排序,然后两两成对比较,如果这一对不相等,那么孤单的数字肯定出现在这一对中。我们可以找这一对前面的一个数字或者后面的一个数字与它俩分别看看能不能凑成一对,如果不能,则说明这个孤单的数字就是它。
这个思路很容易想到,但是需要先排序,然后还得遍历寻找,比较麻烦。异或操作这时就能凸显出其独特的优势,相同的数字之间异或就是0,0与任何数异或都是本身,那么我们将这个数组整体异或,那么两两相互抵消,最后剩下的一定是孤单的数。

public int singleNumber(int[] nums) {

        if(nums.length == 0){
            return 0;
        }
        int count = nums[0];
        for(int i = 1; i < nums.length; i++){
            count = count ^ nums[i];
        }

        return count;
    }

解决了上面简单题,现在开始解决稍难一点的题目,如果数组中有两个孤单数怎么办呢??因为我们已经会解一个孤单数的问题,那么现在如果我们可以将这一个数组内两个孤单数给分开,分到两个部分去,每一个部分仅含一个孤单数,那我们是不是就可以利用刚刚学习的异或操作分别对这两部分做异或,求出孤单数啊。因此问题难点就到了如何将两个孤单数分开到两个数组内。

这两个孤单数肯定不相等,相等就不叫孤单数了。。所以这两个孤单数异或其得到的二进制结果必至少存在一个1。现在我们假设两个孤单数异或结果的第一个1在二进制第k个位,那这就表示在第k位上这两个孤单数是不相同的,一个是1,一个是0,只有这样两个孤单数异或结果第k个位上才会出现1。

是不是有点小开心啊!!!这两个孤单数之间的区别我们已经找到了,接下来就是根据这个区别,将这两个孤单数区分开,我们就可以直接异或求解了。我们假设m=1,将m与两个孤单数的异或求与操作,如果为0,则将m左移一位。上面这个操作,我们就可以得到第k位到底是第几位了。因为1与两个孤单数异或结果的最后一位做与,如果为0,说明其孤单数异或结果最后一位为0,不是我们要找的区别位1。所以我们就将m左移一位,将1变成10,使得1作用与两个孤单数异或结果的第二位。就这样一直找,找到第一个1为止,这个位置就是我们上面所说的k的位置。此时m的值就是10000(假设k在第5位)。

现在我们遍历整个数组,将每一个元素和m做与操作,如果为0,分到一组,否则分到另一组。仔细想想为什么它可以分成两组,并且每一组仅仅含有一个孤单数??

因为对于成对出现的数,这两个数是相等的,那么与上10000肯定相等的,要是0都是0,要是1都是1,自然会分到一组。那么对于那两个孤单数呢??上面我们验证了,在第k位上,也就是第五位上,一个数是1,一个数是0,那么和10000与操作,两个孤单数中第5位是1的数,肯定是10000,第5位是0的那个孤单数与上10000,那肯定是0啊。所以就可以成功分开了!!!

激动吧???成功了。接下来你只需要将这两组数组挨个异或,得到的两个值就是两个孤单值。

class Solution {
    public int[] singleNumbers(int[] nums) {
        
        // 其实这道题的核心就是如何才能把这两个只出现一次的数字分开呢?因为在一个数组中只出现一个一次的数值,我们可以很容易的
        // 利用排序后两两异或的方法得到落单的值。

        //那么该如何将这两个孤单的值分到两个数组内呢?  我们将数组内所有值异或,得到的一定是这两个孤单的值异或数,这个数肯定存在某一位为1,因为
        // 如果不存在某一位为1,那么这个数就是0了,那这两个孤单的数不就相等了吗???所以肯定存在某一位为1.
        // 那么对于这两个孤单的数来说,这一位上肯定是不同值,一个1,一个0异或才会为1.因此,我们就有了区分这两个数的思路:
        // 我们找到这两个孤单数值中这一不同的位。用这两个数与上这个位值,那么一个是1,一个是0.不就区分开来吗?
        // 至于其它元素,两两成对,那与上一个定数,这一对的值肯定是一样的,肯定会分到一组啊。因此现在分成两组,每组内除了一个落单的,其它都是成对的。
        int m = 0;
        for(int i = 0; i < nums.length; i++){
            m = m ^ nums[i];
        }
        
        int a = 1;
        while((a & m) == 0){
            a <<= 1;
        }

        int b = 0;
        int c = 0;
        for(int i = 0; i < nums.length; i++){
            if((nums[i] & a) == 0){
                b = b ^ nums[i];
            }else{
                c = c ^ nums[i];
            }
        }

        return new int[]{b,c};
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值