目录
前言
先回顾常用的位运算
1:给一个数 n ,确定它的二进制表示中的第x位是0 还是 1
2:将一个数 n 的二进制表示的第x 位修改成 1
3:将一个数 n 的二进制表示的第 x位修改成 0
4:与位图联系
5:提取一个数(n)二进制表示中最右侧的 1(重要)
6:干掉一个数(n)二进制表示中最右侧的 1(重要)
7:要考虑位运算的优先级吗?(了解)
8:异或(^)运算的运算律(重要)
9:对于~的妙用实现++/--(知道有就行)
前言
在这篇文章中,我将总结我所知道的所有位运算的独特之处。这些妙用非常重要,运用得当可以显著减少代码量,并提高时间和空间的效率。
位运算是计算机科学中的一种基础操作,其高效性和简洁性使其在许多场景中都能发挥重要作用。掌握位运算的技巧,不仅能够优化算法,还能提升程序的整体性能。
接下来,我将介绍几种常见的位运算技巧及其应用场景。
先回顾常用的位运算
符号 | 描述 | 运算规则 |
---|---|---|
& | 按位与 | 两个位都为1时,结果才为1 |
| | 按位或 | 两个位都为0时,结果才为0 |
^ | 按位异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,高位补0或符号位补齐 |
记忆技巧:
符号 | 记忆技巧 |
& | 全一则一 |
| | 有一则一 |
^ | 有一则一,全一则零 / 相同为零,相异为一 / 无进制位相加 |
1:给一个数 n ,确定它的二进制表示中的第x位是0 还是 1
需要用到的运算符:>> &
操作:
(n >> x) & 1
如果结果是1,则第x位为1
如果结果为0,则第x位位0
大致解释:
比如n的二进制为01101101, x为2
注意:
第0为为1!!!是从下标为0开始数的!!!
2:将一个数 n 的二进制表示的第x 位修改成 1
需要用到的运算符:>> |
操作:
n |= (1 << x)
大致解释:
比如n的二进制为01101101, x为1
3:将一个数 n 的二进制表示的第 x位修改成 0
需要用到的运算符:>> & ~
操作:
n &= (~(1 << x))
大致解释:
比如n的二进制为01101101, x为2
4:与位图联系
其实上面这三种妙解都是可以与位图联系在一起,比方说根据上面这三种方法
在添加上位图的思想,就可以解决下面这三道题。
191. 位1的个数 - 力扣(LeetCode)
338. 比特位计数 - 力扣(LeetCode)
461. 汉明距离 - 力扣(LeetCode)
使用位图的思想,并不是使用bitset,而是使用其思想,比如下面使用位图的思想解决338。
class Solution {
public:vector<int> countBits(int n) {vector<int> v;for(int i=0;i<=n;i++){int t=i;int ret=0;while(t){t&=(t-1);ret++;}v.push_back(ret);}return v;}
};
5:提取一个数(n)二进制表示中最右侧的 1(重要)
需要用到的运算符:&
操作:
n & (-n)
其中的 -n 就是,将最右侧的 1,左边的区域全部变成相反。
需要注意的是,不要想当然去写 -n 的二进制编码,负数的二进制编码,用其正数去求反码,补码。补码才是二进制编码。
大致解释:
比如n为0110101000
6:干掉一个数(n)二进制表示中最右侧的 1(重要)
需要用到的运算符:&
操作:
n &= (n-1)
其中的n-1,就是将n最右侧的 1,其右边的区域(包含1)全部变成相反
大致解释:
7:要考虑位运算的优先级吗?(了解)
答案是,其实不需要考虑的,对于优先级其中由很多,记忆起来是不容易的,那么对于优先级的问题解决,我们只需要对应的位置添加上括号即可。
所以
能加括号就多加括号,拿不准的优先级问题,不需要考虑,直接添加括号解决问题。
8:异或(^)运算的运算律(重要)
对于异或(^)就没有太多要用的了
就有三个
这里为了印象深刻点,就多举例一些常用的异或案例
利用或操作 |
和空格将英文字符转换为小写
('a' | ' ') = 'a'
('A' | ' ') = 'a'
利用与操作 &
和下划线将英文字符转换为大写
('b' & '_') = 'B'
('B' & '_') = 'B'
利用异或操作 ^
和空格进行英文字符大小写互换
('d' ^ ' ') = 'D'
('D' ^ ' ') = 'd'
以上操作能够产生奇特效果的原因在于 ASCII 编码。字符其实就是数字,恰巧这些字符对应的数字通过位运算就能得到正确的结果,有兴趣的读者可以查 ASCII 码表自己算算,本文就不展开讲了。
判断两个数是否异号
int x = -1, y = 2;
bool f = ((x ^ y) < 0); // trueint x = 3, y = 2;
bool f = ((x ^ y) < 0); // false
这个技巧还是很实用的,利用的是补码编码的符号位。如果不用位运算来判断是否异号,需要使用 if else 分支,还挺麻烦的。读者可能想利用乘积或者商来判断两个数是否异号,但是这种处理方式可能造成溢出,从而出现错误。
交换两个数
void swap(int &a, int &b)
{ a ^= b ^= a ^= b;
}
那么给出两个题,使用异或来解决吧
136. 只出现一次的数字 - 力扣(LeetCode)
260. 只出现一次的数字 III - 力扣(LeetCode)
9:对于~的妙用实现++/--(知道有就行)
- 加一
int n = 1; n = -~n; // 现在 n = 2
- 减一
int n = 2; n = ~-n; // 现在 n = 1
其实这俩没什么实际作用,大家了解了解乐呵一下就行,知道有这个就行。