京东Java面试被问:动态规划的状态压缩和优化技巧

一、什么是状态压缩动态规划

状态压缩DP 是用二进制位位运算 来表示和存储状态的一类DP,主要用于解决状态维度高但每个维度状态数少的问题(通常是布尔值)。

典型特征:

  • 状态中的每一维只有少量可能值(通常是0/1)

  • 状态空间巨大但可压缩

  • 常与排列、选择、覆盖等问题相关

二、状态压缩的核心技巧

1. 二进制位表示法

python

复制

下载

复制代码
# 用整数mask的二进制位表示选择状态
mask = 0b10101  # 表示第0、2、4个元素被选中
n = 5
for i in range(n):
    if mask & (1 << i):  # 检查第i位是否为1
        print(f"元素{i}被选中")

2. 常见位运算操作

python

复制

下载

复制代码
# 基本操作
S = (1 << n) - 1  # n位全1
S & (1 << i)      # 判断第i位是否为1
S | (1 << i)      # 设置第i位为1
S & ~(1 << i)     # 设置第i位为0
S ^ (1 << i)      # 翻转第i位
S & (S - 1)       # 清除最低位的1

三、经典问题模型

1. 旅行商问题(TSP)

python

复制

下载

复制代码
def tsp(n, dist):
    # dp[mask][i]: 访问过mask中的城市,最后在i城市的最小代价
    dp = [[float('inf')] * n for _ in range(1 << n)]
    dp[1][0] = 0  # 从城市0开始
    
    for mask in range(1 << n):
        for i in range(n):
            if not (mask & (1 << i)):
                continue
            for j in range(n):
                if mask & (1 << j):
                    continue
                new_mask = mask | (1 << j)
                dp[new_mask][j] = min(dp[new_mask][j], 
                                      dp[mask][i] + dist[i][j])
    
    # 返回起点并形成环路
    return min(dp[(1 << n) - 1][i] + dist[i][0] 
               for i in range(1, n))

2. 棋盘覆盖问题

python

复制

下载

复制代码
def domino_cover(n, m):
    # dp[i][mask]: 处理到第i行,当前行状态为mask
    dp = [0] * (1 << m)
    dp[(1 << m) - 1] = 1  # 初始状态
    
    for i in range(n):
        new_dp = [0] * (1 << m)
        for mask in range(1 << m):
            if dp[mask] == 0:
                continue
            
            # 递归填充当前行
            def dfs(col, current_mask, next_mask):
                if col == m:
                    new_dp[next_mask] += dp[mask]
                    return
                
                # 如果当前位置已被覆盖
                if mask & (1 << col):
                    dfs(col + 1, current_mask, next_mask)
                else:
                    # 竖放
                    if i < n - 1:
                        dfs(col + 1, current_mask | (1 << col), 
                            next_mask | (1 << col))
                    # 横放
                    if col < m - 1 and not (mask & (1 << (col + 1))):
                        dfs(col + 2, current_mask | (3 << col), next_mask)
            
            dfs(0, 0, 0)
        dp = new_dp
    
    return dp[(1 << m) - 1]

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

四、高级优化技巧

1. 枚举子集优化

python

复制

下载

复制代码
# 高效枚举mask的所有子集
def enumerate_subsets(mask):
    subset = mask
    while subset:
        # 处理子集subset
        process(subset)
        subset = (subset - 1) & mask
    # 处理空集
    process(0)

# 复杂度:O(3^n) 而非 O(4^n)
for mask in range(1 << n):
    subset = mask
    while subset:
        # subset是mask的子集
        complement = mask ^ subset  # mask除去subset的部分
        subset = (subset - 1) & mask

2. 滚动数组优化

python

复制

下载

复制代码
# 二维降一维
dp = [0] * (1 << n)
dp[0] = 1

for i in range(m):
    new_dp = [0] * (1 << n)
    for mask in range(1 << n):
        if dp[mask]:
            # 状态转移
            for new_mask in next_masks(mask):
                new_dp[new_mask] += dp[mask]
    dp = new_dp

3. 预处理合法状态

python

复制

下载

复制代码
# 预处理所有合法状态,减少无效转移
def preprocess_states(n):
    valid_states = []
    for mask in range(1 << n):
        # 检查是否没有相邻的1
        if not (mask & (mask << 1)):
            valid_states.append(mask)
    return valid_states

# 预处理状态间转移
def preprocess_transitions(valid_states):
    transitions = {}
    for s1 in valid_states:
        transitions[s1] = []
        for s2 in valid_states:
            if not (s1 & s2):  # 状态兼容性检查
                transitions[s1].append(s2)
    return transitions

4. Meet-in-the-Middle

python

复制

下载

复制代码
def meet_in_middle(n, target):
    half = n // 2
    left_sums = {}
    
    # 枚举前半部分
    for mask in range(1 << half):
        s = sum(nums[i] for i in range(half) if mask & (1 << i))
        left_sums[s] = left_sums.get(s, 0) + 1
    
    # 枚举后半部分
    result = 0
    for mask in range(1 << (n - half)):
        s = sum(nums[half + i] for i in range(n - half) 
                if mask & (1 << i))
        if target - s in left_sums:
            result += left_sums[target - s]
    
    return result

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

五、实战技巧总结

1. 空间优化优先级

text

复制

下载

复制代码
位运算压缩 → 滚动数组 → 按块处理 → 状态哈希

2. 时间优化策略

  • 预处理合法状态和转移

  • 使用位运算加速状态检查

  • 剪枝无效状态

  • 使用对称性减少状态数

3. 常见易错点

python

复制

下载

复制代码
# 错误:忘记处理空集
subset = mask
while True:  # 这样会跳过空集
    # ...
    if subset == 0:
        break
    subset = (subset - 1) & mask

# 正确:显式处理空集
subset = mask
while subset:
    # ...
    subset = (subset - 1) & mask
# 处理空集
process(0)

六、复杂度分析

问题类型 状态数 常见复杂度 优化目标
普通状态压缩 O(2^n) O(2^n * n) 减少n或优化转移
带约束压缩 O(有效状态) O(状态数²) 预处理合法状态
多维压缩 O(2^(n*m)) 指数级 Meet-in-Middle

七、练习题推荐

  1. 入门:LeetCode 78(子集)、LeetCode 526(优美的排列)

  2. 中等:LeetCode 464(我能赢吗)、LeetCode 691(贴纸拼词)

  3. 进阶:LeetCode 1434(每个人戴不同帽子的方案数)

  4. 竞赛级:POJ 2411(铺砖)、UVa 11825(黑客攻击)

八、最佳实践建议

  1. 先写朴素DP,再压缩:确保逻辑正确后再优化

  2. 状态设计要简洁:能用1位不用2位

  3. 善用位运算函数:提高代码可读性

  4. 注意位序方向:统一从低位到高位或反之

  5. 测试边界情况:空集、全集、最大规模

掌握状态压缩的关键在于多练习、多总结。从经典的TSP和铺砖问题开始,逐步扩展到更复杂的问题,你会逐渐建立状态设计的直觉。记住:好的状态设计能让复杂问题变得简单,而差的设计会让简单问题变得复杂。

相关推荐
索荣荣20 分钟前
Java Session 全面指南:原理、应用与实践(含 Spring Boot 实战)
java·spring boot·后端
你撅嘴真丑20 分钟前
字符环 与 变换的矩阵
算法
向阳开的夏天21 分钟前
麒麟V10源码编译QT5.6.3 (x86 & arm64)
开发语言·qt
Amumu1213826 分钟前
Vue Router(二)
java·前端
早点睡觉好了37 分钟前
重排序 (Re-ranking) 算法详解
算法·ai·rag
gihigo199841 分钟前
基于全局自适应动态规划(GADP)的MATLAB实现方案
算法
Evand J42 分钟前
【MATLAB例程】TOA和TDOA混合定位,适用于二维平面的高精度定位。锚点数量、位置、测量噪声可自行调节
开发语言·matlab·定位·tdoa
萧曵 丶43 分钟前
MySQL 语句书写顺序与执行顺序对比速记表
数据库·mysql
念越1 小时前
数据结构:栈堆
java·开发语言·数据结构
淮北4941 小时前
pip虚拟环境包的问题
开发语言·python·pip