【剑指offer】数组中出现1次的数字+数组中出现2次的数字

🔥题目

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

输入:nums = [1, 2, 10, 4, 1, 4, 3, 3]
输出:[2,10] 或 [10,2]

☘️解析

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

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

如果可以将原数组拆分为两个子数组,让这两个出现一次的数字分别分到两个数组中,那么是不是就能在两个子数组中分别求异或和、分别得到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};
    }
}
🌸补充

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

 
 
 
 
 

🔥题目

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

输入:nums = [9,1,7,9,7,9,7]
输出:1

☘️解析

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

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;
    }
}
🌸补充

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值