剑指Offer:面试题15——二进制中1的个数(java)

二进制中1的个数

1. 题目描述

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

2. 前提知识补充

2. 1 位运算规律

在这里插入图片描述

2.2 左移右移运算

  • 左移运算 m << n 就是将原数据 m 的左边去掉n位,在右边补上n个零即可

    • 0110010 << 2 = 1001000
    • 10001010 << 3 = 01010000
  • 右移运算 m >> n 就是将原数据m右边丢去n位,但是在补位的时候情况就比较复杂一点,因为涉及到有符号数和无符号数!

    • 无符号数:右边丢n位,左边补n个0

      • 00001010 >> 2 = 00000010
    • 有符号数:如果是正数就用0补位,如果是负数就用1补位

      • 00001010 >> 2 = 00000010
      • 10001010 >> 2 = 11100010

2.3 位运算的实质

  • 这里我们以16位例:10000
  • 16右移1位时: 16 >> 1 = 01000 = 8 ,即右移一位相当于除以了2
  • 16右移2位时: 16 >> 2= 00100 = 4 ,即右移一位相当于除以了2的平方
  • 16左移1位时: 16 << 1 = 100000 = 32 ,即左移一位相当于乘以了2

3. 算法思路一

3.1 除法

  • 针对于十进制数,我们想要的肯定是转二进制,这样才可以清晰的判断有多少个1,但是实际上我们如果全部转为二进制再最后来看的话显然就显得复杂了,所以应该在我们转二进制的过程中就可以进行判断。

  • 这里我们先使用除法的方式将十进制数转二进制,以30为例:

    • 30 % 2 = 0 , 0为二进制的最后一位, 再将 30 / 2 = 15
    • 15 % 2 = 1 , 1为二进制的倒数第二位 ,再将15 / 2 = 7
    • 7 % 2 = 1,
    • 3 % 2 = 1,
    • 1 % 2 = 1
    • 最终30的二进制为 11110 (也就是从下往上写即可)
  • 至此我们就明白了,只要每次判断模2的余数是1还是0即可

  • 该算法的思路就是每次求出二进制的末尾位,然后进行判断

  • 但是此算法是有漏洞的!后面会讲到

/**
     * 除法
     * @param x
     * @return
     */
    public static int countOne(int x){
        int count = 0;
        while(x != 0){
            if(x % 2 == 1){
                count++;
            }
            x = x / 2;
        }
        return  count;
    }

3.2 位运算

  • 其实在很多架构底层的运算中,一般不使用除法,因为位运算的效率明显高于除法!所以对于上面的写法,我们可以进行改进,那就是使用位运算。
  • 上面也提到了,除以2其实就是右移一位!
  • 其次,在判断末尾位是否为1的时候,可以直接将数字与 1 做位与运算
/**
     * 位运算  输入为负数是会有死循环
     * @param x
     * @return
     */
    public static int countOne1(int x){
        int count = 0;
        while(x != 0){
            if((x & 1) == 1){
                count++;
            }
            x = x >> 1;
        }

        return count;
    }

3.3 算法漏洞

  • 这里就直接放书上的原话,指的就是输入如果是负数的时候就会陷入死循环
    ,因为x永远不可能为0
    在这里插入图片描述

4.算法思路二

  • 刚刚我们每次将 x 与 1 做与运算来判断最后一位二进制尾数是否为1,然后不断的对x做右移运算,正式因为对x做了右移运算所以才会导致负数的情况。那我们就避免对x做右移运算,转而对 1 做运算
  • 我们将 x 与 1做与运算判断尾数是否为1,之后将 1做左移运算变成2 ,再将 x 与 2做与运算,这样可以判断 次末尾数是否为1,再将 1 变成 4,判断倒数第三位的情况。
  • 也就是不断的将1 变成2 ,4 ,8 去判断倒数第一位,倒数第二位的情况
  • 这个循环次数等于二进制数的位数,实际上后面还有最优的解法
/**
     * 位运算  
     * 改进 但是循环32位
     * @param x
     * @return
     */
    public static int countOne2(int x){
        int count = 0;
        int flag = 1;
        while(flag != 0){
            if((x & flag) != 0){  //不等于0  因为 9&8 结果为8
                count++;
            }
            flag = flag << 1;
        }

        return count;
    }

5. 算法思路三(最优解法)

  • 这种思路其实就是为了不循环那么多次,而是有多少个1循环多少次
    在这里插入图片描述
 /**
     * 最优解法
     * @param x
     * @return
     */
    public static int countOne3(int x){
        int count = 0;
        while(x != 0){
            count++;
            x = x & x - 1;
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值