1. 多维DP概述
多维DP是指状态定义涉及两个以上维度的动态规划问题。这类问题通常需要在二维、三维甚至更高维度的状态空间中进行状态转移,常用于处理网格、矩阵、三维空间等复杂问题。
2. 多维DP基本概念
2.1 多维DP特点
- 多维度状态 :状态通常表示为
dp[i][j][k]... - 复杂转移:状态转移可能涉及多个维度的变化
- 空间消耗大:需要仔细设计以优化空间复杂度
- 遍历顺序重要:需要确保所有依赖状态都已计算
2.2 通用模板
python
def multi_dp_template(m, n, p):
"""
三维DP通用模板
"""
# 初始化三维DP数组
dp = [[[0] * p for _ in range(n)] for _ in range(m)]
# 初始化边界条件
for i in range(m):
for j in range(n):
dp[i][j][0] = base_value_1
for j in range(n):
for k in range(p):
dp[0][j][k] = base_value_2
# 状态转移
for i in range(1, m):
for j in range(1, n):
for k in range(1, p):
dp[i][j][k] = max/min(
dp[i-1][j][k] + cost1,
dp[i][j-1][k] + cost2,
dp[i][j][k-1] + cost3,
# ... 其他转移
)
return dp[m-1][n-1][p-1]
3. 二维网格DP进阶
3.1 最小路径和 (LeetCode 64)
问题描述:在网格中从左上角到右下角,求最小路径和。
Python实现
python
def minPathSum(grid):
"""
最小路径和 - 基础版本
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
# 初始化第一行和第一列
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][j]
# 状态转移
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]
#### 空间优化版本
def minPathSum_optimized(grid):
"""
空间优化:只使用一行DP数组
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
dp = [0] * n
# 初始化第一行
dp[0] = grid[0][0]
for j in range(1, n):
dp[j] = dp[j-1] + grid[0][j]
# 更新后续行
for i in range(1, m):
dp[0] += grid[i][0] # 第一列
for j in range(1, n):
dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
return dp[n-1]
#### 原地修改版本
def minPathSum_inplace(grid):
"""
原地修改:使用输入网格存储DP值
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
# 初始化第一行
for j in range(1, n):
grid[0][j] += grid[0][j-1]
# 初始化第一列
for i in range(1, m):
grid[i][0] += grid[i-1][0]
# 状态转移
for i in range(1, m):
for j in range(1, n):
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[m-1][n-1]
Java实现
java
public class MinimumPathSum {
// 标准DP解法
public int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
// 初始化第一列
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i-1][0] + grid[i][0];
}
// 初始化第一行
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j-1] + grid[0][j];
}
// 状态转移
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
// 空间优化版本
public int minPathSumOptimized(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int m = grid.length, n = grid[0].length;
int[] dp = new int[n];
dp[0] = grid[0][0];
for (int j = 1; j < n; j++) {
dp[j] = dp[j-1] + grid[0][j];
}
for (int i = 1; i < m; i++) {
dp[0] += grid[i][0];
for (int j = 1; j < n; j++) {
dp[j] = Math.min(dp[j], dp[j-1]) + grid[i][j];
}
}
return dp[n-1];
}
}
3.2 不同路径带障碍物 (LeetCode 63)
问题描述:网格中有障碍物,求从左上到右下的不同路径数。
python
def uniquePathsWithObstacles(obstacleGrid):
"""
带障碍物的不同路径
"""
if not obstacleGrid or not obstacleGrid[0]:
return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
# 起点或终点有障碍物
if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
return 0
dp = [[0] * n for _ in range(m)]
dp[0][0] = 1
# 初始化第一列
for i in range(1, m):
if obstacleGrid[i][0] == 0:
dp[i][0] = dp[i-1][0]
# 初始化第一行
for j in range(1, n):
if obstacleGrid[0][j] == 0:
dp[0][j] = dp[0][j-1]
# 状态转移
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
#### 空间优化版本
def uniquePathsWithObstacles_optimized(obstacleGrid):
if not obstacleGrid or not obstacleGrid[0]:
return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
if obstacleGrid[0][0] == 1:
return 0
dp = [0] * n
dp[0] = 1
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
elif j > 0:
dp[j] += dp[j-1]
return dp[n-1]
4. 三维DP问题
4.1 三维最大路径和
问题描述:在三维网格中从起点到终点,求最大路径和。
python
def maxPathSum3D(grid):
"""
三维最大路径和
grid: m x n x p 的三维列表
"""
if not grid or not grid[0] or not grid[0][0]:
return 0
m, n, p = len(grid), len(grid[0]), len(grid[0][0])
# 创建三维DP数组
dp = [[[0] * p for _ in range(n)] for _ in range(m)]
# 初始化起点
dp[0][0][0] = grid[0][0][0]
# 初始化三个面的边界
for i in range(1, m):
dp[i][0][0] = dp[i-1][0][0] + grid[i][0][0]
for j in range(1, n):
dp[0][j][0] = dp[0][j-1][0] + grid[0][j][0]
for k in range(1, p):
dp[0][0][k] = dp[0][0][k-1] + grid[0][0][k]
# 初始化三个棱
for i in range(1, m):
for j in range(1, n):
dp[i][j][0] = max(dp[i-1][j][0], dp[i][j-1][0]) + grid[i][j][0]
for i in range(1, m):
for k in range(1, p):
dp[i][0][k] = max(dp[i-1][0][k], dp[i][0][k-1]) + grid[i][0][k]
for j in range(1, n):
for k in range(1, p):
dp[0][j][k] = max(dp[0][j-1][k], dp[0][j][k-1]) + grid[0][j][k]
# 状态转移
for i in range(1, m):
for j in range(1, n):
for k in range(1, p):
dp[i][j][k] = max(
dp[i-1][j][k],
dp[i][j-1][k],
dp[i][j][k-1]
) + grid[i][j][k]
return dp[m-1][n-1][p-1]
#### 空间优化版本(滚动数组)
def maxPathSum3D_optimized(grid):
m, n, p = len(grid), len(grid[0]), len(grid[0][0])
# 使用二维数组滚动
dp = [[0] * n for _ in range(p)]
# 初始化
dp[0][0] = grid[0][0][0]
# 第一层初始化
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][0][j]
for k in range(1, p):
dp[k][0] = dp[k-1][0] + grid[0][0][k]
# 状态转移
for i in range(1, m):
new_dp = [[0] * n for _ in range(p)]
# 更新第一行第一列
new_dp[0][0] = dp[0][0] + grid[i][0][0]
for j in range(1, n):
new_dp[0][j] = max(dp[0][j], new_dp[0][j-1]) + grid[i][0][j]
for k in range(1, p):
new_dp[k][0] = max(dp[k][0], new_dp[k-1][0]) + grid[i][0][k]
# 更新内部
for j in range(1, n):
for k in range(1, p):
new_dp[k][j] = max(
dp[k][j], # 从上到当前层
new_dp[k][j-1], # 从左到当前层
new_dp[k-1][j] # 从上层到当前层
) + grid[i][j][k]
dp = new_dp
return dp[p-1][n-1]
4.2 切披萨问题 (LeetCode 1444)
问题描述:将矩形披萨切成k块,每块至少有一个苹果,求切割方式数。
python
def ways(pizza, k):
"""
切披萨问题 - 三维DP
"""
MOD = 10**9 + 7
m, n = len(pizza), len(pizza[0])
# 预处理苹果数量的前缀和
# apples[i][j]: 从(i,j)到(m-1,n-1)的苹果数
apples = [[0] * (n+1) for _ in range(m+1)]
for i in range(m-1, -1, -1):
for j in range(n-1, -1, -1):
apples[i][j] = apples[i+1][j] + apples[i][j+1] - apples[i+1][j+1]
if pizza[i][j] == 'A':
apples[i][j] += 1
# dp[i][j][c]: 从(i,j)开始,切成c块的方式数
dp = [[[0] * (k+1) for _ in range(n)] for _ in range(m)]
# 初始化:如果当前区域有苹果,可以切成1块
for i in range(m):
for j in range(n):
if apples[i][j] > 0:
dp[i][j][1] = 1
# 状态转移
for cut in range(2, k+1):
for i in range(m):
for j in range(n):
# 水平切
for ii in range(i+1, m):
if apples[i][j] > apples[ii][j]: # 上面部分有苹果
dp[i][j][cut] = (dp[i][j][cut] + dp[ii][j][cut-1]) % MOD
# 垂直切
for jj in range(j+1, n):
if apples[i][j] > apples[i][jj]: # 左边部分有苹果
dp[i][j][cut] = (dp[i][j][cut] + dp[i][jj][cut-1]) % MOD
return dp[0][0][k] % MOD
5. 四维及以上DP问题
5.1 出界的路径数 (LeetCode 576)
问题描述:在网格中移动N步后出界的路径数。
python
def findPaths(m, n, maxMove, startRow, startColumn):
"""
出界的路径数 - 三维DP
dp[move][i][j]: 移动move步后在(i,j)的路径数
"""
MOD = 10**9 + 7
# 方向数组
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# 三维DP数组
dp = [[[0] * n for _ in range(m)] for _ in range(maxMove + 1)]
# 初始化:0步时在起点
dp[0][startRow][startColumn] = 1
result = 0
for move in range(maxMove):
for i in range(m):
for j in range(n):
count = dp[move][i][j]
if count > 0:
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n:
# 还在网格内
dp[move + 1][ni][nj] = (dp[move + 1][ni][nj] + count) % MOD
else:
# 出界了
result = (result + count) % MOD
return result % MOD
#### 空间优化版本(滚动数组)
def findPaths_optimized(m, n, maxMove, startRow, startColumn):
MOD = 10**9 + 7
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# 只使用两个二维数组
dp_prev = [[0] * n for _ in range(m)]
dp_prev[startRow][startColumn] = 1
result = 0
for move in range(maxMove):
dp_curr = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
count = dp_prev[i][j]
if count > 0:
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n:
dp_curr[ni][nj] = (dp_curr[ni][nj] + count) % MOD
else:
result = (result + count) % MOD
dp_prev = dp_curr
return result % MOD
5.2 扔鸡蛋问题 (LeetCode 887)
问题描述:有k个鸡蛋,n层楼,找到鸡蛋恰好不会摔碎的楼层的最小尝试次数。
python
def superEggDrop(k, n):
"""
扔鸡蛋问题 - 二维DP
dp[moves][eggs]: 用moves次移动,eggs个鸡蛋,最多能检查的楼层数
"""
# dp[m][k] = dp[m-1][k-1] + dp[m-1][k] + 1
# 含义:在第X层扔鸡蛋
# 1. 鸡蛋碎了:可以检查 dp[m-1][k-1] 层
# 2. 鸡蛋没碎:可以检查 dp[m-1][k] 层
# 3. 加上当前层
dp = [[0] * (k + 1) for _ in range(n + 1)]
for moves in range(1, n + 1):
for eggs in range(1, k + 1):
dp[moves][eggs] = dp[moves-1][eggs-1] + dp[moves-1][eggs] + 1
if dp[moves][eggs] >= n:
return moves
return n
#### 空间优化版本
def superEggDrop_optimized(k, n):
"""
空间优化:使用一维数组
"""
dp = [0] * (k + 1) # dp[eggs]
moves = 0
while dp[k] < n:
moves += 1
# 从后往前更新,避免覆盖
for eggs in range(k, 0, -1):
dp[eggs] = dp[eggs] + dp[eggs-1] + 1
return moves
#### 二分查找解法
def superEggDrop_binary(k, n):
"""
数学解法:使用二分查找
"""
def f(x):
# 计算用x次移动,k个鸡蛋能检查的最大楼层数
ans = 0
r = 1
for i in range(1, k + 1):
r *= x - i + 1
r //= i
ans += r
if ans >= n:
break
return ans
lo, hi = 1, n
while lo < hi:
mid = (lo + hi) // 2
if f(mid) < n:
lo = mid + 1
else:
hi = mid
return lo
6. 状态压缩DP
6.1 旅行商问题 (TSP)
问题描述:访问所有城市并回到起点的最短路径。
python
def tsp(dist):
"""
旅行商问题 - 状态压缩DP
dist: n x n 距离矩阵
"""
n = len(dist)
# dp[mask][i]: 访问过mask中的城市,当前在城市i的最小代价
# mask是二进制位掩码,第i位为1表示访问过城市i
INF = float('inf')
dp = [[INF] * n for _ in range(1 << n)]
# 初始化:从任意城市开始
for i in range(n):
dp[1 << i][i] = 0
# 状态转移
for mask in range(1 << n):
for i in range(n):
if not (mask & (1 << i)):
continue # 当前城市不在mask中
for j in range(n):
if mask & (1 << j):
continue # 城市j已经访问过
new_mask = mask | (1 << j)
dp[new_mask][j] = min(dp[new_mask][j],
dp[mask][i] + dist[i][j])
# 回到起点
answer = INF
full_mask = (1 << n) - 1
for i in range(n):
answer = min(answer, dp[full_mask][i] + dist[i][0])
return answer
#### 记忆化搜索版本
def tsp_memo(dist):
n = len(dist)
FULL_MASK = (1 << n) - 1
@lru_cache(None)
def dfs(mask, city):
# 所有城市都访问过了,回到起点
if mask == FULL_MASK:
return dist[city][0]
res = float('inf')
for next_city in range(n):
if not (mask & (1 << next_city)):
new_mask = mask | (1 << next_city)
cost = dist[city][next_city] + dfs(new_mask, next_city)
res = min(res, cost)
return res
return dfs(1, 0) # 从城市0开始,mask为1(只有城市0被访问)
6.2 矩阵中的最长递增路径 (LeetCode 329)
问题描述:在矩阵中找到最长递增路径的长度。
python
def longestIncreasingPath(matrix):
"""
矩阵中的最长递增路径 - 记忆化搜索
"""
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
@lru_cache(None)
def dfs(i, j):
max_len = 1
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] > matrix[i][j]:
max_len = max(max_len, 1 + dfs(ni, nj))
return max_len
result = 0
for i in range(m):
for j in range(n):
result = max(result, dfs(i, j))
return result
#### DP+拓扑排序版本
def longestIncreasingPath_topo(matrix):
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# 计算每个点的出度(有多少个更大的邻居)
outdegree = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] > matrix[i][j]:
outdegree[i][j] += 1
# 初始化队列:出度为0的点(局部最大值)
queue = []
for i in range(m):
for j in range(n):
if outdegree[i][j] == 0:
queue.append((i, j))
level = 0
while queue:
level += 1
next_queue = []
for i, j in queue:
for di, dj in directions:
ni, nj = i + di, j + dj
# 指向当前点的邻居(值更小的点)
if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] < matrix[i][j]:
outdegree[ni][nj] -= 1
if outdegree[ni][nj] == 0:
next_queue.append((ni, nj))
queue = next_queue
return level
7. 概率DP
7.1 骑士在棋盘上的概率 (LeetCode 688)
问题描述:骑士在n×n棋盘上随机移动k步后仍留在棋盘上的概率。
python
def knightProbability(n, k, row, column):
"""
骑士在棋盘上的概率 - 三维DP
"""
# 骑士的8个移动方向
directions = [
(-2, -1), (-2, 1), (-1, -2), (-1, 2),
(1, -2), (1, 2), (2, -1), (2, 1)
]
# dp[step][i][j]: 移动step步后在(i,j)的概率
dp_prev = [[0] * n for _ in range(n)]
dp_prev[row][column] = 1.0
for step in range(k):
dp_curr = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
prob = dp_prev[i][j]
if prob > 0:
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < n and 0 <= nj < n:
dp_curr[ni][nj] += prob / 8.0
dp_prev = dp_curr
# 计算总概率
total_prob = 0.0
for i in range(n):
for j in range(n):
total_prob += dp_prev[i][j]
return total_prob
#### 空间优化版本
def knightProbability_optimized(n, k, row, column):
directions = [
(-2, -1), (-2, 1), (-1, -2), (-1, 2),
(1, -2), (1, 2), (2, -1), (2, 1)
]
# 使用两个二维数组
dp = [[0] * n for _ in range(n)]
dp[row][column] = 1.0
for step in range(k):
dp_next = [[0] * n for _ in range(n)]
for r in range(n):
for c in range(n):
if dp[r][c] == 0:
continue
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < n and 0 <= nc < n:
dp_next[nr][nc] += dp[r][c] / 8.0
dp = dp_next
# 计算总概率
total = 0.0
for r in range(n):
for c in range(n):
total += dp[r][c]
return total
8. 计数DP
8.1 不同子序列 (LeetCode 115)
问题描述:计算s中有多少个不同的子序列等于t。
python
def numDistinct(s, t):
"""
不同子序列 - 二维DP
dp[i][j]: s前i个字符中,t前j个字符出现的次数
"""
m, n = len(s), len(t)
# dp[i][j]: s[0:i] 中有多少个不同的子序列等于 t[0:j]
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化:空字符串是任何字符串的子序列
for i in range(m + 1):
dp[i][0] = 1
for i in range(1, m + 1):
for j in range(1, n + 1):
if s[i-1] == t[j-1]:
# 可以选择匹配或不匹配
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
else:
# 只能不匹配
dp[i][j] = dp[i-1][j]
return dp[m][n]
#### 空间优化版本
def numDistinct_optimized(s, t):
m, n = len(s), len(t)
if n > m:
return 0
# 使用一维数组
dp = [0] * (n + 1)
dp[0] = 1 # 空字符串
for i in range(1, m + 1):
# 从后往前更新,避免覆盖
for j in range(min(i, n), 0, -1):
if s[i-1] == t[j-1]:
dp[j] += dp[j-1]
return dp[n]
9. 多维DP优化技巧
9.1 滚动数组优化
对于DP数组,如果当前状态只依赖于前几个状态,可以使用滚动数组减少空间复杂度。
python
# 三维DP滚动数组示例
def dp_with_rolling_array(m, n, p):
# 传统三维DP:O(m*n*p)空间
dp = [[[0] * p for _ in range(n)] for _ in range(m)]
# 滚动数组:O(n*p)空间
dp_prev = [[0] * p for _ in range(n)]
dp_curr = [[0] * p for _ in range(n)]
for i in range(m):
# 计算dp_curr
for j in range(n):
for k in range(p):
dp_curr[j][k] = ... # 根据dp_prev计算
# 滚动
dp_prev, dp_curr = dp_curr, dp_prev
return ...
9.2 状态压缩优化
对于某些DP问题,可以使用位运算压缩状态。
python
# 状态压缩示例:旅行商问题
def tsp_state_compression(dist):
n = len(dist)
FULL_MASK = (1 << n) - 1
# dp[mask][i] 可以表示为字典或二维数组
dp = [[float('inf')] * n for _ in range(1 << n)]
# 或者使用字典存储有效状态
dp_dict = {}
for i in range(n):
dp_dict[(1 << i, i)] = 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 ...
9.3 记忆化搜索
对于复杂的状态转移,记忆化搜索通常比迭代DP更直观。
python
from functools import lru_cache
def dp_with_memoization(params):
@lru_cache(maxsize=None)
def dfs(state1, state2, ...):
# 终止条件
if is_terminal(state1, state2, ...):
return base_value
# 状态转移
result = initial_value
for next_state in get_next_states(state1, state2, ...):
result = combine(result, dfs(next_state) + cost)
return result
return dfs(initial_state)
10. 多维DP解题模板
10.1 通用解题步骤
-
分析问题维度:
- 确定需要几个维度表示状态
- 每个维度的含义是什么
-
定义状态:
- 明确dp数组的含义
- 确定状态变量的范围
-
推导转移方程:
- 分析状态之间的转移关系
- 考虑所有可能的转移路径
-
初始化:
- 设置边界条件
- 处理特殊情况
-
确定遍历顺序:
- 确保依赖状态已计算
- 优化空间复杂度
-
计算结果:
- 从最终状态提取答案
- 处理可能的溢出或边界
10.2 常见问题模式
| 问题类型 | 维度数 | 状态含义 | 优化技巧 |
|---|---|---|---|
| 网格路径 | 2 | dp[i][j] 位置(i,j)的最优解 | 滚动数组 |
| 三维路径 | 3 | dp[i][j][k] 三维位置 | 降维打击 |
| 状态压缩 | 2 | dp[mask][i] 访问状态+当前位置 | 位运算 |
| 概率DP | 3 | dp[step][i][j] 步数+位置 | 矩阵快速幂 |
| 计数DP | 2 | dp[i][j] 前i个中前j个的计数 | 前缀和优化 |
10.3 复杂度分析
| 维度 | 状态数 | 时间复杂度 | 空间复杂度(优化前) | 空间复杂度(优化后) |
|---|---|---|---|---|
| 2D | m×n | O(m×n) | O(m×n) | O(min(m,n)) |
| 3D | m×n×p | O(m×n×p) | O(m×n×p) | O(n×p) |
| 状态压缩 | 2^n×n | O(n²×2^n) | O(n×2^n) | O(2^n) |
| 概率DP | k×m×n | O(k×m×n) | O(k×m×n) | O(m×n) |
10.4 调试技巧
- 可视化DP表:
python
def print_dp_2d(dp):
for row in dp:
print(' '.join(f'{x:4d}' for x in row))
def print_dp_3d(dp):
for k in range(len(dp[0][0])):
print(f"Layer {k}:")
for i in range(len(dp)):
print(' '.join(f'{dp[i][j][k]:4d}' for j in range(len(dp[0]))))
- 小规模测试:
python
def test_small_case():
# 创建小规模测试数据
grid = [[1,2,3],[