题目:
输入一个 32 位整数,输出该数二进制表示中 1 的个数。
注意:
- 负数在计算机中用其绝对值的补码来表示。
数据范围
−100≤ 输入整数 ≤100
样例1
输入:9 输出:2 解释:9的二进制表示是1001,一共有2个1。
样例2
输入:-2 输出:31 解释:-2在计算机里会被表示成11111111111111111111111111111110, 一共有31个1。
前景知识:
这里比较疑惑的点在于“负数在计算机中用其绝对值的补码来表示”这句话。如果我们了解过计算机组成原理的知识,便会知道计算机中保存一个数是用二进制的形式,而二进制表示存在原码、补码、反码、移码的形式,其中最常见的是原码和补码。
一、原码补码的本质关系及各自的用处
原码:创造的目的在于比较直观的体现每一数的大小,符合人类十进制的观察方式。
例如对于1其八位原码可以表示为0,0000001。而-1的八位原码表示为1,0000001。这里虽然我们可以直观的看到1和-1哪个更大。
但是原码的表示存在问题,那就是其不能直接进行加减的运算。例如:
0,0000001+1,0000001=1,0000010。这个我们1+-1=0有相当大的出入,所以我们必须创造另外一种表示方式使得我们对二进制可以像十进制一样直接进行加减运算。
补码:创造目的在于使得二进制可以像十进制一样直接进行加减运算。
想要让二进制像十进制一样直接进行加减运算。从数论的角度,我们要解决的问题就是1+-1这种相反数相加必须为0一个初始的数。有了这样一个思路,那么问题就变得简单,这里我们仍然让正数的补码等于其原码,然后去调整负数的补码让其和正数相加后等于二进制的0,那么问题就解决了,即二进制也可以像十进制一样相加减了。
二、相反数补码转化方式
按照上面的理论,整数的补码为其原码,那么为了保证其负数补码加上其正数补码为0,则其负数补码应为正数各位取反然后末尾+1(各位取反则和正数补码相加为最大值,末尾+1则让最大值重新变为0。这里的本质思想是模除,这里不再展开,感兴趣的可以自己去深入了解)
三、同一数原码补码转化方式
正数:原码补码相等(人为设定,因为只要保证相反数补码相加为零即可,故可以使得其中一个补码不变,另一个补码较原码进行调整)
负数:补码为原码除符号位以外各位取反末尾加1。这里的本质其实和二中的思想相同,不过由于负数的原码符号位本身已经和原码取反了,所以只要再将除符号位的其他位取反再末尾+1,即可保证上面数论的要求。
题目分析:
本题“输入32位整数”,其含义为整数的二进制位数为32位。这里举例9的二进制1001,其实其在计算机中的补码表示为28个0+1001(加表示直接连接),故结果数两个1。而-2的原码为1+29个0+10(这里的+表示直接连接的意思),所以取反+1后为1+29个1+10,故最终答案为31个1。
代码如下:
class Solution { public: int NumberOf1(int n) { unsigned int _n = n; int res = 0; while (_n) { if (_n & 1) { res++; } _n >>= 1; } return res; } };
代码分析:
1、为什么要unsigned_int 类型。因为我们的思路是不停把最后一位和1进行取&,如果为1,则把答案res+1。接着将数右移,然后继续对新的下一位取&。这就要考虑我们右移后前面位置补位的问题,如果是普通的int类型,用补码表示右移后会在前面补1。这就导致我们不知道实际上原始数据有多少个1,所以这里把它转换成无符号整数,那么其补码右移后前面会补0。
2、n&1比较的是哪一位。cpp中如果n为多位的二进制数,而1为一位二进制数,则默认把1和n的末位进行比较。因为1可以转变为00……001,那么进行比较后只有最后一位有价值。
3、右移符号的应用。n>>=1表是右移一位并且赋值给n。这种双目运算符的计算方式都是相同的