题目:请实现一个函数,输入一个整数,输出该数二进制表示中
的个数。例如,把
表示成二进制是
,有
位是
。因此,如果输入
,则该函数输出
。
我们通过将数字与 1 相与,即可知道数字的二进制的最后一位是否为 1,我们只要逐渐的将数字进行右移,即可统计出数字中 1 的数目,所以会写下这样的代码
public static int numbersOfOne(int n) {
int count = 0;
while (n != 0) {
if ((n & 1) != 0) {
count++;
}
n = n >> 1;
}
return count;
}
但是上面的代码有一个问题,那就是如果输入的整数为负数呢? 根据负数的表示原则,它的首位是 1 来表示数字的正负性,在右移的过程中,首位会进行补 1 以表示它的符号还是负的,这个时候统计出的数字就是错误的了,并且会造成上面代码的死循环,如下以 8 位表示的 -7 为例
为了解决负数引起的问题,我们可以反其道而行之,让 1 进行左移,然后与数字相与,同样也可以统计出数字 1 的个数,所以修改代码如下
public static int numbersOfOne(int n) {
int count = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0) {
count++;
}
flag = flag << 1;
}
return count;
}
但是我们发现统计负数中 1 的个数的结果与我们想象的不符,比如我们统计 -7 的个数,结果为
public static void main(String[] args) {
System.out.println(numbersOfOne(-7)); // 30
}
结果是 而不是 ,难道是我们的算法错了,其实不是
,而是计算机存储数字是以其补码进行存储的,对于正数来说,它的补码是它本身,而对于负数,则是将除符号位进行取反,然后加
,所以 的补码为
上面计算出一个数字二进制表示的 的个数需要进行
次循环(对于
int
类型的数据),我们可以进一步对算法进行改进,使得二进制中有几个 则进行几次循环。为了理解下面讲的算法,我们来看一下将一个数字减去
它的二进制会发生什么变化?
先说结论,假设数字二进制最右边的 在第
位,即第
位后面的位全是
,在减去
之后,第
位的
变成
,后面的
全部变为
,而第
位前面的数字保持不变,以
位表示的
为例
那么我们将数字 与
进行相与会得到什么,因为
第
位前面的数字不变,所以
的第
位以前的数字与
相同,所以
n & (n - 1)
得到的第 位以前的二进制是不变的,而
第
位变成
,以及第
位以后的
变为
,所以
n & (n - 1)
得到的第 位及第
位以后的二进制是
,所以
n & (n - 1)
的效果就是将数字 中最右边的
给去除了
所以我们就可以写出这样的代码
public static int numbersOfOne(int n) {
int count = 0;
while(n != 0) {
count++;
n = (n - 1) & n;
}
return count;
}
每次循环会去除一个 ,所以二进制中有几个
,便会进行几次循环。
扩展:
- 用一条语句判断一个整数是不是
的整数次方。一个整数如果是
的整数次方,那么它的二进制表示中有且只有一位是
,而其他所有位都是
。所以我们统计该数二进制中
的个数,如果是一个,那么就是
的整数次方。
- 输入两个整数
和
,计算需要改变
的二进制表示中的多少位才能得到
。比如
的二进制表示为
,
的二进制表示为
,需要改变
中的
位才能得到
。我们可以分两步解决这个问题,首先将这两个数进行异或,这样
和
二进制不同的位会得到
,相同的位是
,我们只要统计异或后二进制中
的个数,即可得到需要改变的位数。