剑指Offer #11 二进制中1的个数(想不到的骚操作)

题目来源:牛客网-剑指Offer专题
题目地址:二进制中1的个数

题目描述

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

题目解析

对于这种涉及位运算的题目,我们首先要了解基本的位运算,我们可以看这篇文章:C++描述的位运算总结

(PS:本文的图例用的是8位的整数,int有32位,画出来也不美观呀)

下面先给说一种因为存在负数而错误的做法:

  • 先通过 ‘&1’ 操作来判断 n n n 的最低位否为1,若为1,则计数器 c n t cnt cnt 的值加1;
  • 然后利用 ‘>>1’ 操作将 n n n 右移一位;
  • 最后重复上述两步操作,直到 n n n 的值为0,得出 c n t cnt cnt 的值即为该数二进制表示中1的个数。

这种方法在 n n n 为正数的时候是没有问题的,因为正数进行右移操作的时,最低位舍弃,最高位补0。过程如图所示:
1
但是负数进行右移操作的时,最低位舍弃,最高位的是1(过程如下图),这样经过多次以后,所有数位都会变成1,进而产生死循环。
2
下面给大家一共四种不同的正确解法:

方法一:
利用Integer内置的toBinaryString()方法将 n n n 转换为二进制字符串,然后二进制串中的0全部消掉,最后求得的字符串长度就是该数二进制表示中1的个数。

public class Solution {
    public int NumberOf1(int n) {
        return Integer.toBinaryString(n).replaceAll("0", "").length();
    }
}

方法二:
这是对上面讲到的错误方法的改进,由于左移操作是最高位舍弃,最低位补0,我们选择让 n n n 不变, 对 ‘&1’ 中的1进行左移,直到移动31位为止
3
由上图的两种情况可以看出,当1移动了 i i i 位之后,若 n n n 的第 i i i 位为1,进行&运算的结果为 ( 1 < < i ) (1 << i) (1<<i),否则为0。于是,我们就可以利用他们为判断条件来计算1的个数啦~

public class Solution {
    public int NumberOf1(int n) {
        int cnt = 0;
        for(int i = 0; i < 32; i++) {
        	//if ((n & (1 << i)) == (1 << i))也是可行的
            if ((n & (1 << i)) != 0)
                cnt++;
        }
        return cnt;
    }
}

方法三:
这种方法是很久之前zls师兄告诉我的,没想到今天才派上用场(我记忆力真好 )。

理论: n n n n − 1 n-1 n1 进行 & \& & 运算可以把 n n n 的二进制表示的右边第一个1变成0

根据上面给的理论,我们只要知道 n n n 可以进行多少次这样的操作,就可以得到 n n n 的二进制表示中1的个数,下图以n=29为例展示这个过程:
4
如果对上诉过程感受不深,可以手动计算多几组数据,虽然这种方法感觉起来比较笨,但却是理解的好办法。能用上这种方法,面试应该没问题了。

public class Solution {
    public int NumberOf1(int n) {
        int cnt = 0;
        while (n != 0) {
            n = n & (n - 1);
            cnt++;
        }
        return cnt;
    }
}

方法四:
这是在牛客网评论区看到做法,由Holiday_12138发表,但是ta并没有给出具体思路,我也参悟不出来,我打算以后理解了再更新这部分,也欢迎各位大佬提供解释。(做法真的太骚了

public class Solution {
	public int  NumberOf1(int n) {
	     int temp = n;
		 temp = (temp & 0x55555555) + ((temp & 0xaaaaaaaa) >>> 1);
		 temp = (temp & 0x33333333) + ((temp & 0xcccccccc) >>> 2);
		 temp = (temp & 0x0f0f0f0f) + ((temp & 0xf0f0f0f0) >>> 4);
		 temp = (temp & 0x00ff00ff) + ((temp & 0xff00ff00) >>> 8);
		 temp = (temp & 0x0000ffff) + ((temp & 0xffff0000) >>> 16);
		 return temp;
     }
}

如果本文对你有所帮助,要记得点赞哦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值