位运算(出现一次数字专题)

位运算(出现一次数字专题)

1、136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXwOuUYU-1656778338157)(C:\Users\zwz\AppData\Roaming\Typora\typora-user-images\image-20220702222819475.png)]

**解法一:**数组加 hashMap 计数(时间复杂度:O(n) 空间复杂度:O(n) )

**解题思路:**第一遍 for 循环用来统计每个数字出现的个数,第二遍 for 循环直接找到数量为 1 的数字输出即可

代码:

class Solution {
    public int findNumberAppearingOnce(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap();
        for(int x : nums){
            map.put(x,map.getOrDefault(x,0) + 1);
        }
     	int res = nums[0];
        for(int x : nums){
            if(map.get(x) == 1){
                res = x;
            }
        }
        return res;
    }
}

**解法二:**位运算(时间复杂度:O(n) 空间复杂度:O(1) )

**解题思路:**此解法为符合题意的解法,因为题意要求我们尽可能不使用额外的空间来实现,位运算 ^ :相同为 0 不同为 1 ,因为数组中只有一个数字出现了 1 次,其余数字均出现了两次,所以我们将数组中的所有元素异或 ^ 一遍后,出现两次的元素会变成 0 ,最终剩下的数字便是我们想要得到的结果。

代码:

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

2、137. 只出现一次的数字 II

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

**进阶:**你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKkmzHXg-1656778338163)(C:\Users\zwz\AppData\Roaming\Typora\typora-user-images\image-20220702224541322.png)]

**解法一:**数组加 hashMap 计数(时间复杂度:O(n) 空间复杂度:O(n) )

**解题思路:**第一遍 for 循环用来统计每个数字出现的个数,第二遍 for 循环直接找到数量为 1 的数字输出即可

代码:

class Solution {
    public int findNumberAppearingOnce(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap();
        for(int x : nums){
            map.put(x,map.getOrDefault(x,0) + 1);
        }
     	int res = nums[0];
        for(int x : nums){
            if(map.get(x) == 1){
                res = x;
            }
        }
        return res;
    }
}

**解法二:**位运算(时间复杂度:O(32n) 空间复杂度:O(1) )

**解题思路:**此解法为半符合题意的解法,为什么说是半符合,因为题意要求我们算法具有线性的时间复杂度,虽然 O(32n) 看似可以化简 O(n) ,但是根据这个题的数据范围,因为 n 一般在百万级别,log n < 32,所以 32n > n log n 不是严格的 O(n) , 其实O(32n)是要比O(n log n)高的,所以并不能算为严格的O(n)算法,但是比起解法一,我们并没有使用额外的空间,所以还是非常好的一种解法。

​ 具体的解题想法:我们需要统计数组中所有数字每一位 0 和 1 的个数,因为除了我们需要求的数字出现一次之外,其他所有的数字都出现了三次,所以在 32 中的每一位都有 1 或者 0 的个数模 3 等于 1 ,而如果 1 的个数模 3 等于 1 ,那么说明我们要找的那个数字在哪一位的二进制为 1 ,反之则为 0 ,就这样经过 32 次循环我们便能把所有位置是 0 或者 1 求出来,然后转换为 10 进制便是我们要求的答案。

代码:

class Solution {
    public int findNumberAppearingOnce(int[] nums) {
        int ans = 0;
        for (int i = 0; i < 32; ++ i ) {
            int cnt = 0;
            for (auto x : nums) {
                if (x >> i & 1) cnt ++; 
            }
            if (cnt % 3) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

**解法三:**位运算(时间复杂度:O(n) 空间复杂度:O(1) )

**解题思路:**此解法为符合题意的解法

​ 具体的解题想法:与解法二相比,解题思想都是一样的,但是用到了一个叫做状态机的知识,现在看不懂没关系,下面有图片解释状态机的转移过程。还有就是利用两个数字在做位运算的过程当中,其中每一位的 0 1 运算都是相互独立的,这个机制加上状态机就可以帮助我们省略掉外面一层 32 次的for循环。个人感觉这个解法可以让面试官眼前一亮,如果真的被问到。

image-20220702232204628

代码:

class Solution {
    public int findNumberAppearingOnce(int[] nums) {
        int ones = 0;
        int twos = 0;
        for(int x : nums){
            ones = (ones ^ x) & ~twos;
            twos = (twos ^ x) & ~ones;
        }
        return ones;
    }
}

3、260. 只出现一次的数字 III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

**进阶:**你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtNUlPdL-1656778338164)(C:\Users\zwz\AppData\Roaming\Typora\typora-user-images\image-20220702232653815.png)]

**解法一:**数组加 hashMap 计数(时间复杂度:O(n) 空间复杂度:O(n) )

**解题思路:**第一遍 for 循环用来统计每个数字出现的个数,第二遍 for 循环直接找到两个数量为 1 的数字返回数组输出即可。

代码:

class Solution {
    public int[] findNumsAppearOnce(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap();
        for(int x : nums){
            map.put(x,map.getOrDefault(x,0) + 1);
        }
        List<Integer> list = new ArrayList();
        for(int x : nums){
            if(map.get(x) == 1){
                list.add(x);
                if(list.size() == 2){
                    break;
                }
            }
        }
        int res[] = new int[]{list.get(0),list.get(1)};
        return res;
    }
}

**解法二:**位运算(时间复杂度:O(n) 空间复杂度:O(1) )

**解题思路:**此解法为符合题意的解法,该题目与题目:136. 只出现一次的数字非常相似,所以刚开始肯定考虑能否直接通过异或 ^ 得到答案,但是因为该题目有两个个数为 1 的数字,所以经过异或完之后的结果为 x^y ,也就是我们只知道最终两个数字的异或结果,但是并不知道这两个数字确切是几,所以下一步我们应该想办法把两个数字分出来。

​ 怎么将两个数字分开?我们可以知道,如果两个数字相同,异或结果为 0 ,是因为两个数字每一位的二进制都是一样的,所以异或结果为 0 ,那么两个数字不一样,肯定两个数字的 32 位上存在一个为 0 ,一个为 1 的情况,这样异或结束后的结果中,这一位也必然为 1 ,所以我们可以先通过 x^y 的结果不断右移与 1 相 & ,通过判断结果是否为 1 找出 x^y 二进制位为 1 的哪一位,然后将数组中所有的数字右移同样的次数,随后进行异或,出现两次的数字仍然能异或变成 0,而最后剩下的那个数字 a 就必然是我们要求的两个数字之一,然后通过 x^y 再异或上 上一步求出来的数字 a 便是另一个我们要求的数字,因为返回不需要顺序,所以返回即可。大家可以结合代码看。

代码:

`class Solution {
    public int[] findNumsAppearOnce(int[] nums) {
        //假设两个唯一的数为x和y
        //先把所有的数异或一遍,可以得到x ^ y
        int xy = 0;
        for(int num : nums){
            xy ^= num;
        }
        //再找到x ^ y中某一位为1,则x和y在这一位上一定不同(一个是1一个是0)
        int k = 0;
        while((xy >> k & 1) == 0) k++;
        //通过此位将原来所有的数分成两类:此位是1和此位是0(此时x和y各属于一类)
        //在每一类中找出唯一的那个数
        int x = 0;
        for(int num : nums)
            if((num >> k & 1) == 1) 
                x ^= num;

        int y = x ^ xy;
        int[] res = new int[]{x, y};
        return res;
    }
}

作者:莫凡
链接:https://www.acwing.com/solution/content/14140/
来源:AcWing

此处引用了 acwing 莫凡大佬的代码,因为注释写的特别棒,供大家参考

总结

​ 这三道题全是需要使用位运算进行解决的题目,题目一算是裸题,仅仅利用异或 ^ 的特性便可以计算,第二题 则是利用 32 位 int 类型数字每一位个数的思路进行求解,第三题也是具体到了某一位。其中第二题状态机的做法让人耳目一新,但是却不好在其他题目上运用,所以只能是这道题尽量背过。

​ 三道题都让人学到很多的东西,只能说不断的学习,不断的进步,除此之外,二进位枚举也用到了位运算,也算是比较常用的暴力操作,都很帅,如果有什么问题,欢迎交流。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值