动态规划套路:如何处理多状态问题
关键词:动态规划、多状态问题、状态定义、状态转移、递推关系、股票买卖、打家劫舍
摘要:动态规划(Dynamic Programming, DP)是算法领域的“万能钥匙”,但遇到多状态问题时,很多人会陷入“状态定义混乱”“转移方程写不出”的困境。本文将用“游戏升级”的比喻,结合股票买卖、打家劫舍等经典案例,从0到1拆解多状态问题的处理套路,帮你掌握“定义状态-设计转移-优化实现”的完整方法论。
背景介绍
目的和范围
本文目标是解决动态规划中最让开发者头疼的“多状态问题”。我们将覆盖:
- 多状态问题的本质特征
- 状态定义的3个黄金原则
- 状态转移方程的设计技巧
- 从单状态到多状态的思维升级
- 经典多状态问题(如股票买卖、带限制的打家劫舍)的代码实现
预期读者
- 学过基础动态规划(如斐波那契、爬楼梯)但遇到复杂问题卡壳的开发者
- 准备面试算法岗,需要攻克中等/困难DP题的求职者
- 对算法优化感兴趣,想理解状态机思想的技术爱好者
文档结构概述
本文采用“从生活到代码”的递进式结构:
- 用“游戏角色状态”类比引入多状态概念
- 拆解多状态问题的核心要素(状态定义、转移、初始化)
- 通过股票买卖问题(LeetCode 309)演示完整解题过程
- 总结通用处理套路并给出优化建议
- 扩展至更多实际应用场景
术语表
- 单状态DP:状态仅由一个维度定义(如
dp[n]
表示第n级台阶的走法数) - 多状态DP:状态由多个维度共同定义(如
dp[i][0]
表示第i天不持有股票,dp[i][1]
表示持有股票) - 状态转移:从旧状态推导新状态的规则(如“今天持有股票”可能由“昨天持有”或“昨天不持有但今天买入”转移而来)
- 状态压缩:通过观察状态转移规律,用变量代替数组降低空间复杂度
核心概念与联系
故事引入:游戏角色的“状态管理”
假设你在玩一款RPG游戏,角色有3种状态:
- 健康(无Buff)
- 中毒(每回合掉血)
- 隐身(不会被攻击)
你需要根据当前状态决定下一步行动:
- 如果当前健康:可以选择攻击(保持健康)或使用隐身药(转为隐身)
- 如果当前中毒:必须喝解药(转为健康)或继续中毒(掉血)
- 如果当前隐身:可以攻击(显形,转为健康)或保持隐身
这里的关键是:角色的下一步状态由当前状态和选择的动作共同决定。动态规划的多状态问题,本质上就是在模拟这种“状态-动作-新状态”的转移过程。
核心概念解释(像给小学生讲故事一样)
概念一:状态(State)
状态是“当前时刻的关键特征”。就像游戏角色的“健康/中毒/隐身”,动态规划中的状态是对问题当前阶段的高度抽象。
例子:计算“第i天的最大收益”时,可能需要记录两个状态:持有股票
和不持有股票
(因为是否持有会直接影响下一步操作)。
概念二:状态转移(State Transition)
状态转移是“从当前状态到下一个状态的规则”。就像游戏中“隐身状态使用攻击会显形”,动态规划中需要明确“在状态A时,通过什么操作可以转移到状态B”。
例子:如果今天持有股票(状态1),那么昨天可能有两种情况:
- 昨天就持有(今天没操作,直接继承状态)
- 昨天不持有(今天买入,从状态0转移到状态1)
概念三:多状态问题(Multi-state Problem)
多状态问题是“需要同时跟踪多个状态才能解决的问题”。就像煮奶茶需要同时关注“茶叶浓度”和“牛奶温度”,有些问题仅用单状态(如总收益)无法准确描述,必须用多个状态共同记录关键信息。
例子:股票买卖的“冷冻期”问题(卖出后第二天不能买入),需要额外记录“是否处于冷冻期”的状态。
核心概念之间的关系(用小学生能理解的比喻)
想象你在经营一家奶茶店,需要同时跟踪三个状态:
- 状态A:当前有0杯奶茶
- 状态B:当前有1杯奶茶
- 状态C:当前有2杯奶茶(最多存2杯)
状态与转移的关系:
- 状态A可以通过“制作1杯”转移到状态B,或“制作2杯”转移到状态C
- 状态B可以通过“卖出1杯”回到状态A,或“再制作1杯”转移到状态C
- 状态C只能通过“卖出1杯”转移到状态B,或“卖出2杯”回到状态A
这里的每个状态(A/B/C)都是当前的“奶茶库存”,转移规则(制作/卖出)决定了如何从一个状态到另一个状态。动态规划的多状态问题,就是用类似的方式管理问题的关键状态。
核心概念原理和架构的文本示意图
多状态DP的核心架构可以概括为:
状态定义 → 初始化边界 → 状态转移方程 → 遍历计算 → 结果提取
其中:
- 状态定义:用多维数组
dp[i][s]
表示第i阶段的状态s(s是状态编号) - 初始化:确定初始阶段(i=0)各状态的值
- 转移方程:根据当前阶段的状态s,推导出下一阶段各状态的值
- 遍历顺序:按阶段顺序(如天数、物品数)依次计算每个阶段的状态