当前位置: 首页 > news >正文

三维DP深度解析

三维DP深度解析

    • 一、三维DP基础认知
      • 1.1 与低维DP的核心区别
      • 1.2 三维DP的设计步骤
    • 二、经典案例详解
      • 2.1 案例1:三维背包问题(多约束背包)
        • 问题描述
        • 三维DP设计
        • 代码实现
        • 空间优化:三维→二维
      • 2.2 案例2:买卖股票的最佳时机Ⅲ
        • 问题描述
        • 三维DP设计
        • 代码实现
        • 空间优化:三维→二维
      • 2.3 案例3:立体网格中的最短路径
        • 问题描述
        • 三维DP设计
        • 代码实现
    • 三、三维DP的关键技巧与注意事项
      • 3.1 状态维度的提取原则
      • 3.2 空间优化的通用思路
      • 3.3 常见误区
    • 总结

在动态规划的进阶学习中,当问题的复杂度上升——需要同时考虑“多个独立维度的状态变化”(如“时间+选择+持有状态”“体积+重量+数量”)时,一维或二维DP已无法清晰刻画子问题关系。这时,三维DP成为解决问题的关键工具。它通过三个维度的状态定义,将复杂问题拆解为可递推的子问题,适用于处理多约束、多状态的场景。

一、三维DP基础认知

1.1 与低维DP的核心区别

  • 状态维度:一维DP用dp[i]表示“单一维度的子问题”(如“前i个元素”);二维DP用dp[i][j]表示“两个维度的关联”(如“前i个元素+容量j”);三维DP则用dp[i][j][k]表示“三个维度的交叉子问题”,需同时考虑三个独立变量的约束(如“前i个物品+容量j+重量k”)。
  • 适用场景:当问题需要满足三个独立约束条件,或子问题的解依赖“三个维度的状态变化”时,三维DP是自然选择。例如:
    • 三维背包:需同时考虑物品的“体积、重量、数量”三个约束;
    • 股票买卖:需同时考虑“天数、交易次数、持有状态”三个维度;
    • 立体网格:需考虑“x坐标、y坐标、z坐标”的移动约束。
  • 核心挑战:三维DP的状态定义更复杂(需明确三个维度的含义),且空间占用较高(需优化存储),但本质仍是“分解子问题→定义状态→推导递推关系”的逻辑。

1.2 三维DP的设计步骤

  1. 提取问题的三个核心维度:从问题中找到三个独立的约束或状态变量(如“物品数、体积、重量”)。
  2. 定义三维状态:明确dp[i][j][k]的具体含义(如“考虑前i个物品,体积不超过j,重量不超过k时的最大价值”)。
  3. 推导递推关系:分析“是否选择第i个物品”(或其他决策)对dp[i][j][k]的影响,建立与dp[i-1][j][k](不选)、dp[i-1][j-v][k-w](选)等前驱状态的关系。
  4. 初始化边界条件:处理“i=0”“j=0”“k=0”等最小子问题(如“0个物品时价值为0”)。
  5. 填充DP表并优化:按顺序计算所有状态,必要时通过“滚动维度”压缩空间(三维→二维)。

二、经典案例详解

2.1 案例1:三维背包问题(多约束背包)

问题描述

n个物品,每个物品有体积v[i]、重量w[i]、价值val[i]。现有一个背包,最大容量为V,最大承重为W。求能装入背包的最大价值(每个物品只能选一次)。

三维DP设计
  1. 核心维度:物品数量(i)、背包容量(j)、背包承重(k)。
  2. 状态定义dp[i][j][k]表示“考虑前i个物品,使用不超过j的体积和不超过k的重量时,能获得的最大价值”。
  3. 递推关系
    • 不选第i个物品:dp[i][j][k] = dp[i-1][j][k]
    • 选第i个物品(需满足体积和重量约束):dp[i][j][k] = dp[i-1][j - v[i-1]][k - w[i-1]] + val[i-1]
    • 取两者最大值:dp[i][j][k] = max(不选, 选)
  4. 边界条件
    • i=0(0个物品):dp[0][j][k] = 0(价值为0);
    • j=0k=0(容量或承重为0):dp[i][0][k] = 0dp[i][j][0] = 0(无法装物品)。
代码实现
public class ThreeDimensionalKnapsack {public int maxValue(int n, int V, int W, int[] v, int[] w, int[] val) {// 三维DP数组:[物品数][体积][重量]int[][][] dp = new int[n + 1][V + 1][W + 1];// 填充DP表:从1个物品开始遍历for (int i = 1; i <= n; i++) {// 当前物品的体积、重量、价值(i-1对应原数组下标)int currV = v[i - 1];int currW = w[i - 1];int currVal = val[i - 1];for (int j = 0; j <= V; j++) {for (int k = 0; k <= W; k++) {// 不选当前物品:继承前i-1个物品的结果dp[i][j][k] = dp[i - 1][j][k];// 选当前物品:需满足体积和重量约束if (j >= currV && k >= currW) {dp[i][j][k] = Math.max(dp[i][j][k],dp[i - 1][j - currV][k - currW] + currVal);}}}}return dp[n][V][W]; // 考虑所有物品,满约束时的最大价值}public static void main(String[] args) {ThreeDimensionalKnapsack solution = new ThreeDimensionalKnapsack();int n = 3; // 3个物品int V = 5; // 最大容量5int W = 6; // 最大承重6int[] v = {2, 3, 1}; // 体积int[] w = {3, 4, 2}; // 重量int[] val = {5, 6, 2}; // 价值System.out.println(solution.maxValue(n, V, W, v, w, val)); // 输出7(选物品0和2:5+2)}
}
空间优化:三维→二维

观察发现,dp[i][j][k]仅依赖dp[i-1][j][k](上一层),可压缩为二维数组(滚动更新):

public int maxValueOptimized(int n, int V, int W, int[] v, int[] w, int[] val) {// 压缩为二维:[体积][重量](仅保留当前层)int[][] dp = new int[V + 1][W + 1];for (int i = 1; i <= n; i++) {int currV = v[i - 1];int currW = w[i - 1];int currVal = val[i - 1];// 逆序遍历(避免覆盖上一层未使用的状态)for (int j = V; j >= currV; j--) {for (int k = W; k >= currW; k--) {dp[j][k] = Math.max(dp[j][k], dp[j - currV][k - currW] + currVal);}}}return dp[V][W];
}
  • 空间复杂度:从O(n×V×W)O(n \times V \times W)O(n×V×W)降至O(V×W)O(V \times W)O(V×W)(删除“物品数”维度)。

2.2 案例2:买卖股票的最佳时机Ⅲ

问题描述

给定一支股票的价格数组prices,最多完成两笔交易(买入+卖出为一笔),求最大利润(不能同时持有多支股票)。例如:

  • 输入:prices = [3,3,5,0,0,3,1,4]
  • 输出:6(第一笔0→3,第二笔1→4,利润3+3=6)
三维DP设计
  1. 核心维度:天数(i)、交易次数(j)、持有状态(k)。
    • i:第i天(0~n-1);
    • j:已完成的交易次数(0~2,最多2笔);
    • k:持有状态(0:不持有,1:持有)。
  2. 状态定义dp[i][j][k]表示“第i天,已完成j笔交易,当前持有状态为k时的最大利润”。
  3. 递推关系
    • 不持有状态(k=0)
      • 前一天也不持有:dp[i-1][j][0]
      • 前一天持有,今天卖出(交易次数+1,需满足j≥1):dp[i-1][j-1][1] + prices[i]
      • 取最大值:dp[i][j][0] = max(前一天不持有, 今天卖出)
    • 持有状态(k=1)
      • 前一天也持有:dp[i-1][j][1]
      • 前一天不持有,今天买入:dp[i-1][j][0] - prices[i]
      • 取最大值:dp[i][j][1] = max(前一天持有, 今天买入)
  4. 边界条件
    • 初始天(i=0):
      • 持有:dp[0][0][1] = -prices[0](买入第一天股票);
      • 不持有:dp[0][j][0] = 0
    • 交易次数j=0时,无法卖出(卖出需至少1笔交易)。
代码实现
public class BestTimeToBuyAndSellStockIII {public int maxProfit(int[] prices) {int n = prices.length;if (n == 0) return 0;// 三维DP数组:[天数][交易次数][持有状态]// 交易次数0~2,持有状态0(不持有)、1(持有)int[][][] dp = new int[n][3][2];// 初始化第0天dp[0][0][1] = -prices[0]; // 买入第0天股票(0笔交易,持有)// 第0天其他状态(不持有或交易次数>0)均为0或无效(初始化为0)// 填充DP表for (int i = 1; i < n; i++) {for (int j = 0; j <= 2; j++) {// 1. 不持有状态(k=0)// 情况1:前一天不持有int notHoldPrev = dp[i - 1][j][0];// 情况2:今天卖出(需j≥1)int sellToday = (j >= 1) ? (dp[i - 1][j - 1][1] + prices[i]) : 0;dp[i][j][0] = Math.max(notHoldPrev, sellToday);// 2. 持有状态(k=1)// 情况1:前一天持有int holdPrev = dp[i - 1][j][1];// 情况2:今天买入int buyToday = dp[i - 1][j][0] - prices[i];dp[i][j][1] = Math.max(holdPrev, buyToday);}}// 最大利润:最后一天,交易次数0~2,不持有状态的最大值return Math.max(dp[n - 1][2][0], Math.max(dp[n - 1][1][0], dp[n - 1][0][0]));}public static void main(String[] args) {BestTimeToBuyAndSellStockIII solution = new BestTimeToBuyAndSellStockIII();int[] prices = {3, 3, 5, 0, 0, 3, 1, 4};System.out.println(solution.maxProfit(prices)); // 输出6}
}
空间优化:三维→二维

dp[i][j][k]仅依赖dp[i-1][j][k],可压缩为二维数组(保留“交易次数”和“持有状态”):

public int maxProfitOptimized(int[] prices) {int n = prices.length;if (n == 0) return 0;// 压缩为二维:[交易次数][持有状态]int[][] dp = new int[3][2];dp[0][1] = -prices[0]; // 初始化第0天for (int i = 1; i < n; i++) {// 临时数组保存上一天状态(避免覆盖)int[][] prev = new int[3][2];for (int j = 0; j <= 2; j++) {prev[j][0] = dp[j][0];prev[j][1] = dp[j][1];}for (int j = 0; j <= 2; j++) {// 不持有状态dp[j][0] = Math.max(prev[j][0], (j >= 1) ? (prev[j - 1][1] + prices[i]) : 0);// 持有状态dp[j][1] = Math.max(prev[j][1], prev[j][0] - prices[i]);}}return Math.max(dp[2][0], Math.max(dp[1][0], dp[0][0]));
}

2.3 案例3:立体网格中的最短路径

问题描述

在一个x×y×z的立体网格中,从起点(0,0,0)到终点(x-1,y-1,z-1),每次只能沿x、y、z轴方向移动1步(不能斜向移动),求最短路径的步数(固定为x+y+z-3,此处扩展为“带权重的网格”,求最小权重和)。

三维DP设计
  1. 核心维度:x坐标(i)、y坐标(j)、z坐标(k)。
  2. 状态定义dp[i][j][k]表示“从(0,0,0)(i,j,k)的最小权重和”。
  3. 递推关系
    • 只能从三个方向到达(i,j,k)
      • x方向前一步:(i-1,j,k)(需i≥1);
      • y方向前一步:(i,j-1,k)(需j≥1);
      • z方向前一步:(i,j,k-1)(需k≥1);
    • 因此:dp[i][j][k] = grid[i][j][k] + min(来自x, 来自y, 来自z)(加上当前网格的权重)。
  4. 边界条件
    • 起点(0,0,0)dp[0][0][0] = grid[0][0][0]
    • 仅x轴移动(j=0,z=0):dp[i][0][0] = dp[i-1][0][0] + grid[i][0][0]
    • 仅y轴或z轴移动类似。
代码实现
public class 3DGridShortestPath {public int minPathSum3D(int[][][] grid) {int x = grid.length;int y = grid[0].length;int z = grid[0][0].length;// 三维DP数组:[x][y][z]int[][][] dp = new int[x][y][z];// 初始化起点dp[0][0][0] = grid[0][0][0];// 初始化x轴(y=0,z=0)for (int i = 1; i < x; i++) {dp[i][0][0] = dp[i - 1][0][0] + grid[i][0][0];}// 初始化y轴(x=0,z=0)for (int j = 1; j < y; j++) {dp[0][j][0] = dp[0][j - 1][0] + grid[0][j][0];}// 初始化z轴(x=0,y=0)for (int k = 1; k < z; k++) {dp[0][0][k] = dp[0][0][k - 1] + grid[0][0][k];}// 填充DP表for (int i = 0; i < x; i++) {for (int j = 0; j < y; j++) {for (int k = 0; k < z; k++) {if (i == 0 && j == 0 && k == 0) continue; // 跳过起点int minPrev = Integer.MAX_VALUE;// 检查x方向前一步if (i > 0) minPrev = Math.min(minPrev, dp[i - 1][j][k]);// 检查y方向前一步if (j > 0) minPrev = Math.min(minPrev, dp[i][j - 1][k]);// 检查z方向前一步if (k > 0) minPrev = Math.min(minPrev, dp[i][j][k - 1]);dp[i][j][k] = grid[i][j][k] + minPrev;}}}return dp[x - 1][y - 1][z - 1];}public static void main(String[] args) {3DGridShortestPath solution = new 3DGridShortestPath();int[][][] grid = {{{1, 2, 3},{4, 5, 6}},{{7, 8, 9},{10, 11, 12}}};System.out.println(solution.minPathSum3D(grid)); // 输出33(1→2→3→6→12,或其他最短路径)}
}

三、三维DP的关键技巧与注意事项

3.1 状态维度的提取原则

三维DP的核心是“找到三个独立的状态维度”,提取时需注意:

  • 独立性:三个维度应相互独立(如“交易次数”与“持有状态”无关);
  • 必要性:每个维度都是问题的核心约束(如三维背包必须同时考虑体积和重量);
  • 可递推性:子问题的解能通过三个维度的变化推导(如“前i个物品”可通过“前i-1个物品”推导)。

3.2 空间优化的通用思路

三维DP的空间优化本质是“删除可滚动的维度”:

  1. 观察状态依赖:若dp[i][j][k]仅依赖i-1层的状态(如三维背包、股票问题),可删除“i”维度,用二维数组滚动更新;
  2. 逆序遍历:压缩维度后,需逆序遍历被删除维度的对应变量(如背包的体积、重量),避免覆盖未使用的前驱状态;
  3. 临时变量:若依赖同一层的多个状态(如立体网格),需用临时变量保存未更新的状态,避免覆盖。

3.3 常见误区

  • 维度冗余:定义了不必要的维度(如可通过二维解决却用三维),增加复杂度;
  • 状态定义模糊:未明确dp[i][j][k]的具体含义(如“交易次数”是“已完成”还是“已开始”),导致递推错误;
  • 忽略边界初始化:三维DP的边界包括“i=0”“j=0”“k=0”三种情况,遗漏任何一种都会导致结果错误。

总结

三维DP是处理多约束、多状态问题的强大工具,其核心在于通过三个维度清晰刻画子问题的依赖关系。从三维背包的“体积+重量+物品”,到股票问题的“天数+交易次数+持有状态”,三维DP始终遵循“分解子问题→定义状态→推导递推关系”的逻辑,只是状态维度更复杂。

掌握三维DP的关键是:

  1. 从问题中提取三个独立的核心维度(约束或状态);
  2. 明确dp[i][j][k]的具体含义(避免模糊);
  3. 基于“决策选择”(如“选/不选物品”“买/卖股票”)推导递推关系;
  4. 必要时通过“滚动维度”优化空间(三维→二维)。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.dtcms.com/a/293411.html

相关文章:

  • 数学与应用数学专业核心课程解析
  • 【编程练习】
  • day 32 打卡
  • Linux中信号认识及处理和硬件中断与软中断的讲解
  • 生成式人工智能对网络安全的影响
  • 软件工程:软件设计
  • Python机器学习:从零基础到项目实战
  • 一个基于现代C++智能指针的优雅内存管理解决方案
  • Pycharm下载、安装及配置
  • Linux 内核不能直接访问物理地址,必须通过虚拟地址访问。
  • 17.VRRP技术
  • 【C++】简单学——vector类(模拟实现)
  • 基于SpringBoot+Vue的班级管理系统(Echarts图形化分析)
  • 一、Vue概述以及快速入门
  • DeepSeek下载量断崖式下跌72%,AI助手市场大洗牌 | AI早报
  • 广播分发中心-广播注册流程
  • 秋招Day17 - Spring - AOP
  • 构建RAG智能体(2):运行状态链
  • C#文件操作(创建、读取、修改)
  • 【世纪龙科技】电动汽车原理与构造-汽车专业数字课程资源
  • [c++11]final和override
  • 黄山派lvgl8学习笔记(2)导入头文件和新建一个按钮控件
  • 标记语言---XML
  • linux 驱动-power_supply 与 mtk 充电框架
  • 工业互联网时代,如何通过混合SD-WAN提升煤炭行业智能化网络安全
  • 【Pytorch】数据集的加载和处理(一)
  • 使用ubuntu:20.04和ubuntu:jammy构建secretflow环境
  • ndarray的创建(小白五分钟从入门到精通)
  • 嵌入式开发学习(第三阶段 Linux系统开发)
  • 数据资产——解读数据资产全过程管理手册2025【附全文阅读】