位运算小结


1. 位运算简介

C++的变量在内存中是以二进制的方式存储的,如果直接对其采用位运算,可以大大简化运行时间,提高运行速度。
其头文件为:

#include<bits/stdc++.h>

2. 按位与的小技巧

2.1 判断一个整数是否为2的倍数

如果一个数是偶数,其二进制数一定为xxxxx0,也就是说其末尾一定为0;如果一个数是奇数,其二进制数已定位xxxxx1,末尾一定为1,那么只要去判断其最后一位,就可以判断奇偶性。1的二进制数为00001(前面有多个0),与1做与运算,就可以判断这个数是不是偶数。

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n = 5; // 5 -- 101
  bool ans = n & 1; // 101 & 001 = 001 --> 1
	// ans == 1 odd 
	// ans == 2 even
	return 0;
}

2.2 判断一个数是否为2的幂级数

代码如下:

int n = 8;
bool ans = ! (n & (n - 1));

2.3 判断一个数是否为4的幂级数

判断一个数是否为4的幂级数的充分必要条件如下:
1.该数是2的幂级数,即 !(n & (n - 1)) 为true;
2.该数和(20+22+24+…+230)按位与,结果不为0

int cmp = 0;
for (int i = 0; i < 32; i = i + 2) {
	cmp = cmp*4 + 1;
}
return !(n & (n - 1)) && (n & cmp);

这道题也可以用递归来做,如果n&10且n>>1 & 10,那么再进入下一层递归,直到1。

bool isPowerOfFour(int n) {
    if (n <= 0) return false;
    if (n == 1) return true;
     int tmp = n & 1;
    if (tmp == 0) {
        n = n >> 1;
        tmp = n & 1;
        if (tmp == 0) return isPowerOfFour(n >> 1);
    }
    return false;
}

2.4 JZ15 二进制中1的个数

这一题看起来很容易,直接用1按位与,然后将数字右移一位,直到它为0就可以了,这各方法对于正数来说是可以的,因为正数往右移一位,最前面补0。但是如果是负数的话,往右移一位,补的是1。
所以不能用与1按位与的方法,如下方法可以每次将最后一个1变成0:

n & (n - 1)

比如说二进制数01110,其-1得到01101,那么按位与得到01100,最后一个1变成了0。
因此本题代码如下:

int  NumberOf1(int n) {
     int ans = 0;
     while (n != 0) {
         n = n & (n - 1);
         ans++;
     }
     return ans;   
 }

3. 按位异或的小技巧

3.1 保留原数则与0异或,翻转原数则与1异或

代码如下:

int n = 5;
n = n ^ 0;

3.2 Leetcode 136 求数组中出现过奇数次的数

这个用到了异或的交换律和结合率,即a ^ b ^ a = b。

int ans = nums[0];
for (int i = 1; i < nums.size(); ++i) {
     ans = ans ^ nums[i];
}
return ans;

3.3 JZ56 数组中只出现一次的两个数字

这个用到了异或的交换律和结合率,即a ^ b ^ a = b。以及:
a ^ b = c,那么c ^ a = b, c ^ b = a。
这一题要注意的是,有两个数字都出现过一次,都要找出来。

vector<int> FindNumsAppearOnce(vector<int>& array) {
	// 以 1 2 2 3 3 9为例
	int sum = 0;
	vector<int> ans(2);
	for (int x: array) sum ^= x; // sum 为 1(0001)^9(1001) = 8(1000)
	int k = 0;
	while (!(sum >>k & 1)) k++; // 找到sum中第一个为1的位置,说明这一位的两个数不同 k = 3
	for (int x: array) {
		if (x >> k & 1) ans[0] ^= x; // ans[0] = 9
	}
	ans[1] = sum ^ ans[0]; // ans[1] = 8 ^ 9 = 1
	return ans;
}

3.4 汉明距离

代码如下:

int n = x ^ y, ans = 0;
// 方法1 让n向右移位
while (n > 0) {
	ans += n & 1; // 让这个数的最后一位跟1比较,如果有1,就给ans+1
	n >>= 1; // 然后让n向右移位
}
// 方法2 让flag向左移位
int flag = 1;
while (flag > 0) {
	int temp = n & flag;
	ans += (temp == flag); // 如果temp和flag相同,那么说明n和flag对应的1位是相同的,此时ans+1
}
return ans;

其他相关实例可以参考如下博客:https://blog.csdn.net/weixin_43736974/article/details/84543970

4. 左/右移运算符

4.1 把数字x2 /2

int n = 5;
n = n << 1; // 左移一位,x2
n = n >> 1; // 右移一位,/2

4.2 Leetcode 190 颠倒二进制位

代码如下:

uint32_t ans;
for (int i = 0; i < 32; ++i) {
	ans <<= 1;
	ans += n & 1;
	n >>= 1;
}
return ans;

4.3 JZ 71 跳台阶扩展问题

这一题经过分析,可以不用动态规划,因为答案ans = 2 ^ (n - 1),因此代码如下:

int jumpFloorII(int number) {
	if (number <= 2) return number;
	while (--number) ans = ans << 1;
	return ans;
}

4.4 注意

4.4.1 符号数

32位里的有符号整数int的最高位为符号位,0为正,1为负。
如果一个二进制数010000…00 (230),这时候再左移一位,即得到100000…00(231),这个数是整数的无穷小,-2147483648。
如果一个二进制数10000…00 (231),这时候再左移一位,即得到00000…00(0),这个数是0。

4.4.2 左/右移超过位数

32位的数,如果左/右移33位,那么实际上只左/右移1位,并且给出如下所示的warning。左/右移n位,实际左/右移n%32位。

warning: left shift count >= width of type [-Wshift-count-overflow]
   n = n << 33;

5. 位运算函数bitset

bitset有很多函数,如下所示:

// 头文件
#include<bitset>
// 初始化
bitset<8> b1(7);  // 把7变成二进制,补齐8位放到b1中:00000111
string s = "010";
bitset<4> b2(s);  // 把010补齐4位放到b2中:0 010
// 元素访问
cout << b1[0];  // 输出为1
cout << b2[0];  // 输出为0
// 函数
b1.count()  // 输出其中1的个数:3
b1.size()  // 输出大小:8
b1.test(0)  // 检查第0位是否为1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值