Skip to content

位运算基础与常用技巧

计算机的世界是二进制的。不管是数字、文字还是图片,最终都以 0 和 1 的形式存储和处理。

位运算直接操作这些二进制位,绕过了高级抽象,因此速度极快。很多看似复杂的问题,用位运算可以一行代码解决。这也是为什么位运算在算法面试中经常出现——它考察的是对计算机底层的理解。

二进制表示

在深入位运算之前,先确保你理解二进制。

十进制 → 二进制

十进制 5 的二进制表示:
5 = 4 + 1 = 2² + 2⁰ = 0101(4位表示)

十进制 13 的二进制表示:
13 = 8 + 4 + 1 = 2³ + 2² + 2⁰ = 1101

位编号(从右到左,从 0 开始):

二进制:1 1 0 1
位编号:3 2 1 0
        ↑     ↑
       最高位  最低位

在 JavaScript 中查看二进制:

javascript
// 十进制 → 二进制字符串
(13).toString(2);  // "1101"
(5).toString(2);   // "101"

// 二进制 → 十进制
parseInt("1101", 2);  // 13

// 直接使用二进制字面量
const a = 0b1101;  // 13
const b = 0b0101;  // 5

六种位运算符

JavaScript 提供了六种位运算符:

1. 按位与 AND(&

两位都为 1 才是 1,否则为 0。

  1 1 0 1  (13)
& 0 1 0 1  (5)
─────────
  0 1 0 1  (5)
javascript
13 & 5  // 5

应用:检查某位是否为 1、清除某些位、取模运算。

2. 按位或 OR(|

有一位为 1 就是 1,都是 0 才是 0。

  1 1 0 1  (13)
| 0 1 0 1  (5)
─────────
  1 1 0 1  (13)
javascript
13 | 5  // 13

应用:设置某位为 1、合并标志位。

3. 按位异或 XOR(^

两位不同为 1,相同为 0。

  1 1 0 1  (13)
^ 0 1 0 1  (5)
─────────
  1 0 0 0  (8)
javascript
13 ^ 5  // 8

应用:交换两数、找唯一数、加密。

4. 按位取反 NOT(~

0 变 1,1 变 0。

javascript
~5   // -6
// 原理:JavaScript 使用 32 位有符号整数
// 5 = 00000000 00000000 00000000 00000101
// ~5 = 11111111 11111111 11111111 11111010 = -6(补码表示)

规律~n = -(n + 1)

javascript
~0   // -1
~5   // -6
~(-1) // 0

5. 左移(<<

所有位向左移动,右边补 0。

5 << 1:
  0 1 0 1  (5)
← 移动 1 位
  1 0 1 0  (10)
javascript
5 << 1   // 10  (5 × 2)
5 << 2   // 20  (5 × 4)
5 << 3   // 40  (5 × 8)

规律n << k 等于 n × 2^k

6. 右移(>>

所有位向右移动,左边补符号位(算术右移)。

13 >> 1:
  1 1 0 1  (13)
  移动 1 位 →
  0 1 1 0  (6)
javascript
13 >> 1   // 6   (13 / 2 向下取整)
13 >> 2   // 3   (13 / 4 向下取整)
-8 >> 1   // -4  (负数右移,左边补 1)

规律n >> k 等于 Math.floor(n / 2^k)

无符号右移 >>>:左边补 0(不考虑符号位)

javascript
-8 >> 1   // -4  (算术右移)
-8 >>> 1  // 2147483644  (无符号右移)

常用位运算技巧

这些技巧在算法题中反复出现,值得记忆。

1. 判断奇偶

javascript
// 奇数的最低位是 1,偶数是 0
n & 1 === 1  // 奇数
n & 1 === 0  // 偶数

// 比 n % 2 更快

2. 乘除 2 的幂

javascript
n << 1    // n × 2
n >> 1    // n / 2(向下取整)
n << k    // n × 2^k
n >> k    // n / 2^k(向下取整)

// 注意:仅适用于正整数

3. 交换两数(不用临时变量)

javascript
a = a ^ b;
b = a ^ b;  // b = (a^b) ^ b = a
a = a ^ b;  // a = (a^b) ^ a = b

// 实际中不建议用,可读性差且可能更慢

4. 清除最低位的 1

javascript
n & (n - 1)  // 把最右边的 1 变成 0

// 例子:
// n     = 1100 (12)
// n-1   = 1011 (11)
// n&(n-1) = 1000 (8)  最右边的 1 被清除

应用:统计 1 的个数、判断 2 的幂。

5. 获取最低位的 1

javascript
n & (-n)  // 只保留最右边的 1

// 例子:
// n    = 0 0 1 1 0 0 (12)
// -n   = 1 1 0 1 0 0 (补码)
// n&(-n) = 0 0 0 1 0 0 (4)

原理:负数用补码表示,-n = ~n + 1

6. 判断是否是 2 的幂

javascript
n > 0 && (n & (n - 1)) === 0

// 2 的幂只有一个 1
// 1 = 0001, 2 = 0010, 4 = 0100, 8 = 1000
// n & (n-1) 清除这唯一的 1 后变成 0

7. 获取/设置/清除某一位

javascript
// 获取第 k 位(k 从 0 开始)
(n >> k) & 1

// 设置第 k 位为 1
n | (1 << k)

// 清除第 k 位(设为 0)
n & ~(1 << k)

// 翻转第 k 位
n ^ (1 << k)

示例

javascript
let n = 13;  // 1101

// 获取第 2 位
(n >> 2) & 1  // 1

// 设置第 1 位为 1
n | (1 << 1)  // 1111 = 15

// 清除第 2 位
n & ~(1 << 2)  // 1001 = 9

// 翻转第 0 位
n ^ (1 << 0)  // 1100 = 12

异或的神奇性质

异或(XOR)是位运算中最有用的,它有以下性质:

  1. 自反性a ^ a = 0
  2. 零元素a ^ 0 = a
  3. 交换律a ^ b = b ^ a
  4. 结合律(a ^ b) ^ c = a ^ (b ^ c)

这些性质导出一个重要结论:

一组数连续异或,成对的数会抵消,只留下出现奇数次的数。

javascript
a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b

这是「只出现一次的数字」系列问题的核心。

javascript
// 数组 [2, 3, 2],找只出现一次的数
2 ^ 3 ^ 2 = (2 ^ 2) ^ 3 = 0 ^ 3 = 3

位运算的注意事项

1. JavaScript 的 32 位限制

JavaScript 位运算将操作数转换为 32 位有符号整数

javascript
// 超过 32 位的数会被截断
2 ** 31 | 0  // -2147483648(溢出)

// 浮点数会被截断为整数
5.7 | 0  // 5

2. 优先级问题

位运算优先级低于比较运算符,容易出错:

javascript
// 错误:先计算 1 === 1,结果是 true,再与 n 做位与
n & 1 === 1  // 实际是 n & (1 === 1)

// 正确
(n & 1) === 1

3. 负数的位运算

JavaScript 使用补码表示负数。对负数进行位运算要格外小心:

javascript
-1 >> 1   // -1(算术右移,左边补 1)
-1 >>> 1  // 2147483647(无符号右移,变成很大的正数)

位运算 vs 常规运算

操作位运算常规运算备注
判断奇偶n & 1n % 2位运算更快
乘 2n << 1n * 2现代编译器会优化
除 2n >> 1Math.floor(n/2)正数用位运算更快
交换异或法临时变量临时变量更清晰

在算法题中,位运算的主要优势不是性能,而是提供了独特的解题思路

本章小结

  1. 六种运算符&(与)、|(或)、^(异或)、~(取反)、<<(左移)、>>(右移)
  2. 常用技巧:判断奇偶、乘除 2、清除/获取最低位的 1、判断 2 的幂
  3. 异或性质:自反性 a^a=0,零元素 a^0=a,交换律,结合律
  4. 注意事项:32 位限制、优先级、负数补码

下一章我们将学习位运算的应用场景,看看如何用位运算解决实际问题。

位运算基础与常用技巧 has loaded