LeetCode算法题7:位运算


前言

      Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2

      简单总结一下位运算相关的算法题:

      补码:正数的补码等于其原码;负数的补码等于:先得到其绝对值所表示的原码,然后将符号位置为 1,再对其余位取反(0-1,1-0),然后再加 1。
      四位二进制的补码表示:

01234567
00000001001000110100010101100111
-7-6-5-4-3-2-1-0
10011010101111001101111011111000

      左移:<<,向左移动一位,右边补 0,相当于乘二。
      右移:>>,向右移动一位,左边补上符号位(正数补 0,负数补 1),相当于除二。

      无符号右移:>>>,向右移动一位,左边补 0。

一、2 的幂

      题目链接:https://leetcode-cn.com/problems/power-of-two/

      题目描述:
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

      几种算法参考如下:

    //在一些语言中,位运算的优先级较低,需要注意运算顺序。
    public boolean isPowerOfTwo(int n) {
        /*
        if(n<=0)
            return false;
        //如果一个数是2的幂,那么其二进制表示中只能有一个 1
        int num1=0;
        for(int i=0;i<32;i++){//n 每次向右移,判断它的每一位是否为 1
            if((n&1)==1)
                num1++;
            n>>=1;   
        }
        if(num1==1)
            return true;
        return false;
        */

		/*
        if(n<=0)
            return false;
        int num1=0;
        for(int i=0;i<32;i++){//令 1 每次向左移动, 判断其是否和 n 相等。
            if((n&(1<<i))!=0)
                num1++;
        }
        if(num1==1)
            return true;
        return false;
        */

        //return n>0&& (n&(n-1))==0;

        //return n>0 && (n&(-n))==n;//需要理解负数的补码表示方法

        int BIG = 1 << 30;//这是int能够表示的最大的2的次方数了,即2 的30次方,判断是否为BIG的约数

        return n > 0 && BIG % n == 0;
    }  

二、位1的个数

      题目链接:https://leetcode-cn.com/problems/number-of-1-bits/

      题目描述:
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

      1,依次对每一位判断的算法参考如下:

    public int hammingWeight(int n) {
        //常规思路: n右移,或者将1左移。
        /*
        int num1=0,m=32;
        while(m--!=0){
            if((n&1)==1)
                num1++;
            n>>=1;
        }
        return num1; */
    }

      2,运算:n & (n−1),其运算结果恰为把 n 的二进制表示中的最低位的 1 变为 0 之后的结果。6 & 5=(0110)&(0101)=0100,这样就将 6 的二进制中最后一个 1 变为 0 了。
      这样我们可以利用这个位运算的性质加速我们的检查过程,在实际代码中,我们不断让当前的 n 与 n−1 做与运算,直到 n 变为 0 即可。因为每次运算会使得 n 的最低位的 1 被翻转,因此运算次数就等于 n 的二进制位中 1 的个数。
      算法参考如下:

	public int hammingWeight(int n) {
        int num1=0;
        while(n!=0){
            n=(n&(n-1));
            num1++;
        }
        return num1;
    }

三、颠倒二进制位

      题目链接:https://leetcode-cn.com/problems/reverse-bits/

      题目描述:
颠倒给定的 32 位无符号整数的二进制位。(将二进制位改为逆序:如 1101 变为 1011)

      常规思路,对每一位依次判断,参考算法如下:

    public int reverseBits(int n) {
        int ans=0,num=31,tmp;
        while(num--!=0){
            tmp=(n&1);
            ans=(ans|tmp);
            n>>=1;
            ans<<=1;   // 多左移了一次。。。。。
        }
        
        tmp=((n>>31)&1);
        ans=(ans|tmp);
        return ans; 
    }

四、只出现一次的数字

      题目链接:https://leetcode-cn.com/problems/single-number/

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

说明:

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

      采用异或运算⊕。异或运算有以下三个性质。1,任何数和 0 做异或运算,结果仍然是原来的数。(任何数和全1做异或,会将原来数中的1变为0,0变为1)2,任何数和其自身做异或运算,结果是 0。3,异或运算满足交换律和结合律。

      参考算法如下:

    public int singleNumber(int[] nums) {
        int ans=0;
        for(int n:nums)
            ans^=n;
        return ans;
    }

      熟悉一下集合操作(思路来源于官方题解):
      1,使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。采用 HashSet,参考算法如下:

	public int singleNumber(int[] nums) {
        Set<Integer> set=new HashSet<>();
        for(int i:nums){
            if(!set.add(i))
                set.remove(i);
        }
        int ans=0;
        for(int i:set)
            ans=i;
        return ans;
    }

      或者使用 contains 方法:

	public int singleNumber(int[] nums) {
        Set<Integer> set=new HashSet<>();
        for(int i:nums){
            if(set.contains(i))
                set.remove(i);
            else
                set.add(i);
        }
        int ans=0;
        for(int i:set)
            ans=i;
        return ans;
    }

      2,使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。采用 HashMap,参考算法如下:

	public int singleNumber(int[] nums) {
        Map<Integer,Integer> map=new HashMap<>();
        for(int i:nums){
            if(map.containsKey(i))
                map.put(i,map.get(i)+1);
            else
                map.put(i,1);
        }
        int ans=0;
        for(int i:map.keySet())
            if(map.get(i)==1)
                ans=i;
        return ans;
    }

      3,计算数组中的所有元素之和;然后依次将元素存储到集合中,如果发生重复,将这些发生重复的元素加起来,再乘以 2 即为原数组中重复元素之和;然后二者相减可得结果。参考算法如下:

	public int singleNumber(int[] nums) {
        Set<Integer> set=new HashSet<>();
        int num=0,k=0;
        for(int i:nums){
            num+=i;
            if(!set.add(i))
                k+=i;
        }
        return num-2*k;
    }

总结

      完

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值