【剑指offer】问题15:二进制中1的个数

给定一个十进制,统计其二进制表示中1的个数。

  • 解法一:
    public int NumberOf1(int n) {
        int flag = 1, count = 0;
        while(flag != 0) {
            if((n & flag) != 0) {
                count ++;
            }
            flag = flag << 1;
        }
        return count;
    }

使用一个辅助变量,初始值是1,也就是2的0次方,不断左移,并且与n进行与操作,这样就可以判断n从右往左各bit位上是否为1。进而得到问题的解。

  • 解法二:
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0) {
            count ++;
            n = n & (n - 1);
        }
        return count;
    }

这种解法应用了一种巧妙的思路:n与n-1进行与操作,会消除n二进制中最右边的一位1。我们可以简单的思考一下,假如n的二进制中最后一位是1,那么减去1之后,最后一位变成了0,其他位不变,满足上面的结论;如果最后一位是0,那么减去1后,就相当于从右往左数,第一位非0也就是1的位置开始,所有位按位取反,此时再与n做与操作,也是正好把从右往左的第一位1消除掉。结论得到证明。

  • 解法三:
    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;
    }

第三种解法就是jdk里面给出的大神级实现了(Integer类里的静态方法)。开始以为应该会用解法二的解法就ok了。但是这里给出了一种看似更为"复杂"的方法。让我们来看一下。
这种方式总体的思路有点类似于分治-合并的思路。两两一组统计组内1的个数(分治),然后量量一组合并求和,直到全部求和完毕,得到整个二进制串中1的个数。让我们来逐行看一下源码。我们用666这个十进制作为例子,debug一下这个方法的每一行。666对应的二进制为:

0000 0000 0000 0000 0000 0010 1001 1010

第一行:这一行应该是这个方法中最关键也是最难以理解的一行,两两一组,求组内1的个数。直接看的话,这个减操作还是很迷的。我们先来看一下每两位一组,原值与二进制中1的个数的真值表。(这个地方的思路,是百度了下jdk这个源码的讲解,看了某位大神的博客后,回来自己debug的。知识产权值得保护,下次再看到这的时候,补充下想法的来源。)

原值 1的个数(二进制) 操作过程
00 00 00 - 00 = 00
01 01 01 - 00 = 01
10 01 10 - 01 = 01
11 10 11 - 01 = 10

由于是每两位一组,因此真值表中原值只有4个值。对应的1的个数在第二列。下面就是见证奇迹的时刻了。我们发现,第二列的值,是第一列的值作为被减数,第一列右移一位的值作为减数求差的结果,也就是 i - i>>>1。有了这个公式,再来看第一行的操作,就比较好理解了。0x55555555对应的二进制为 0101 0101 0101 0101。i右移一位,再与一下0x55555555的话,就相当于是右移之后,再把组内的 第一个数字给消除掉,即构造我们上面公式的减数。举个例子,如果原数是1111,右移一位的话是0111,分成两组为(01)(11),与一下0x55555555,得到我们期望的减数(01)(01)。至此,减数被减数都有了,第一行执行完毕,得到的二进制串就是每两位中1的个数。ps:这一步一共分成了16组(32/2)。

step1:右移一位
0000 0000 0000 0000 0000 0001 0100 1101
step2:与一下0x55555555
0000 0000 0000 0000 0000 0001 0100 0101
step3:用被减数(666)减一下上面的结果
0000 0000 0000 0000 0000 0001 0101 0101

第二行:从这一行开始,就是求和了。第二行是相邻两组合并。如果我们给分组编号,1-16组,那么+号前面的是保留组号2、4、6、8...16的分组。+号后面的操作是将1、3、5、7...15的分组,首先移动到2、4、6、8...16组的位置上,然后将其他位置清0,这样就跟+号左边的对齐了,然后做加法。

step1: 保留2、4、6...
0000 0000 0000 0000 0000 0001 0001 0001
step2:右移两位,保留2、4、6、8..
0000 0000 0000 0000 0000 0000 0001 0001
step3:求和
0000 0000 0000 0000 0000 0001 0010 0010

第三行:每四个一组求和。

step1: i + i>>>4
0000 0000 0000 0000 0000 0001 0011 0100
step2: &0x0f0f0f0f
0000 0000 0000 0000 0000 0001 0000 0100

第四行:每8个一组求和。

0000 0000 0000 0000 0000 0000 0000 0101

第五行:每16个一组求和

0000 0000 0000 0000 0000 0000 0000 0101

第六行: 获取最后结果。因为int是4字节,32bit,最多也就32个1,所以最终结果最大值为32(2^5),即为 0010 0000,所以取6位即可,0x3f就是这么来的。

00 0101(二进制) = 5(十进制) 
  • 总结
    该方法总共5次无符号位移操作,5次与操作,4次加法操作,一次减法操作,总共15次计算。解法二的话,最坏场景的操作数是: 32*(1次加法+ 一次减法 + 一次与操作) = 96次操作。虽说都是常数级的操作,但是应该jdk的实现复杂效率更高一些。后面考虑怎么证明一下该方法的效率比方法二高。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 题目描述: 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 分析: 先复习几个知识点: 补码: ...
    夏臻Rock阅读 1,270评论 0 1
  • 网站乱码问题我们会经常碰到,大多见于非英文的中文字符或其他字符乱码,而且,这类问题常常是因为编码方式问题,主要原因...
    波段顶底阅读 2,819评论 1 9
  • 1.题目 输入一个整数,输出该数二进制表示中1的个数。 2.必备知识-原码、反码与补码 2.1 原码 将最高位作为...
    Codeapes阅读 305评论 0 0
  • Chapter1: 位运算的奇技淫巧 3. 题解:计算无符号二进制数中1的个数 题目 计算无符号整数的二进制表示中...
    Aurochsy阅读 934评论 0 1
  • 今又重阳,岁岁重阳 作者:吴华锦 来日又方长,今朝何须忙。 疏朗伸坚壁,惺忪看新阳。 屋下葡萄园,青藤着瓦墙。 红...
    华锦阅读 430评论 0 0