状态压缩动态规划(State DP)在LeetCode-Py项目中的应用详解
概述
状态压缩动态规划(State DP)是一种针对小规模数据问题的优化动态规划方法,它通过二进制数的特性来高效表示和操作状态集合。本文将深入探讨状态压缩DP的核心概念、实现技巧以及在LeetCode-Py项目中的典型应用。
状态压缩DP基础
二进制表示集合
状态压缩DP的核心思想是使用二进制数的每一位来表示集合中元素的选取状态:
- 1表示选取该元素
- 0表示不选取该元素
对于一个长度为n的集合,可以用一个n位二进制数表示其所有子集(共2ⁿ种可能)。
示例: 集合S = {5,4,3,2,1}(n=5):
- 11111表示选取全部元素
- 10101表示选取第1、3、5个元素
- 01001表示选取第1、4个元素
状态转移方式
状态压缩DP主要有两种状态转移方式:
- 枚举子集:从当前状态出发,枚举所有可能的子集进行转移
- 枚举超集:从当前状态出发,枚举所有可能的超集进行转移
其中枚举子集的方式更为常用。
适用条件
状态压缩DP适用于:
- 问题规模较小(通常n ≤ 20)
- 需要表示集合的选取状态
- 状态转移与集合操作相关
当n过大时,状态数量呈指数级增长(2ⁿ),可能导致算法超时。
常用位运算操作
在状态压缩DP中,我们需要熟练掌握以下位运算操作:
| 操作描述 | 位运算表达式 | |---------|------------| | 总状态数量 | 1 << n
| | 添加第i个元素 | A | (1 << i)
| | 删除第i个元素 | A & ~(1 << i)
| | 检查第i个元素 | A & (1 << i)
或 (A >> i) & 1
| | 清空集合 | A = 0
| | 全选集合 | A = (1 << n) - 1
| | 集合补集 | A ^ ((1 << n) - 1)
| | 集合并集 | A | B
| | 集合交集 | A & B
|
枚举子集技巧:
subA = A
while subA > 0:
# 处理子集subA
subA = (subA - 1) & A
典型应用解析
应用1:两个数组最小的异或值之和
问题描述: 给定两个长度相同的整数数组nums1和nums2,可以重新排列nums2,求使两数组对应元素异或值之和最小的排列方式。
状态压缩DP解法:
-
状态定义:
dp[state]
表示nums2选取状态为state时的最小异或和- state的二进制位表示nums2中元素的选取情况
-
状态转移:
for state in range(1 << n): cnt = bin(state).count('1') for i in range(n): if (state >> i) & 1: dp[state] = min(dp[state], dp[state ^ (1 << i)] + (nums1[cnt-1] ^ nums2[i]))
-
复杂度分析:
- 时间复杂度:O(2ⁿ × n)
- 空间复杂度:O(2ⁿ)
应用2:数组的最大与和
问题描述: 将数组nums分配到numSlots个篮子中,每个篮子最多放2个数字,求数字与篮子编号按位与结果之和的最大值。
状态压缩DP解法:
-
问题转化:
- 将每个篮子视为两个槽位,共2×numSlots个槽位
- 每个槽位最多放1个数字
-
状态定义:
dp[state]
表示槽位选取状态为state时的最大与和- state的二进制位表示各槽位的使用情况
-
状态转移:
for state in range(1 << (2*numSlots)): cnt = bin(state).count('1') if cnt > len(nums): continue for i in range(2*numSlots): if (state >> i) & 1: dp[state] = max(dp[state], dp[state ^ (1 << i)] + ((i//2+1) & nums[cnt-1]))
-
复杂度分析:
- 时间复杂度:O(2^(2m) × 2m),其中m=numSlots
- 空间复杂度:O(2^(2m))
总结
状态压缩DP是一种强大的算法技巧,特别适合解决小规模集合的组合优化问题。通过本文的讲解,我们了解了:
- 如何使用二进制数表示集合状态
- 常用的位运算操作集合的方法
- 两种典型问题的状态压缩DP解法
掌握状态压缩DP需要:
- 熟悉位运算操作
- 理解状态表示和转移的含义
- 根据问题特点设计合适的状态定义
对于LeetCode上的相关问题,当看到数据规模n≤20时,可以考虑使用状态压缩DP来解决。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考