状态机动态规划(DP)全解
状态机动态规划(DP)是解决复杂序列决策问题的重要方法,尤其适用于互联网大厂的算法笔试和面试。下面我将为你详细解析状态机DP的核心概念、典型题目及多语言实现。
🔁 状态机动态规划(DP)详解与典型应用
状态机DP通过将问题抽象为多个状态及状态间的转移关系来建模,特别适合描述具有多种状态和状态依赖的决策过程。它通过维护多个状态数组(DP Table)来记录不同状态下的最优解,是处理复杂序列问题的有效手段。
📊 一、状态机DP核心思想
状态机DP将动态规划问题中的每个步骤分解为多个可能的状态,并通过状态转移方程来描述这些状态之间的关系。其本质是当问题具有有限个状态,且状态之间的转移形成拓扑图时,可以利用动态规划在线性时间内解决问题。
基本要素:
- 状态定义:明确问题中的不同状态(如持有股票/未持有股票)
- 状态转移:定义状态之间如何转换(如买入操作导致从未持有变为持有)
- 初始状态:确定起始时刻各个状态的值
- 目标状态:从最终状态中选择最优解
🛠️ 二、状态机DP解题框架
状态机DP的解题过程可以归纳为以下步骤,这些步骤构成了解决此类问题的系统方法:
| 步骤 | 说明 | 关键点 |
|---|---|---|
| 1. 识别子状态 | 分析问题中的状态类型 | 找出所有可能的状态(通常2-3个) |
| 2. 定义DP数组 | 设计多维数组记录状态 | dp[i][j]表示第i步处于状态j的最优值 |
| 3. 初始化 | 设置初始状态值 | 通常dp[0][state]有明确初始值 |
| 4. 状态转移方程 | 定义状态间转换关系 | 核心难点,需考虑所有可能转移 |
| 5. 计算顺序 | 确定填表顺序 | 通常从左到右线性扫描 |
| 6. 提取结果 | 从最终状态选择最优解 | max(dp[n][all_states]) |
💰 三、典型题目与应用
1. 买卖股票最佳时机问题系列
买卖股票问题是状态机DP最经典的应用场景,通过不同约束条件衍生出多个变种。
题目一:买卖股票最佳时机 II(无限交易)
问题描述:给定股票价格数组,你可以进行多次交易(每次买入卖出算一次交易),但必须在再次买入前卖出已有股票,求最大利润。
状态定义:
dp[i][0]:第i天结束时未持有股票的最大利润dp[i][1]:第i天结束时持有股票的最大利润
状态转移方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
多语言实现:
# Python实现
def maxProfit(prices):
n = len(prices)
dp = [[0] * 2 for _ in range(n)]
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
return dp[n-1][0]
// Java实现
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
}
return dp[n-1][0];
}
// C++实现
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);
}
return dp[n-1][0];
}
题目二:买卖股票最佳时机 IV(最多K次交易)
问题描述:给定股票价格和整数k,你最多可以完成k笔交易,求最大利润。
状态定义:
dp[i][j][0]:第i天已完成j次交易且未持有股票的最大利润dp[i][j][1]:第i天已完成j次交易且持有股票的最大利润
状态转移方程:
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
多语言实现:
# Python实现
def maxProfit(k, prices):
n = len(prices)
if n == 0:
return 0
dp = [[[0] * 2 for _ in range(k+1)] for _ in range(n)]
# 初始化
for j in range(1, k+1):
dp[0][j][1] = -prices[0]
for i in range(1, n):
for j in range(1, k+1):
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
return dp[n-1][k][0]
2. 打家劫舍问题系列
题目:打家劫舍 II(环形排列)
问题描述:房屋围成一圈,相邻房屋不能同时偷窃,求最大金额。
解题思路:将环形问题拆分为两个线性问题:1) 不偷第一间房;2) 不偷最后一间房
多语言实现:
# Python实现
def rob(nums):
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
# 不偷第一间房
profit1 = rob_linear(nums[1:])
# 不偷最后一间房
profit2 = rob_linear(nums[:-1])
return max(profit1, profit2)
def rob_linear(nums):
n = len(nums)
if n == 0:
return 0
dp = [[0] * 2 for _ in range(n)]
dp[0][0] = 0
dp[0][1] = nums[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] = dp[i-1][0] + nums[i]
return max(dp[n-1][0], dp[n-1][1])
3. 粉刷房子问题
问题描述:有n个房子排成一行,每个房子可以用红、蓝、绿三种颜色粉刷,不同颜色成本不同且相邻房子不能同色,求最小成本。
状态定义:
dp[i][0]:第i个房子刷红色时的最小总成本dp[i][1]:第i个房子刷蓝色时的最小总成本dp[i][2]:第i个房子刷绿色时的最小总成本
状态转移方程:
dp[i][0] = cost[i][0] + min(dp[i-1][1], dp[i-1][2])dp[i][1] = cost[i][1] + min(dp[i-1][0], dp[i-1][2])dp[i][2] = cost[i][2] + min(dp[i-1][0], dp[i-1][1])
💡 四、状态机DP优化技巧
- 空间优化:使用滚动数组或变量减少空间复杂度
- 状态压缩:当状态数较多但状态间存在规律时,可用位运算压缩
- 初始化技巧:合理设置初始值,避免边界问题
- 依赖分析:确保状态转移满足拓扑序,避免循环依赖
🎯 五、互联网大厂考点分析
互联网大厂笔试面试中状态机DP的常见考点:
- 基础模型变种:在经典模型上增加约束条件(如交易手续费、冷冻期)
- 状态设计能力:如何合理定义状态解决新问题
- 边界处理:对特殊情况的处理能力(如空输入、极端值)
- 空间优化:能否提出优化方案减少空间复杂度
- 问题转化:将新问题转化为已知状态机模型的能力
📝 六、备考建议
- 掌握经典模型:深入理解买卖股票、打家劫舍等经典问题的状态设计和转移方程
- 多语言实现:熟练使用Python、Java、C++三种语言实现算法
- 模拟练习:在OJ平台上进行限时练习,提高编码速度和准确性
- 总结归纳:建立自己的解题框架和思维模式
- 分析真题:研究各大厂过往真题,了解出题风格和难度
状态机DP通过将复杂问题分解为有限状态和状态转移,提供了一种系统化的解决方案。掌握这种算法思想不仅有助于通过技术面试,也能提升实际工程中的问题解决能力。
更多推荐


所有评论(0)