求一个二进制在内存里面存放了多少个1,也就是相当于求这个二进制数的补码中有多少个1。
这里给大家展示三种不同的方法。
第一种写法(易错) 这里给大家展示的是不完全正确的写法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int count_one(int n)
{
int count = 0;
while (n)//当n为0的时候自动跳出
{
if (n % 2 == 1)//当n的补码模2等于1的时候,也就是说n的补码的最后一位必定是1,count累加
{
count++;
}
n = n / 2;//不管末位数是否为1,进一次循环,就利用二进制的特性除以2将最右边的数扔掉,最左边补符号位,无符号位补0
}
return count;
}
int main()
{
//求一个整型数存在内存里面存放了多少个1(求补码中有多少个1)
int n = 0;
int count = 0;
scanf("%d", &n);
count = count_one(n);
printf("count = %d\n", count);
return 0;
}
如果你写代码的时候以为你的代码这样就可以完成任务的时候,那你就错了,上面的写法仅当n是正数的时候是正确的
![](https://i-blog.csdnimg.cn/blog_migrate/b61ee6dfdd34c1cccd95fd158ca54ba3.png)
当n为负数时
![](https://i-blog.csdnimg.cn/blog_migrate/e50447d7dc3613ed391eeccc50dca620.png)
当n = -1的时候,结果为0,而众所周知 -1的补码有32个1
探究一下原因:
当n进入count_one函数的时候, -1模2, -1是不够除2的,会商0,所以count不会++, - 1 / 2也只会商0,当n = 0跳出循环return count,所以当n = -1的时候就会得0(如图)
![](https://i-blog.csdnimg.cn/blog_migrate/b568538ffb84ee3d98bb93ecc7c4019a.png)
当一个数是负数的时候,上面的代码就是存在问题的,该如何解决问题呢?
答:既然负数会让这段代码出现错误的结果,那我们就让接收的数恒为正数即可,即让接收n的时候无视符号位,我们让原本接收的int n改变成unsigned int n,就可以解决问题了
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int count_one(unsigned int n)//将int类型换成unsigned int,将有符号位换成无符号位这时代码就正确了
{
int count = 0;
while (n)
{
if (n % 2 == 1)
{
count++;
}
n = n / 2;
}
return count;
}
int main()
{
//求一个整型数存在内存里面存放了多少个1(求补码中有多少个1)
int n = 0;
int count = 0;
scanf("%d", &n);
count = count_one(n);
printf("count = %d\n", count);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/e1f9022aa5992fff85ac0b8e0d38828f.png)
第二种方法
我们知道-1的补码 10000000000000000000000000000001
而1的补码是 00000000000000000000000000000001
此时-1&1会得到 00000000000000000000000000000001
从这里可以简单看出:当1按位与上某一个数的时候会得到1本身
我们可以利用这一特点,让一个数和1进行按位与,当这个数的最末尾那个数按位与1 == 1的时候,也就是说这个数的末尾数也是1,再向右移一位,继续按位与1,重复该动作,这样也可以计算出1的个数了
代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int count_one(int n)
{
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)//-1的补码是10000000000000000000000000000001
{ //1的补码是 00000000000000000000000000000001 两者相与等于1的时候,表示-1的末位数位1
if (((n >> i) & 1) == 1)//当按位与1后,再向右位移i位,就达到了-1中的数逐个与1的效果
{
count++;
}
}
return count;
}
int main()
{
//求一个整型数存在内存里面存放了多少个1(求补码中有多少个1)
int n = 0;
int count = 0;
scanf("%d", &n);
count = count_one(n);
printf("count = %d\n", count);
return 0;
}
但该方法存在一定的缺点:就是不管是什么数,都要与1进行31次比较,最后才能判断1的个数
第三种方法
这是一种比较经典的处理方法,大家可以好好参考一下
假设n=13,则它的补码为 1101
则n-1的补码为 1100
此时令n=n&(n-1),会得到 1100
此时n再减1得 1011
此时再令n=n&(n-1)得 1000
n-1 0111
n=n&(n-1)得 0000
此时我们可以发现一个规律:每当n-1后再按位与一个n,会使这个数最右边得那个1消失,当该操作重复下去的时候,n最终会变成0。
我们可以通过这个规律可以用来求出n的补码中有几个1。
代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int count_one(int n)
{
int count = 0;
while (n)
{
n = n&(n - 1);
count++;
}
return count;
}
int main()
{
//求一个整型数存在内存里面存放了多少个1(求补码中有多少个1)
int n = 0;
int count = 0;
scanf("%d", &n);
count = count_one(n);
printf("count = %d\n", count);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/b65cc86d6fb3016fb93ea390f424d0bb.png)
在这三种方法里面,方法三当然是最高效、最实用的,但也是最让人难以想到的。
今天就给大家分享这三种方法解决牛客网上的热题。
如有错误遗漏,欢迎指正,一起学习,一起进步。