二进制中1的个数

题目描述:

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

分析:

先复习几个知识点:

  • 补码: 在计算机中所有的整数都是以二进制形式存储的,其中正整数用原码表示,负数用其正值的补码表示。
    补码的计算机方式:对该负数的正值的原码,进行取反后加一,得到补码。
    例如-6在计算机中的补码表示如下:(以32位计算机为例)

-6 的正值 6 的二进制:
00000000 00000000 00000000 00000110
取反,得反码:
11111111 11111111 11111111 11111001
+1 ,得补码,即 -6 在计算机中的二进制表示:
11111111 11111111 11111111 11111010

  • 补码的性质:在计算机中,加法器实现最为简单,很多运算最终都要转化为加法运算,而,减去一个数相当于加上这个数的补码。(非常巧妙的设计,让人佩服冯诺依曼的天才创意!关于补码的详细介绍
  • 负数的右移:因为负数在内存中是以补码形式存在的,所有首先根据负数的原码求出负数的补码(符号位不变,其余位按照原码取反加1),然后保证符号位不变,其余位向右移动到X位,在移动的过程中,高位补1.等移位完成以后,然后保持符号位不变,其余按位取反加1,得到移位后所对应数的原码。即为所求。详见

  • 正数的左移与右移、负数的无符号右移,就是相应的补码移位所得,在高位补0即可。
    负数的右移,就是补码高位补1,然后按位取反加1。

  • 左移、右移:
    在不溢出的情况下
    左移n位后的值 等于原值乘以2的n次方
    例如 4 <<2 就是16,二进制就是 00000100 <<00010000
    -4<<2 就是-16 二进制就是 11111100 <<11110000
    右移n位后的值 等于原值除以2的n次方的商
    例如 4 >>2 就是1,二进制就是 00000100 >>00000001
    -4>>2 就是-1 二进制就是 11111100 <<11111111

代码:

  1. 将二进制转化为数组,然后一位一位进行判断(重点:熟悉java中相应的API)
public class Solution {
    public int NumberOf1(int n) {
        int result =0;
        char[] array = Integer.toBinaryString(n).toCharArray();
        for(char c:array){
            if(c=='1'){
                result ++;
            }
        }
        return result;
    }
}
  1. 改进,既然熟悉java的API,就会发现JAVA 的 JDK 库里 Integer 有个 bitCount 方法,直接调用即可:
public class Solution {
    public int NumberOf1(int n) {
       return Integer.bitCount(n);
    }
}

查看Integer.bitCount()的源码如下:

/** 
     * Returns the number of one-bits in the two's complement binary 
     * representation of the specified {@code int} value.  This function is 
     * sometimes referred to as the <i>population count</i>. 
     * 
     * @return the number of one-bits in the two's complement binary 
     *     representation of the specified {@code int} value. 
     * @since 1.5 
     */  
    public static int bitCount(int i) {  
        // HD, Figure 5-2  
        i = i - ((i >>> 1) & 0x55555555);  
        i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);  
        i = (i + (i >>> 4)) & 0x0f0f0f0f;  
        i = i + (i >>> 8);  
        i = i + (i >>> 16);  
        return i & 0x3f;  
    }  

二分法,两两一组相加,之后四个四个一组相加,接着八个八个,最后就得到各位之和了。
第一行是计算每两位中的 1 的个数 , 并且用该对应的两位来存储这个个数 , 如 : 01101100 -> 01011000 , 即先把前者每两位分段 01 10 11 00 , 分别有 1 1 2 0 个 1, 用两位二进制数表示为 01 01 10 00, 合起来为 01011000.
第二行是计算每四位中的 1 的个数 , 并且用该对应的四位来存储这个个数 . 如 : 01101100 经过第一行计算后得 01011000 , 然后把 01011000 每四位分段成 0101 1000 , 段内移位相加 : 前段 01+01 =10 , 后段 10+00=10, 分别用四位二进制数表示为 0010 0010, 合起来为 00100010 .
下面的各行以此类推 , 分别计算每 8 位 ,16 位 ,32 位中的 1 的个数 .
将 0x55555555, 0x33333333, 0x0f0f0f0f 写成二进制数的形式就容易明白了 .

3.根据上面的源码,可以类似的使用二分法写出 HAKMEM 算法的解法

public class Solution {
    public int NumberOf1(int n) {
       int num;     
       num = (n >> 1) & 033333333333;     
       n = n - num;    
       num = (num >> 1) & 033333333333;    
       n = n - num;     
       n = (n + (n >> 3)) & 030707070707;    
       n = n%63;   
       return n ;   
    }
}

这个通过率只有40%,应该是哪里有问题。

  1. 通过位移判断奇偶数并计数,标志位初始为1,将其和输入值相与,n & 1 的结果为 1 或 0 ,为 1 的时候 count+=1 ,为 0 的时候 count+=0
public class Solution {
    public int NumberOf1(int n) {
      int count = 0;
        int flag = 1;
        while(flag!=0){
            if((n&flag)!=0){
                count++;
            }
            flag = flag<<1;
        }
        return count;
    }
}
  1. 正整数的二进制数最高位为 0 ,负整数和二进制数最高们为 1 ,则可利用左移、判断正负来实现 1 的个数的计算。
public class Solution {
    public int NumberOf1(int n) {
      int count = 0;
       while(n!=0){
           if(n<0){
               count++;}
           n=n<<1;//左移一位,左边的最高位为符号位,根据正负数来判断符号位的0,1,从而得到1的个数
       }
        return count;
    }
}

这种方法最容易理解.

  1. 思想: x & (x-1) 可以消去 x 二进制数的最后一位 1

n : 10110100
n-1 : 10110011
n&(n-1) : 10110000

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

把一个整数减去1,再和原整数做运算,会把该整数最右边一个1变成0,那么一个整数的二进制表示有多少个1,就可以进行多少次这样的操作.
O(logM) 时间复杂度解法,其中 M 表示 1 的个数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容