二进制枚举算法
二进制枚举算法详解
一、什么是二进制枚举算法
二进制枚举算法是一种 通过整数的二进制表示来枚举所有子集 的方法。适用于处理组合问题,特别是与“取或不取”某个元素相关的场景。
📌 原理说明
设有一个集合 A = [a₀, a₁, ..., aₙ₋₁],我们要枚举它的所有子集。集合中每个元素有两种状态:选 or 不选。因此总共有 2ⁿ 种子集。
我们可以用一个 n 位的 二进制数 来表示一个子集,例如:
| 二进制位 | 子集含义 | 
|---|---|
| 0 | 不选该元素 | 
| 1 | 选该元素 | 
比如对于集合 [1, 2, 3]:
- 000→ 空集- {}
- 101→ 选第1和第3个元素 →- {1, 3}
- 111→ 全选 →- {1, 2, 3}
✅ 实现步骤
- 确定元素个数:假设为 n
- 循环从 0到2ⁿ - 1,每个数的二进制表示对应一个子集
- 用位运算判断每一位是否为 1
- 构造当前子集
- 输出或处理这个子集
二、图示理解
假设集合为 [a, b, c],其子集枚举如下(每一行是一个整数的二进制表示):
css复制编辑i(二进制)  是否选a  是否选b  是否选c   子集
000           0        0        0       {}
001           0        0        1       {c}
010           0        1        0       {b}
011           0        1        1       {b, c}
100           1        0        0       {a}
101           1        0        1       {a, c}
110           1        1        0       {a, b}
111           1        1        1       {a, b, c}
三、代码实现(Go 示例)
✅ Go 实现:
go复制编辑package mainimport "fmt"func binaryEnum(arr []int) {n := len(arr)for i := 0; i < (1 << n); i++ {var subset []intfor j := 0; j < n; j++ {if (i>>j)&1 == 1 {subset = append(subset, arr[j])}}fmt.Println(subset)}
}func main() {arr := []int{1, 2, 3}binaryEnum(arr)
}
四、二进制枚举的优势
| 优势 | 说明 | 
|---|---|
| ✅ 简洁 | 使用整数和位运算代替递归或多重循环 | 
| ✅ 效率高 | 位运算在底层执行非常快 | 
| ✅ 结构清晰 | 子集状态一一对应二进制 | 
| ✅ 易扩展 | 可结合子集统计、位掩码优化 | 
五、适用场景
✅ 小规模子集枚举问题
当元素个数不大(通常 n ≤ 20),可暴力枚举 2ⁿ 种组合。
✅ 集合操作(求交并差)
集合可用一个二进制数表示,如 set = 0b10101 表示选了第1、第3、第5个元素。
✅ 状态压缩动态规划(Bitmask DP)
如 TSP(旅行商问题)、集合覆盖、最短Hamilton路径等问题。
✅ 穷举判断满足某种条件的子集
如:求所有子集中和为某个目标值的个数。
六、进阶优化技巧
- 提前剪枝:遇到明显不符合要求的中间状态时立即跳过
- 记录中间状态:用 map 缓存某些子集结果
- 结合排列枚举:在每个子集内部再对元素进行排列
七、常见误区
| 误区 | 正确做法 | 
|---|---|
| n很大时也使用二进制枚举 | 超过 n = 25后就非常慢,应换动态规划或回溯 | 
| 忘记位移方向 | (i >> j)是向右移第j位 | 
| 忽视索引越界 | 注意 arr[j]不要超过范围 | 
| 忽略子集顺序 | 二进制枚举默认顺序由低到高 | 
八、小练习建议
- 给定整数数组,输出所有子集
- 求所有子集中元素和为 target的子集个数
- 二进制枚举所有满足异或为 0 的子集
- 用二进制枚举解决集合划分问题(如背包)
