面试题10:二进制中1的个数
我们在日常生活中用到的是十进制,所以一开始接受其他进制都感觉很不习惯;在程序员圈子里有一个流传了很久的笑话,说世界上一共有10种人,一种人知道二进制,而另一种人不知道二进制……
说起二进制,就要提到与之形影不离的位运算了,位运算总共有五种运算:与、或、异或、左移和右移。
下图是与、或、异或的运算规律:
左移运算符m<<n表示把m左移n位。左移n位的时候,最左边的n位将被丢弃掉,同时在最右边补上n个0.
例如:
00001010<<2 = 00101000
10001010<<3 = 01010000
右移运算符m>>n表示把m右移n位。右移n位的时候,最右边的n位将被丢弃。但右移分两种情况:第一种:如果数字是一个无符号的数值,则最左边的n位用0填补;另一种情况是:如果数字是一个有符号的数值,则用数字的符号位填补最左边的n位。
总结:数字是正数,右移移动n位,最左边补n位0;数字是负数,则右移之后在最左边补n个1.
例如:
00001010>>2 = 00000010
10001010>>3 = 11110001
面试题10:
题目:
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把 9表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2。
第一种算法:
思路:先判断整数二进制最右边的一位是不是1,是1,则计数器++,并进行右移,;是0则直接进行右移,直到这个整数的值为0;
代码:
//不好,可能会引起死循环,不是理想的
int NumberOf1(int n)//形参设置成unsigned int类型可以避免死循环
{
int count = 0;
while(n)
{
if(n & 1)
{
count++;
}
n = n >> 1;
}
return count;
}
缺点:当这个数为负数时,每次进行右移的时候,左边会进行补符号位操作,也就是1,当全部变成1的时候,就成为了死循环,这不是我们想要看到的,所以上述代码只适合形参为正数的计算。
第二种算法:
思路:定义一个变量flag值为1,首先让它与形参进行与运算,如果为真则计数器++,并且flag变量左移一位,即到了第二位,继续进行相同的操作;若为假,则直接左移一位,直到flag变量为0,也就是移动了和形参相同的位数,最后变量所有的位全为0,则运算结束。
代码:
//常规算法:输入的整数转化成二进制有多少位,就需要循环多少次
int Num_find_1(int n)
{
int count = 0;
unsigned int flag = 1;
while(flag)
{
if(n & flag)
{
count++;
}
flag = flag << 1;
}
return count;
}
缺点:输入的整数转化成二进制有多少位,就需要循环多少次,效率不是很高。
第三种算法:
思路:利用把形参这个整数减去1,再和原整数做与运算,这样每次都会把整数最右边的一个0变成0;并且形参转化成二进制中有几个1,则执行与之对应的次数,相比较前面两种而言,不担心正数和负数的特殊情况,效率也明显得到了提高;
图解:
代码:
int Num_of_1(int n)
{
int count = 0;
while(n)
{
count++;
n = (n-1) & n;
}
return count;
}
int main()
{
/*------------------------计算二进制中1的个数普通方法测试用例-----------------------*/
/*出现死循环的测试,传入一个负数会出现死循环这种可能,所以形参设置成unsigned,
可以避免这种情况*/
//cout<<NumberOf1 (-1) <<endl;//死循环
/*------------------------计算二进制中1的个数常规算法测试用例-----------------------*/
//cout<<Num_find_1(-1) <<endl;//效率不是很好,时间复杂度O(n)
/*------------------------计算二进制中1的个数完美算法测试用例-----------------------*/
//正数:n为1
cout<<"二进制中1的个数为: "<<Num_of_1(1)<<endl;
//正数:n为0x7FFFFFFF
cout<<"二进制中1的个数为: "<<Num_of_1(0x7FFFFFFF)<<endl;
//负数:n为0x80000000
cout<<"二进制中1的个数为: "<<Num_of_1(0x80000000)<<endl;
//负数:n为0xFFFFFFFF
cout<<"二进制中1的个数为: "<<Num_of_1(0xFFFFFFFF)<<endl;
//特殊测试:n为0
cout<<"二进制中1的个数为: "<<Num_of_1(0)<<endl;
return 0;
}
扩展一:用一条语句判断一个整数是不是2的整数次方,一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0.再由前面所述得到:把这个整数减去1之后再和它自己做与运算,这个整数中唯一的1就会变成0.
扩展二:输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。比如10的二进制表示为1010,13的二进制表示为1101,需要改变1010中的三位才能得到1101.我们可以分两步去解决它:第一步求这两个数的异或,第二步统计异或结果中1的位数。