【经典专题】数组中出现1次/2次的数字——垂直方向的位运算

问题引入

有一个数组:nums = [2, 2, 3, 3, 6, 6] 。

它具有怎样的性质呢?所有元素异或和为0。

这个位运算好像很简单?别着急,接下来,我们将要把位运算发挥到极致。

 
 

情境壹——num出现一次,其余元素出现两次

一个数组nums里除某个数字之外,其他数字都出现了两次。找出这个数字。

不假思索,一气呵成。

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        for (int num : nums) {
            res ^= num;
        }
        return res;
    }
}

 
 

情境贰——num1出现一次,num2出现一次,其余元素出现两次

一个数组nums里除两个数字之外,其他数字都出现了两次。找出这两个数字。

此时,我们有些蒙了。因为数组中存在两个只出现一次的数字,所以直接求异或和,一定会得到一个没有意义的结果。

是不是求异或和的思路不能用了呢?

如果可以将原数组拆分为两个子数组,让这两个出现一次的数字分别分到两个数组中,那么是不是就能在两个子数组中分别求异或和、分别得到num1和num2了呢?

问题又来了,如何拆分数组,才能将num1和num2恰好分到两个子数组中?我们需要一个特征,根据这个特征,可以将原本相等的数字分到同一个数组中,将num1和num2分到不同的数组中。

为了寻求这个特征,我们回到最初的尝试结果,即所有元素的异或和sum。所有元素的异或和其实等价于num1 ^ num2,思考一下,如果在某一位上这个异或值为1说明什么?说明num1和num2在该位上不相等,即一定是一个为0一个为1;而对于相等的其他数字,在该位上自然也是相等的——这不正是我们所寻求的数组拆分特征吗?

根据这个特征,数组拆分为两个子数组。在两个子数组中分别求异或和,也就分别得到了num1和num2。一切迎刃而解!

看着图解理解,非常清晰:
在这里插入图片描述

理解了算法的过程,代码自然也不在话下:

class Solution {
    public int[] singleNumber(int[] nums) {
        // 求所有数字异或和
        int sum = 0;
        for (int num : nums) {
            sum ^= num;
        }
        // 找异或和第一个为1的位
        int mask = 1;
        while ((sum & mask) == 0) {
            mask <<= 1;
        }
        // 以该位为依据分组异或
        int x = 0;
        int y = 0;
        for (int num : nums) {
            if ((num & mask) == 0) {
                x ^= num;
            } else {
                y ^= num;
            }
        }
        return new int[]{x, y};
    }
}

 
 

情境叁——num出现一次,其余元素出现三次

一个数组nums里除某个数字之外,其他数字都出现了三次。找出这个数字。

此时,我们又有些蒙了。出现三次的话,使用异或运算是无法消掉的呀!不要着急,跟着下面的思路来:

1)首先,将所有数字都看成 32位 的二进制数;

2)接着,将数组中的所有数字相加。那么,对于"某一位"来说,数值一定是 3N或3N+1

3)为什么?所有出现3次的数字对该位置的贡献,和一定是0或3,出现一次的数字对该位置的贡献,一定是0或1

4)所以,对该位置的和,用3取余,结果就是出现一次的数字在该位置的值。

这种解法是具有通用性的,即出现4次啊、5次啊、N次啊,直接用4、5、N取余即可。

看着图解理解,非常清晰:

在这里插入图片描述

理解了算法的过程,代码自然也不在话下:

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        for (int i = 0; i < 32; i++) {
            // 对于int每一位
            int bit = 0;
            // 计算该位上的和
            for (int num : nums) {
                bit += ((num >> i) & 1);
            }
            // 对3取余即为res在该位的值
            res += ((bit % 3) << i);
        }
        return res;
    }
}

 
 

感悟总结

当没有头绪的时候,不妨从垂直的角度,以位为单位,一位一位的去分析问题,或许会有意想不到的效果>_< !

 
 
 
 
 
 
 
 
 
 
 
 

E N D END END

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值