一、位运算符及其优先级
我们知道,计算机中的数在内存中都是以二进制形式进行存储的 ,而位运算就是直接对整数在内存中的二进制位进行操作,因此其执行效率非常高,在程序中尽量使用位运算进行操作,这会大大提高程序的性能。
那么,涉及位运算的运算符如下表所示:
注意:参与位运算的对象只能是整型数据(int, unsigned, char),不能为实型
异或运算符的运算律
- 异或0:a^0 = a;
- 消消乐:a^a = 0;
- 交换律和结合律:abc = acb; abc = a(bc);
位运算符的优先级
优先级需要弄清楚,如果不太清楚可以加小括号确保是想要的运算顺序,这里只是相对优先级,即只是和一些常用的算术运算符做比较。
二、位运算的应用
2.1 判断位
给一个数n,确定它的二进制表示中的第x位是0还是1
算法:(n>>x)&1
原理:按位与的运算规则——有0则0,遇1不变
举例:判断第3位
- 11001010 //n
00011001 //n>>3
00000001 //&1
00000001 //结果
2.2 打开位
将一个数n二进制表示的第x位修改成1
算法:n |= (1<<x)
原理:按位或的运算规则——有1则1,遇0不变
举例:打开第4位
- 11001010 //目标
00010000 //1<<4
11011010 //|= 得结果
2.3 关闭位
将一个数n二进制表示的第x位修改成0
算法:n &= ~(1<<x)
原理:按位与的运算规则——有0则0,遇1不变
举例:关闭第7位
- 11001000 //目标
10000000 //1<<7
01111111 //~(1<<7)
01001000 //&= 得结果
2.4 转置位
将一个数n二进制表示的第x位转置(0变1,1变0)
算法:n ^= (1<<x)
原理:按位异或的运算规则——0变1,1变0
举例:转置第4位
- 11011010 //目标
00010000 //1<<4
11001010 //^= 得结果
2.5 位图
定义和特性:位图是一种数据结构,它由一个二进制位数组或者比特数组组成,每个位(bit)只能存储 0 或 1。位图通常被用来表示大量整数的集合,通过位的状态来表示该整数是否出现在集合中。位图支持高效的位操作(如按位与、按位或、按位异或等),适合用于高效地存储和操作大量的布尔型数据。
实现方式:位图可以被实现为一个数组,其中每个元素都是一个字节,而每个字节又包含8个位。对于很大的位图,可以使用多个数组进行分段存储。位图通常不仅仅用于表示整数的集合,还可以用于标记某个特定值是否存在。
详细内容请阅读文章:【高阶数据结构】哈希的其他应用 {位图的介绍及实现,位图的组合应用;布隆过滤器的介绍及实现,布隆过滤器的应用;哈希切分}-CSDN博客
2.6 get lowbit
提取一个数n二进制表示中最低位的1
算法:n & -n
原理:-n(按位取反再加1)将最低位的1左边的位(不包括logbit位)全部转置,左边转置后按位与得0,右边相同位按位与不变。
举例:
- 11011000 //目标
00101000 //-n
00001000 //& 得结果
2.7 close lowbit
关闭一个数n二进制表示中最低位的1
算法:n & (n-1)
原理:n-1 将最低位的1右边的位(包括logbit位)全部转置,左边相同位按位与不变,右边转置后按位与得0。
举例:
- 11011000 //目标
11010111 //n-1
11010000 //& 得结果
2.8 其他
-
位运算实现乘除法:将a左移一位实现*2,将a右移一位实现/2。
- a < < n ≡ a ∗ 2^n
- a > > n ≡ a / 2^n
-
统计二进制数中有多少个1:
int func(int x){int count=0;while (x){count++;x=x&(x-1); //将lowbit关闭}return count; }
-
字符的大小写转换:对应大小写字母ASSIC码的二进制数只有第5位不同(大写为0,小写为1),因此可以通过操作第5位实现大小的相互转换
- 字符小写转大写:ch&=~32;
-
字符大写转小写:ch|=32;
-
字符大小写互换:ch^=32;
-
交换两变量的值:a=a^b; b=a^b; a=a^b;(异或运算符的运算律:结合律、消消乐)
三、相关编程题
3.1 位1的个数
题目链接
191. 位1的个数 - 力扣(LeetCode)
题目描述
算法原理
不断的close lowbit,每关闭一个1count就++,直到将所有的1都关闭,量的值变为0。
编写代码
class Solution {
public:int hammingWeight(int n) {int count = 0;while(n>0){n &= (n-1);++count;}return count;}
};
3.2 比特位计数
题目链接
338. 比特位计数 - 力扣(LeetCode)
题目描述
算法原理
同上 close lowbit
编写代码
class Solution {
public:vector<int> countBits(int n) {vector<int> ans(n+1, 0);for(int i = 0; i <= n; ++i){int count = 0;int k = i;while(k>0){++count;k &= k-1;}ans[i] = count;}return ans;}
};
3.3 汉明距离
题目链接
461. 汉明距离 - 力扣(LeetCode)
题目描述
算法原理
异或运算:相异为1,相同为0。tmp = x^y将x和y二进制表示中不同的位置置为1,相同的位置置为0。再统计tmp中位1的个数即可,原理同上 close lowbit
编写代码
class Solution {
public:int hammingDistance(int x, int y) {int tmp = x ^ y;int count = 0;while(tmp>0){++count;tmp &= tmp-1;}return count;}
};
3.4 只出现一次的数字
题目链接
136. 只出现一次的数字 - 力扣(LeetCode)
题目描述
算法原理
异或运算的运算律:消消乐
编写代码
class Solution {
public:int singleNumber(vector<int>& nums) {int ret = 0;for(auto e : nums){ret ^= e;}return ret;}
};
3.5 只出现一次的数Ⅲ
题目链接
260. 只出现一次的数字 III - 力扣(LeetCode)
题目描述
算法原理
- 将所有的数按位异或到tmp,最终其实就是只出现一次的两个数的异或结果,即将两个数不同的位置置为1。
- get lowbit将tmp的最低位1取出(还是存入tmp)。注意有符号数需要特殊处理INT_MIN(符号位为1,其他位为0),有符号数取反的位操作是:符号位不变,其他位取反再+1。INT_MIN取反会溢出。实际上INT_MIN的lowbit就是它本身。
- 再次遍历数组,判断所有数的tmp位(异或结果的最低位1),将只出现一次的两个数分到不同的组中进行异或,最终就能分别得到这两个数了。
编写代码
class Solution {
public:vector<int> singleNumber(vector<int>& nums) {int tmp = 0;for(auto e : nums){tmp ^= e;}tmp = tmp==INT_MIN? tmp:tmp&(-tmp); //get lowbitvector<int> ret(2, 0);for(auto e : nums){if(e & tmp){ret[0] ^= e;}else{ret[1] ^= e;}}return ret;}
};
3.6 判定字符是否唯一
题目链接
面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)
题目描述
算法原理
编写代码
//用一个int数据充当位图
class Solution {
public:bool isUnique(string astr) {// 利用鸽巢原理进行优化if(astr.size() > 26)return false;// 用位图判断一个字符是否在集合中int bitmap = 0;for(auto e : astr){int tmp = 1 << (e-'a');if(!(bitmap & tmp)){bitmap |= tmp;}else{return false;}}return true;}
};//用STL的bitset数据结构
class Solution {
public:bool isUnique(string astr) {if(astr.size() > 26)return false;bitset<26> bs;for(auto e : astr){int x = e - 'a'; //位图的第x位if(!bs.test(x)){bs.set(x);}else{return false;}}return true;}
};
3.7 丢失的数字
题目链接
268. 丢失的数字 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:int missingNumber(vector<int>& nums) {int ret = 0;int i = 0;for(; i < nums.size(); ++i){ret ^= nums[i];ret ^= i;}ret ^= i;return ret;}
};
3.8 两整数之和
题目链接
371. 两整数之和 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:int getSum(int a, int b) {int bitsum = a ^ b; //无进位相加的结果int bitcarry = (a & b)<<1; //进位的结果while(bitcarry != 0) //当不再有进位时退出循环{a = bitsum;b = bitcarry;bitsum = a ^ b;bitcarry = (a & b)<<1;}return bitsum;}
};
3.9 只出现一次的数字Ⅱ
题目链接
137. 只出现一次的数字 II - 力扣(LeetCode)
题目描述
算法原理
依次确定每一个二进制位
为了方便叙述,我们称「只出现了一次的元素」为「答案」。
由于数组中的元素都在 int(即 32 位整数)范围内,因此我们可以依次计算答案的每一个二进制位是 0 还是 1。
具体地,考虑答案的第 i 个二进制位(i 从 0 开始编号),它可能为 0 或 1。对于数组中非答案的元素,每一个元素都出现了 3 次,对应着第 i 个二进制位的 3 个 0 或 3 个 1,无论是哪一种情况,它们的和都是 3 的倍数(即和为 0 或 3)。因此:
答案的第 i 个二进制位就是数组中所有元素的第 i 个二进制位之和除以 3 的余数。
这样一来,对于数组中的每一个元素 x,我们使用位运算 (x >> i) & 1得到 x 的第 i 个二进制位,并将它们相加再对 3 取余,得到的结果一定为 0 或 1,即为答案的第 i 个二进制位。
编写代码
class Solution {
public:int singleNumber(vector<int>& nums) {int ret = 0;for(size_t i = 0; i < 32; ++i){int bitsum = 0;for(auto e : nums){bitsum+=(e>>i)&1;}bitsum %= 3;ret |= (bitsum<<i);}return ret;}
};
3.10 消失的两个数字
题目链接
面试题 17.19. 消失的两个数字 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:vector<int> missingTwo(vector<int>& nums) {int tmp = 0;//异或到一起for(auto e : nums) tmp ^= e; //原数组for(int i = 1; i<=nums.size()+2; ++i) tmp ^= i; //全数组//此时的tmp种存放的是消失的两个数字的异或结果int lowbit = tmp==INT_MIN? tmp:tmp&(-tmp);vector<int> ret(2, 0);//划分成两类异或for(auto e : nums) //原数组if(e & lowbit) ret[0] ^= e;else ret[1] ^= e;for(int i = 1; i<=nums.size()+2; ++i) //全数组if(i & lowbit) ret[0] ^= i;else ret[1] ^= i; return ret;}
};