位运算 常见方法总结 算法练习 C++
文章目录
- 1. 基础位运算
- 左移运算符 `<<`
- 右移运算符 `>>`
- 按位与 `&`
- 按位或 `|`
- 按位异或 `^`
- 2. 位运算的优先级
- 3. 给你一个数n确定其的二进制表示中的第x位是0还是1
- 4. 将一个数n的二进制表示的第x位修改成1
- 5. 将一个数n的二进制表示的第x位修改成0
- 6. 位图的思想
- 7. 提取一个数n的二进制表示中的最右侧的1 (lowbit)
- 8. 清除一个数n二进制表示中的最右侧的1
- 9. 异或(^)操作的运算律
- 相关题目练习
- 常见误区总结


注:二进制中我们规定右边是最低位,从右向左记下标为0,1,2,3…
int n = 0 0 1 0 1 1 1 0 1...8 7 6 5 4 3 2 1 0 (下标,因为是int所以32位)
至于为什么要以下标为0开始?
下标从0开始的设计与右移操作相匹配:若要将下标为2的位移动到最低位,只需右移2位即可,操作数与下标完全对应,非常直观。
1. 基础位运算
左移运算符 <<
功能:将一个数的二进制位向左移动指定的位数,右侧空出的位用0填充。
int a = 5; // 二进制: 00000101
int b = a << 2; // 二进制: 00010100,结果为20
移动过程:
00000101 (5) // 原始值
<< 2 // 左移2位
----------------00010100 (20) // 结果值
说明:左移n位相当于在不溢出的情况下乘以2ⁿ,运算效率远高于乘法。
⚠️ 常见误区:若左移导致有符号数的符号位发生变化(如int类型的0b10000000左移1位),会触发溢出,结果不再符合“乘2ⁿ”规则,实际开发中需注意数值范围。
右移运算符 >>
功能:将一个数的二进制位向右移动指定的位数。
- 无符号整数:左侧空出的位用0填充
- 有符号整数:通常采用算术右移,左侧空出的位用符号位填充
int a = 10; // 二进制: 00001010
int b = a >> 2; // 二进制: 00000010,结果为2
移动过程:
00001010 (10) // 原始值
>> 2 // 右移2位
----------------00000010 (2) // 结果值
说明:右移n位相当于在不考虑符号的情况下除以2ⁿ(向下取整)。
按位与 &
运算规则:两个数的对应二进制位都为1时,结果位才为1(有0则0,全1才1)。
int a = 5; // 二进制: 00000101
int b = 3; // 二进制: 00000011
int c = a & b; // 二进制: 00000001,结果为1
运算过程:
00000101 (5)
& 00000011 (3)
----------------00000001 (1)
说明:只有两个对应位同时为1,结果才为1,可用于筛选特定位。
按位或 |
运算规则:两个数的对应二进制位中只要有一个为1,结果位就为1(有1则1,全0才0)。
int a = 5; // 二进制: 00000101
int b = 3; // 二进制: 00000011
int c = a | b; // 二进制: 00000111,结果为7
运算过程:
00000101 (5)
| 00000011 (3)
----------------00000111 (7)
说明:只要有一个对应位为1,结果就为1,可用于设置特定位。
按位异或 ^
运算规则:两个数的对应二进制位不同时,结果位为1;相同时,结果位为0。
int a = 5; // 二进制: 00000101
int b = 3; // 二进制: 00000011
int c = a ^ b; // 二进制: 00000110,结果为6
运算过程:
00000101 (5)
^ 00000011 (3)
----------------00000110 (6)
说明:异或可理解为"无进位相加",相同为0,不同为1,后续会介绍其重要运算律及实战应用。
2. 位运算的优先级
位运算符的优先级从高到低排列如下:
- 按位非
~
(单目运算符,优先级最高) - 左移
<<
、右移>>
- 按位与
&
- 按位异或
^
- 按位或
|
与其他运算符的关系:
- 位运算符优先级低于算术运算符(
+
、-
、*
、/
)和关系运算符(>
、<
、==
) - 位运算符优先级高于逻辑运算符(
&&
、||
)
实用建议:
位运算符的优先级关系复杂,记忆困难。实际编程中,建议使用括号明确指定运算顺序,既避免错误又提高可读性:
int result = (a | b) & (c << 2); // 清晰的运算顺序
3. 给你一个数n确定其的二进制表示中的第x位是0还是1
方法:(n >> x) & 1
步骤解析:
- 右移定位:将n右移x位,使第x位移到最低位(第0位),此时其他位均被移走或填充(不影响最低位);
- 按位筛选:与1(二进制
0b...0001
)按位与,保留最低位(原第x位),清除其他所有位; - 结果判断:最终结果为0 → 原第x位是0;结果为1 → 原第x位是1。
示例:判断n=5(101)的第2位:
4. 将一个数n的二进制表示的第x位修改成1
方法:n = n | (1 << x)
步骤解析:
- 将1左移x位,得到一个只有第x位为1、其余位为0的数(掩码)
- 与n进行按位或运算,使n的第x位变为1,其他位保持不变
示例:将n=5(101)的第1位改为1:
5. 将一个数n的二进制表示的第x位修改成0
方法:n = n & (~(1 << x))
步骤解析:
- 将1左移x位,得到只有第x位为1的数
- 对其取反,得到只有第x位为0、其余位为1的数(掩码)
- 与n进行按位与运算,使n的第x位变为0,其他位保持不变
示例:将n=5(101)的第2位改为0:
6. 位图的思想
我们上面说的几点都是为这一点也就是位图服务的👇
位图的本质就是一个哈希表(数组),不同的是哈希表是将数据存储到数组中方便我们来查找,而位图是用一个变量,其中每个二进制位来记录我们的信息,0/1表示不同信息,所以说用位图来记录我们就会经常用到上面的几个操作。
官方一点就是👇
位图(BitMap)是位运算的重要应用,其本质是用一个二进制位来表示一个数据的状态(0或1),从而极大节省存储空间。
核心思想:
- 传统哈希表用一个元素存储一个数据
- 位图用一个二进制位标记一个数据的存在状态
- 一个32位整数可以表示32个数据的状态
优势:
- 空间效率极高(存储相同数据量,仅需传统方法的1/32空间)
- 操作高效(基于位运算,速度快)
应用场景:
- 海量数据去重
- 数据查找与判重
- 标记状态集合
实现基础:
位图的实现依赖于前面介绍的位操作:
- 判断某数据是否存在 → 使用"判断第x位是0还是1"
- 添加数据 → 使用"将第x位修改为1"
- 删除数据 → 使用"将第x位修改为0"
7. 提取一个数n的二进制表示中的最右侧的1 (lowbit)
方法:n & (-n)
计算机中负数以补码存储,补码规则为:
- 正数的补码 = 其原码(二进制本身);
- 负数的补码 = 对其绝对值的原码“取反(~)”后加1(即
-n = ~n + 1
)。
lowbit的核心逻辑:对n取负后,最右侧的1保持不变,其左侧所有位取反,与原数按位与后,仅保留最右侧的1。
原理:在补码表示中,-n = ~n + 1,会使最右侧的1保持不变,其左侧所有位取反,我们理解了 -n 的含义这个方法就很好理解了。
当我们进行 -n 操作之后,最右侧的1右边部分没有动,左边的部分全部取反,那么我们与原来的数 & ,我们发现右边的部分因为取反全为0,左边的部分没动但都是0。
示例:提取424的二进制表示中的最右侧的1
8. 清除一个数n二进制表示中的最右侧的1
方法:n & (n - 1)
原理:当对 n 减 1 时,会触发“借位”逻辑——从最右侧的 1 开始,该位变为 0,其右侧所有的 0 均变为 1;此时与原数 n 按位与,最右侧的 1 会被“抵消”为 0,其他位保持不变,最终实现“清除最右侧 1”的效果
示例:清除n=106(110101000)最右侧的1:
9. 异或(^)操作的运算律
异或运算具有以下重要性质,在算法设计中非常实用:
(1) 恒等律:a ^ 0 = a
任何数与0异或,结果仍为该数
(2) 归零律:a ^ a = 0
任何数与自身异或,结果为0
(3) 结合律:a ^ b ^ c = a ^ (b ^ c)
异或运算的顺序不影响最终结果
(4) 自反性:a ^ b ^ b = a
连续两次与同一个数异或,结果为原数
应用示例:不使用临时变量交换两个数
int x = 10, y = 20;
x = x ^ y; // x现在是x^y
y = x ^ y; // y = (x^y)^y = x
x = x ^ y; // x = (x^y)^x = y
相关题目练习
以下题目需结合前文知识点实现,避免使用 C++ 内置函数(如 __builtin_popcount
),强化对位运算逻辑的理解:
题目链接 | 核心知识点 | 解题思路提示 |
---|---|---|
力扣 191. 位1的个数 | 清除最右侧1(n & (n-1) ) | 循环执行 n = n & (n-1) ,每执行一次代表清除一个 1,计数加 1,直到 n 变为 0,最终计数即为 1 的个数。 |
力扣 338. 比特位计数 | 清除最右侧1 / 动态规划 | 方法1:对每个数循环清除最右侧 1 计数(基础思路);方法2:动态规划,dp[i] = dp[i & (i-1)] + 1 (利用“i 比 i&(i-1) 多一个 1”的规律,优化效率)。 |
力扣 461. 汉明距离 | 异或 + 位1计数 | 步骤1:对两个数异或(x ^ y ),异或结果中“1 的位置”即为两数二进制不同的位置;步骤2:统计异或结果中 1 的个数,即为汉明距离。 |
力扣 136. 只出现一次的数字 | 异或运算律(归零律 + 恒等律) | 所有数异或:出现两次的数会因 a^a=0 抵消,最终结果即为“只出现一次的数”(0 ^ 唯一数 = 唯一数 )。 |
力扣 260. 只出现一次的数字 III | lowbit + 异或分组 | 步骤1:所有数异或,结果为“两个唯一数的异或值(x^y)”;步骤2:用 lowbit 提取 x^y 最右侧的 1,以此为依据将数组分为两组(该位为 1 的组、为 0 的组),x 和 y 会分属不同组;步骤3:两组分别异或,得到两个唯一数。 |
常见误区总结
- 左移溢出风险:认为“左移 n 位一定等于乘 2ⁿ”,忽略有符号数的溢出问题(如 int 类型
0x80000000
左移 1 位,符号位从 1 变为 0,结果错误),实际使用需确保左移后数值在类型范围内。 - 右移符号位混淆:对负数右移的“补 1”规则不清晰,导致计算结果偏差(如
-10 >> 2 = -3
,而非-2
,需记住“右移向下取整”)。 - 位图位位置计算:用
x % 32
计算位图中的位位置,效率低于x & 31
(位运算无需除法操作),且仅当“每个元素占 2^k 位”时可用(如 64 位元素用x & 63
)。 - 异或交换的局限:无临时变量交换仅适用于“两个不同的变量”,若 x 和 y 指向同一内存地址(如
swap(x, x)
),会导致 x 变为 0(x = x^x = 0
),实际开发需注意变量地址是否相同。