剑指offer 二进制中1的个数(C++)两种方法

本文深入探讨了计算机中二进制与十进制的转换,重点讲解了如何通过按位与运算计算整数二进制表示中1的个数,以及如何判断一个数是否为2的幂。提供了两种不同的算法实现,并分析了它们的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

输入一个整数,输出该数二进制表示中111的个数。其中负数用补码表示。

考查知识点

1、计算机中的二进制与十进制

计算机中的int类型占用444个字节,每个字节占888位,则int类型占用了323232位。

计算机中的正整数就是其本身转化为二进制后的表示;负数的表示是其绝对值的补码

原码:
一个正数的原码,是按照绝对值大小转换成的二进制数;
一个负数的原码,是按照绝对值大小转换成的二进制数,然后最高位补111

比如:
555−5-55 的原码分别是:

00000000 00000000 00000000 00000101; //5的原码
10000000 00000000 00000000 00000101; //-5的原码

反码:
正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。

比如:
555−5-55 的反码分别是:

00000000 00000000 00000000 00000101; //5的反码
11111111 11111111 11111111 11111010; //-5的反码

补码:

正数的补码与原码相同;

负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加111

比如:

−5-55的补码为:

1111111 11111111 11111111 11111011; // -5的补码

总结:
原码表示法规定:用符号位和数值表示带符号数,正数的符号位用“000”表示,负数的符号位用“111”表示,数值部分用二进制形式表示。

反码表示法规定:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。

补码表示法规定:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加111

正零和负零的补码相同,[+0]补=[-0]补=0000 0000B

八进制和十六进制:

无论是多少进制,C、C++都是int类型,八进制前面加000,十六进制前面加0x。但如果直接打印数值,结果还是十进制。

int i = 0x75; // 无论8进制还是16进制  直接写  打印的时候直接恢复到十进制
cout << i << endl; // i = 117为其结果

2、C,C++中的运算符

<<表示左移操作符:

m << n :表示m的2n2^n2n,也表示m为二进制时,左移n位,即后面补n个000

int j = 5 << 2; // 表示5 * 4 = 20
cout << "j = " << j << endl; // 20

>>表示右移操作符:

m >> n :表示m 除以 2n2 ^ n2n 取整,也表示m为二进制表示时,m右移n位,即删掉后面n位数。

int j = 7 >> 2; // 7 / 2 = 3
cout << "j = " << j << endl; // 3

&表示按位与:

两个二进制数每一位均为111,该位置返回111,其余情况返回000

int i = 5, j = 4;// 5 = 101, 4 = 100 
int k = i & j; // 按位与 k = 100
cout << k << endl; // 4

|表示按位或:
两个二进制数每一位均为000,该位置返回000,其余情况返回111

int i = 6, j = 5;// 6 = 110, 5 = 101
int k = i | j; // 按位与 k = 111
cout << k << endl; // 7

&&表示逻辑与:

&&两边式子为真返回真,否则返回假。

||表示逻辑或:

||两边的表达式均为假返回假,否则返回真。

解题思路

本题目可以通过 & 按位与运算来求二进制中111的个数 和通过 对一个从111开始的整数循环的 << 左移 来匹配二进制中1的个数。

代码实现1

如果一个整数不为000,那么这个整数至少有一位是111。如果我们把这个整数减111,那么原来处在整数最右边的111就会变为000,原来在111后面的所有的000都会变成111(如果最右边的111后面还有000的话)。其余所有位将不会受到影响。

例如: 如果 n=10011n = 10011n=10011

1、n−1=10010n - 1 = 10010n1=10010 ,n & flag 是 10011&10010=1001010011 \& 10010 = 1001010011&10010=10010,此时$ n = 10010$,即二进制中少了一个111

2、n−1=10010−1=10001n - 1 = 10010 - 1 = 10001n1=100101=10001,n & flag 是 10010&10001=1000010010 \& 10001= 1000010010&10001=10000,此时 n=10000n = 10000n=10000,即二进制中又少了一个111

以此类推,每次进行按位与,均少一个111,最后 n = 0 统计少了多少个111即可。

class Solution {
public:
     int  NumberOf1(int n) {
         int num = 0;
         while(n)
         {
             num++;
             n &= (n - 1); //n = n & (n - 1); // 两者一致
         }
         return num;
     }
};

代码实现2

flag每次 的 flag << 1 就是每次 从111 变成 101010 变成 100100100 变成 100010001000… 每次 flag 与 n进行按位与 只要不为000 就说明 nnn 中的某一位为111 否则该位为000

例如: 如果 n=10010n = 10010n=10010
1、flag = 1,n & flag 是 10010&00001=010010 \& 00001 = 010010&00001=0;
2、flag = 10,n & flag 是 10010&00010=0001010010 \& 00010 = 0001010010&00010=00010,即该位置存在111

以此类推即可。

这种思想还是有一定的隐患的,就是while循环必须32次,直到flag = 0 为止。

class Solution {
public:
     int  NumberOf1(int n) {
         int num = 0;
         int flag = 1;
         while(flag)
         {
             if (n & flag)
                 num++;
          flag = flag << 1; // flag <<= 1; //两者一致
         }
         return num ;
     }
};

下面是在代码中的while循环中添加这段代码后的效果:

cout << "flag = " << flag << endl;

flag的变化:

flag = 2
flag = 4
flag = 8
flag = 16
flag = 32
flag = 64
flag = 128
flag = 256
flag = 512
flag = 1024
flag = 2048
flag = 4096
flag = 8192
flag = 16384
flag = 32768
flag = 65536
flag = 131072
flag = 262144
flag = 524288
flag = 1048576
flag = 2097152
flag = 4194304
flag = 8388608
flag = 16777216
flag = 33554432
flag = 67108864
flag = 134217728
flag = 268435456
flag = 536870912
flag = 1073741824
flag = -2147483648
flag = 0

可见这种算法的效率还是比较低的。

某公司面试真题

用一个表达式,判断一个数xxx是否是2N2^N2N,不能用任何循环语句。

解析

2、4、8、162、4、8、1624816…转化为二进制是10、100、1000、1000010、100、1000、1000010100100010000…,也就是二进制中存在至多111111,如果x−1x - 1x1后再与xxx进行按位与运算,返回000则说明是xxx2N2^N2N,否则不是。

答案

!(x & (x - 1));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值