一次搞透,面试中的数1问题的五种方法!

面试中,除了TopK,是否被问过:求一个正整数的二进制表示包含多少个1?
画外音:姊妹篇《一次搞透,面试中的TopK问题!》。
 
例如:
uint32_t i=58585858;
i的二进制表示是:
0000 0011 0111 1101 1111 0011 0000 0010
于是,i的二进制表示包含15个1。
 
到底有几种方法,这些思路里蕴含的优化思路究竟是怎么样的,今天和大家聊一聊。
 
一、位移法。
思路:既然输入n是uint32,每次取n的最低位,判断是不是1,位移32次,循环判断即可。
 
伪代码

do{

    if ((n&1)==1){

       result++;

    }

    n>>= 1;

    i++;

} while(i

 
分析:不管n的二进制表示里包含多少个1,都需要循环计算32次,比较耗时。有没有可能,每次消除掉一个1,这样来降低计算次数呢?
 
二、求与法。
观察一下nn-1这两个数的二进制表示:
(1)最末位一个1会变成0;
(2)最末位一个1之后的0会全部变成1;
(3)其他位相同;
 
栗子
           x = 1011 0000
         x-1= 1010 1111
x & (x-1) = 1010 0000
 
于是,n&(n-1)这个操作,可以起到“消除最后一个1”的功效。
 
思路:逐步通过n&(n-1),来消除n末尾的1,消除了多少次,就有多少个1。
 
伪代码

while(n){

   result++;

   n&=(n-1);

}

 
分析:这个方法,n的二进制表示有多少个1,就会计算多少次。总的来说,n的长度是32bit,如果n的值选取完全随机,平均期望由16个1构成,平均下来16次,节省一半的计算量。
 
三、查表法。
空间换时间,是算法优化中最常见的手段,如果有相对充裕的内存,可以有更快的算法。
 
思路:一个uint32的正整数n,一旦n的值确定,n的二进制表示中包含多少个1也就确定了,理论上无需重新计算:
1的二进制表示中包含1个1
2的二进制表示中包含1个1
3的二进制表示中包含2个1
58585858的二进制表示中包含15个1
...
 
提前计算好结果数组:
result[1]=1;
result[2]=1;
result[3]=2;
result[58585858]=15;
 
伪代码
return result[n];
 
查表法的好处是,时间复杂度为O(1),潜在的问题是,需要很大的内存。
 
内存分析
假如被分析的整数是uint32,打表数组需要记录2^32个正整数的结果。
n的二进制表示最多包含32个1,存储结果的计数,使用5个bit即可。
故,共需要内存2^32 * 5bit = 2.5GB。
画外音:5个bit,能表示00000-11111这32个数。
 
四、二次查表法。
查表法,非常快,只查询一次,但消耗内存太大,在工程中几乎不被使用。
 
算法设计,本身是一个时间复杂度与空间复杂度的折衷,增加计算次数,往往能够减少存储空间。
 
思路
(1)把uint32的正整数n,分解为低16位正整数n1,和高16正整数n2;
(2)n1查一次表,其二进制表示包含a个1;
(3)n2查一次表,其二进制表示包含b个1;
(4)则,n的二进制表示包含a+b个1;
 
伪代码

uint16 n1 = n & 0xFFFF;

uint16 n2 = (n>>16) & 0xFFFF;

return  result[n1]+result[n2];

 
问题来了:增加了一倍的计算量(1次查表变2次查表),内存空间是不是对应减少一半呢?
 
内存分析
被分析的整数变成uint16,打表数组需要记录2^16个正整数的结果。
n1和n2的二进制表示最多包含16个1,存储结果的计数,使用4个bit即可。
故,共需要内存2^16 * 4bit = 32KB。
 
好神奇!!!
 
计算量多了1次,内存占用量却由2.5G降到了32K(1万多倍),是不是很有意思?
 
五、总结
数1,不难;但其思路有优化过程,并不简单:
(1)位移法,32次计算;
(2)n&(n-1),能消除一个1,平均16次计算;
(3)查表法,1次查表,2.5G内存;
(4)二次查表法,2次查表,32K内存;
 
知其然,知其所以然
思路比结论重要。

架构师之路-分享可落地的架构文章

 
推荐阅读:
世界上最美的排序算法!
世界上最慢的排序算法!
世界上最开脑洞的排序算法!

发布者:糖太宗,转载请注明出处:https://www.qztxs.com/archives/science/technology/6011

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年5月11日 下午8:49
下一篇 2022年5月11日 下午8:50

相关推荐

  • SQL 速查表

    内容 查找数据的查询 修改数据的查询 聚合查询 连接查询 视图查询 修改表的查询 https://github.com/enochtangg/quick-SQL-cheatsheet/edit/master/README_zh-hans.md   1. 查找数据的查询 SELECT: 用于从数据库中选择数据 SELECT * FROM table_...

    技术 2022年5月28日
    2500
  • OpenVPN + Ldap + OTP

    OpenVPN 服务端的一些配置 VPN是企业内比较重要的一个资产,不能从网上乱下载,去官网看看 https://openvpn.net/,第一次不懂事,不清楚OpenVPN还有商业版,OpenVPN Access Server 和 OpenVPN Community,直接就安装了OpenVPN AS,没有网上说的那么难,还有图形化界面,登陆进行发现2个并发...

    技术 2022年5月28日
    75300
  • 互联网分层架构,为啥要前后端分离?

    有水友在评论中留言问我: 沈老师,我在一家创业公司,大概有20人左右的研发团队。   团队正在推进前后端分离,我觉得架构变得复杂了,项目研发周期变长了,但组长说,互联网公司都在搞前后端分离,所以我们也要搞。   我还是不理解,为什么要进行前后端分离呀? 今天,简单说说,互联网分层架构里的前后端分离。 画外音:“别人在搞xxoo技术”一定不能成为,一家公司推动...

    2022年5月14日
    2700
  • java面试字节跳动——字节码

    1.前言 先来个定义:Java字节码是一组可以由Java虚拟机(JVM)执行的高度优化的指令,它被记录在Class文件中,在虚拟机加载Class文件时执行。 说大白话就是,字节码是Java虚拟机能够看明白的可执行指令。 前面的文章中已经强调了很多次了,Class文件不等于字节码,为什么我要一直强调这个事情呢? 因为在绝大部分的中文资料和博客中,这两个东西都被...

    2022年5月18日
    4700
  • 面试杀手锏:Redis源码之SDS(Redis 数据结构源码解析)

    1.前言 Hello,欢迎大家来到《 Redis 数据结构源码解析系列》,在《Redis为什么这么快?Redis底层实现原理》一文中我说过 ,Redis 速度快的一个原因就是其简单且高效的数据结构。(新手建议反复多推敲几遍) 2.SDS命令实战[初来乍到] SDS 是 Redis 中最简单的数据结构。Redis 中所有的数据结构都是以唯一的 key 字符串作...

    2022年5月19日
    3100

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信