剑指Offer--010-二进制中1的个数
2016-04-11 15:30
549 查看
链接
牛客OJ:二进制中1的个数九度OJ:http://ac.jobdu.com/problem.php?pid=1513
GitHub代码: 010-二进制中1的个数
CSDN题解:剑指Offer–010-二进制中1的个数
牛客OJ | 九度OJ | CSDN题解 | GitHub代码 |
---|---|---|---|
二进制中1的个数 | 1513-二进制中1的个数 | 剑指Offer–010-二进制中1的个数 | 010-二进制中1的个数 |
题意
题目描述输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
样例输入
3
4
5
-1
样例输出
1
2
32
通过移位测试每一位
我们很容易想到的算法就是通过移位的方法,挨个判断其每一位
class Solution { public: int NumberOf1(int n) { int count = 0; while(n) { if(n & 1 == 1) { count++; } n >>= 1; } return count; } };
但是这个算法有致命的缺陷,我们假设通过移位的方式可以获取到其每一位,但是并不总是对的
逻辑右移与算术右移
比如一个有符号位的8位二进制数11001101,逻辑右移就不管符号位,如果移一位就变成01100110。算术右移要管符号位,右移一位变成10100110。逻辑左移=算数左移,右边统一添0
逻辑右移,左边统一添0
算数右移,左边添加的数和符号有关
e.g:1010101010,其中[]位是添加的数字
逻辑左移一位:010101010[0]
算数左移一位:010101010[0]
逻辑右移一位:[0]101010101
算数右移一位:[1]101010101
因此如果输入负数,那么我们的算法简单的判断是不是0来终结,岂不是要死循环
避免负数移位的死循环
为了负数时候避免死循环,我们可以不右移数字n,转而去移动测试位那么思考我们的循环结束条件,flag一直左移(乘以2),当超出表示标识范围的时候,我们就可以终止了,但是这样子的话,最高位的符号位没有测试,因此要单独测试,同时由于会溢出,我们的flag需要用long来标识
类型 | 范围 |
---|---|
unsigned int | 0~4294967295 |
int | 2147483648~2147483647 |
unsigned long | 0~4294967295 |
long | 2147483648~2147483647 |
long long | 最大值:9223372036854775807 |
long long | 最小值:-9223372036854775808 |
unsigned long long | 最大值:1844674407370955161 |
__int64 | 最大值:9223372036854775807 |
__int64 | 最小值:-9223372036854775808 |
unsigned __int64 | 最大值:18446744073709551615 |
class Solution { public: int NumberOf1(int n) { int count = 0; int i = 0; unsigned long flag = 1; while(flag <= INT_MAX) { debug <<n <<" & " <<flag <<" == "<<(n & flag) <<endl; if((n & flag) != 0) { count++; } flag <<= 1; } // 由于循环终结,我们使用的是flag <= INT_MAX // 因此前面的循环只会执行31次 if((n & flag) != 0) // 最后测试符号位 { count++; } return count; } };
继续考虑,有没有什么能够精简我们的循环条件,
考虑这点,如果数据发生溢出的话,会被截断,截断以后是多少呢,0
代码如下
class Solution { public: int NumberOf1(int n) { int count = 0; int i = 0; unsigned int flag = 1; while(flag != 0) { debug <<n <<" & " <<flag <<" == "<<(n & flag) <<endl; if((n & flag) != 0) { count++; } flag <<= 1; } debug <<n <<" & " <<flag <<" == "<<(n & flag) <<endl; return count; } };
这种方法循环的次数正好是是整数二进制数的位数,比如int就是32位循环32次
那么有没有一种方法,整数中有几个1就循环几次呢?
整数中有几个1就循环几次–lowbit优化
我们分析n以n-1两个数的差别,如果n!=0,那么其二进制位中至少有一个1
如果n的最低位是1(奇数),那么n-1正好把这个最低位的1变成0,其他位不变
如果n的最低位是0(偶数),那么假设其右起第一个1位于m位,即m位后面全是0,那么n-1的第m位由1变成0,而第m位后面的所有0均变成1,m位之前的所有位保持不变。
因此通过分析发现:
把一个整数n减去1,再和原来的整数做与运算,会把该整数最右边一个1变成0,那么该整数有多少个1,就会进行多少次与运算
即循环的次数,即二进制中1的个数
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
#include <iostream> using namespace std; // 调试开关 #define __tmain main #ifdef __tmain #define debug cout #else #define debug 0 && cout #endif // __tmain class Solution { public: int NumberOf1(int n) { int count = 0; while(n) { count++; n = n & (n - 1); } return count; } }; #include <cmath> int __tmain( ) { cout <<sizeof(long) <<endl; Solution solu; cout <<solu.NumberOf1(1) <<endl;; cout <<solu.NumberOf1(2) <<endl;; cout <<solu.NumberOf1(-2147483648) <<endl;; return 0; }
STL-bitset
STL中用来方便地管理一系列的bit位而不用程序员自己来写代码。bitset除了可以访问指定下标的bit位以外,还可以把它们作为一个整数来进行某些统计
class Solution { public: int NumberOf1(int n) { bitset<32> bit(n); return bit.count(); } };
相关文章推荐
- 查看github pages文档的方式
- 使用BAE的基于Web.py的简单博客程序
- 只有程序员看的懂的面试圣经|如何拿下编程面试
- 下一次技术面试时要问的 3 个重要问题
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- PHP程序员面试 切忌急功近利(更需要注重以后的发展)
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- mysql binlog二进制日志详解
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解