位运算小结
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