挑战面试编程:计算整数二进制位中1的个数
题目:
在计算机中,整数是以2的补码的形式给出的。 给出整数A和B,假设计算机是32位机,求从A到B之间的所有二进制数中,一共用了多少个1。 输入格式: 多组数据,每组数据一行,由两个整数A,B, -2147483648<=A<=B<=2147483647 输出格式: 每组输出一行,从A到B使用的1的个数。(本题取自csdn高校俱乐部线上编程挑战赛)
分析:
我们知道任何数据在计算机中都是用二进制表示的,即用一堆0或1表示各种类型的数据。当然还要考虑它的字节数,例如在32位机中。sizeof(int)=4,sizeof(float)=4,sizeof(double)=8,sizeof(long long)=8。当然一种数据类型配了多少字节来存储,这不仅与cpu的字长、系统有关,还与编译器有关。上面的数据是我的机器上测试值。既然要统计二进位中1的个数,当然优先考虑位运算。
下面介绍几种位运算(位运算即按位运算):
位与 &
运算规则:0&0=0;
位或 |
运算规则:0|0=0;
位异或 ^
运算规则:0^0=0;
思路:
用该整数与1做32次位与运算,即可统计1的个数。因为一个四个字节的整数1,它的高位有31个0,只有最后一位是1,即00000000 00000000 00000000 00000001。这样就可以判断概数的二进制最低位是1还是0。位于结果若是1,则表明最后一位1,若是0,则表明最后一位是0(对照运算规则,可很容易看出)。每一次判断后,都得对该整数右移一位,以判断次最低位。
代码
<span style="font-family:Courier New;font-size:14px;">
#include<iostream>
using namespace std;
long long count_one(int n)
{
long long count=0; //考虑到数字可能很大,就用使用long long类型
for(int i=0; i<32; i++)
{
if(n&1) //位与运算
count++;
n>>=1; //向右移动一位
}
return count;
}
int main()
{
int i,j;
int a,b;
long long sum=0;
while(cin>>a>>b && a) //输入0 0结束程序
{
for(int j=a; j<=b; j++)
sum+=count_one(j);
cout<<sum<<endl;
sum=0; //重置
}
cin.get();
return 0;
}
</span>
运行实例:
update:
感谢一楼的提示,有了以下的新思路:
思路:如果当前数的最小二进位是0,则下一个数不用统计,便可知它的二进制位1的个数比当前数多一个。于是,我们可以两个两个的计算,大概只需遍历 n/2 次,这样是不是快很多。
<span style="font-family:Courier New;font-size:18px;">
int count_one(int a, bool &flag)
{
int temp = a;
int count = 0;
if (temp & 1)
flag = false;
while (temp)
{
count++;
temp = temp&(temp-1);
}
return count;
}
int main()
{
int a, b;
bool flag = true;
int count = 0;
double sum = 0;
while (scanf_s("%d %d", &a, &b) != EOF)
{
for (int i = a; i <= b; i++)
{
count = count_one(i, flag);
if (flag)
{
if (i < b) //若还有下一个数
{
sum += 2 * count + 1;
i++;
}
if (i == b) //无下一个数
sum += count;
}
else
{
sum += count;
}
flag = true;
/* //这个是以上流程的优化写法
if (flag && i < b)
{
sum += 2 * count + 1;
i++;
}
else
sum += count;
flag = true;
*/
}
printf("%.0lf\n", sum);
sum = 0;
}
system("pause");
return 0;
}
</span>
几点说明:
- 右移运算的左边二进位如何填充。这个c语言规范中没有明确规定,那就是说具体实现得看编译器如何处理。还好大多数编译器是这样处理的:对于无符号数或正数,用0填充;对于负数用1填充。大家可以在自己的机器上试试。
- 右移运算a>>1,是不会改变a自身的值的,要想改变a自身的值,得使用a>>=1。
- 为了终止程序,我个人加了对a值非0的判断,即此程序不可原封不动作为挑战赛的答案,得进行必要修改。
- 关于位运算的奇妙用处还有很多,希望各位同学多提想法,欢迎不吝赐教。先谢了!
补充:
异或运算^ 可用来交换数据(注意:这里的a,b不能指向同一个元素,否则最后都是0,所加一个是否相等的判断)
void swap(int &a, int &b)
{
if(a!=b)
{
a^=b;
b^=a;
a^=b;
}
}
关于交换数据的其它方法:
最常见的是增加一个变量 temp,方法如下:
方法一:
void swap(int &a, int &b)
{
if(a!=b)
{
int temp=a;
a=b;
b=temp;
}
}
方法二:
void swap(int &a, int &b)
{
if(a!=b)
{
a=a+b;
b=a-b;
a=a-b;
}
}
update 2015年10月9日
最快的算法或许是这样:
对于一个整型的数a,做运算a = a & (a - 1);后a二进位为1且位于最右边的那一个二进制位会消失。
如a=3;做运算a&(a-1)
0000 0011
& 0000 0010
0000 0010
由于这种现象的存在,只需记录a为0前,运算a&(a-1)可以进行的次数,即可得知a的二进制位上1的个数。
所有内容的目录