前言:
& 与
| 或
~ 非
^ 异或(相同为0,相异为1)
>> 右移
<< 左移
常用操作:
求x的二进制位的第k位数字 x >> k & 1
lowbit(x) = x & -x,返回x的二进制位的最后一位1
目录
二进制中1的个数
算法1、(暴力枚举) O(nlongn)
思路:
对于每个数字a,a&1得到了该数字的最后一位,之后将a右移一位,直到位0,就得到了1的个数
C++ 代码
#include<iostream>
using namespace std;
int n;
int a,k;
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a);
k=0;
while(a){
k+=a&1;
a=a>>1;
}
printf("%d ",k);
}
return 0;
}
算法2、 (lowbit) O(nlogn)
思路:
使用lowbit操作,进行,每次lowbit操作截取一个数字最后一个1后面的所有位,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数;
根据计算机负数表示的特点,数字在内存中以补码形式存储,正数原码、反码、补码相同;负数表示形势是补码,就是反码+1,
如:10的补码00001010,-10的补码为反码+1,即11110110(11110101+1),二者按位与得到了00000010,就是2,10 - 2=8,8补码00001000.......一直减下去直到变成00000000
#include<iostream>
using namespace std;
int lowbit(int x){
return x&(-x);
}
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
int res=0;
while(x) x-=lowbit(x),res++;
cout<<res<<' ';
}
return 0;
}
算法3、(位运算) 接近O(1)
思路:
读懂了算法2的操作就明白了,算法2的优化
#include <iostream>
using namespace std;
int cnt(int b){
int res = 0;
while (b>0) {
b = b & (b-1);
res++;
}
return res;
}
int main(){
int n, b;
cin >> n;
for (int i=0;i<n;i++) {
cin >> b;
cout << cnt(b)<<" ";
}
return 0;
}
整数转换
解题思路
- n = A ^ B,可以得到A和B有哪几位是不同的,即n二进制1的个数,就是A和B有几位不同的数量
- n = n & (n - 1)可以去掉n二进制的最右边的一个1,可以用来统计1的数量,那么1的数量就是答案
class Solution {
public:
int convertInteger(int A, int B) {
unsigned int n = A ^ B;
int cnt = 0;
while(n){
n &= n - 1;
cnt++;
}
return cnt;
}
};
注意:
如果A,B每一位都不同,如 01010101... 和 10101010...,这样异或结果为32个1。 此时 x - 1会超过 INT_MIN,所以必须将x设置为无符号整数。(不可用long long,因为有64位,可能导致异或结果多出32)
-1的返回值
int func(int x)
{
int count = 0;
while (x)
{
count++;
x = x & (x - 1);//与运算
}return count;
}
A: 死循环 B: 64 C: 32 D: 16
正确答案: Cx=x&(x-1)这个表达式执行一次就会将x的2进制中最右边的1去掉,在x变成0之前,表达式能执行几次,就去掉几个1,所以这个代码实现了求一个有符号整数二进制补码中1的个数的功能,我们知道-1的补码是全1,而int类型4个字节32位,选C
int count = 0;
int x = -1;
while (x)
{
count++;
x = x >> 1;
}
printf("%d", count);
A: 1 B: 2 C: 32 D: 死循环,没结果
正确答案: D此题一个关键,有符号数右移运算高位是补符号位的,负数的符号位是1,所以x永远不会变为0,是个死循环
void func()
{
int k = 1 ^ (1 << 31 >> 31);
printf("%d\n", k);
}
正确答案: C(1 << 31 );左移31位,并在右侧填充0,得到0x80000000,即符号位为1,其他为0即-2147483648int k = 1^(1 << 31 >> 31);注意,这里在右移的时候,符号位保持为1,右移后填充1,结果为0xFFFFFFFF,即-1,0x00000001^0xFFFFFFFF,即0xFFFFFFFE(-2)