深入解析“最大路径和”问题(二):树形动态规划与矩阵取数问题
在上一篇博客中,我们通过二叉树的“最大路径和”问题,详细讲解了树形动态规划的解法。然而,路径问题不仅仅存在于树结构中,矩阵中也有类似的路径最优化问题。这次我们将进一步扩展,分析矩阵取数的问题。
问题描述:矩阵取数游戏
题目
小明玩一个矩阵取数游戏,规则如下:
- 给定一个 n × n n \times n n×n) 的矩阵 a i j a_{ij} aij,其中每个格子的价值 a i j ≥ 0 a_{ij} \geq 0 aij≥0。
- 游戏从左上角格子 ( 1 , 1 ) (1, 1) (1,1) 出发,只能向右或下移动,直到到达右下角格子 ( n , n ) (n, n) (n,n)。
- 每经过一个格子,就能获得该格子的价值。
- 要求求出从左上角到右下角路径的最大价值和。
输入
- 第一行:矩阵大小 n n n。
- 接下来的 n n n 行:每行有 n n n个非负整数,表示矩阵格子的价值。
输出
- 最大价值和。
方法论:动态规划解法
1. 解题思路
矩阵取数问题是一个典型的二维动态规划问题。
动态规划的核心是通过构造一个二维数组
d
p
dp
dp,记录到达每个格子的最大价值和,逐步计算出右下角的最大得分。
状态定义
- d p [ i ] [ j ] dp[i][j] dp[i][j]:表示从左上角到达格子 ( i , j ) (i, j) (i,j) 的最大价值和。
状态转移方程
我们定义:
a
[
i
]
[
j
]
a[i][j]
a[i][j]为矩阵位置
(
i
,
j
)
(i,j)
(i,j)的价值。
从起点到达格子
(
i
,
j
)
(i, j)
(i,j),只能从上方或左方到达,因此:
d
p
[
i
]
[
j
]
=
max
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
+
a
i
j
dp[i][j] = \max(dp[i-1][j], dp[i][j-1]) + a_{ij}
dp[i][j]=max(dp[i−1][j],dp[i][j−1])+aij
边界条件
- 起点 d p [ 0 ] [ 0 ] = a 00 dp[0][0] = a_{00} dp[0][0]=a00。
- 第一行和第一列只能从左或上单向到达:
- 第一行: d p [ 0 ] [ j ] = d p [ 0 ] [ j − 1 ] + a 0 j dp[0][j] = dp[0][j-1] + a_{0j} dp[0][j]=dp[0][j−1]+a0j
- 第一列: d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] + a i 0 dp[i][0] = dp[i-1][0] + a_{i0} dp[i][0]=dp[i−1][0]+ai0
2. 算法步骤
- 初始化二维数组 d p dp dp,大小为 n × n n \times n n×n。
- 填充边界条件:第一行和第一列的值。
- 按照状态转移方程依次填充 d p [ i ] [ j ] dp[i][j] dp[i][j]。
- 返回右下角 d p [ n − 1 ] [ n − 1 ] dp[n-1][n-1] dp[n−1][n−1] 的值。
3. 时间复杂度
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),因为需要填充一个 n × n n \times n n×n 的矩阵。
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)(可通过滚动数组优化至 O ( n ) O(n) O(n))。
代码实现(C++)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
vector<vector<int>> matrix(n, vector<int>(n)); // 输入矩阵
vector<vector<int>> dp(n, vector<int>(n)); // 动态规划数组
// 输入矩阵值
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> matrix[i][j];
}
}
// 初始化起点
dp[0][0] = matrix[0][0];
// 初始化第一行
for (int j = 1; j < n; ++j) {
dp[0][j] = dp[0][j-1] + matrix[0][j];
}
// 初始化第一列
for (int i = 1; i < n; ++i) {
dp[i][0] = dp[i-1][0] + matrix[i][0];
}
// 填充动态规划数组
for (int i = 1; i < n; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + matrix[i][j];
}
}
// 输出右下角的最大价值和
cout << dp[n-1][n-1] << endl;
return 0;
}
实例解析:一步步解决问题
输入示例
4
1 3 1 2
1 5 1 1
4 2 1 3
1 1 1 10
动态规划表计算
初始化:
- 起点 d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1
填充第一行和第一列:
i \ j i \backslash j i\j | 1 | 3 | 1 | 2 |
---|---|---|---|---|
1 | 1 | 4 | 5 | 7 |
5 | 2 | |||
4 | 6 | |||
1 | 7 |
填充其余格子:
按照状态转移公式逐步计算:
i \ j i \backslash j i\j | 1 | 3 | 1 | 2 |
---|---|---|---|---|
1 | 1 | 4 | 5 | 7 |
5 | 2 | 9 | 10 | 11 |
4 | 6 | 11 | 12 | 15 |
1 | 7 | 12 | 13 | 25 |
输出结果
最终最大价值和为 d p [ 3 ] [ 3 ] = 25 dp[3][3] = 25 dp[3][3]=25。
矩阵问题与树问题的对比
特性 | 树形动态规划 | 矩阵动态规划 |
---|---|---|
数据结构 | 树 | 矩阵 |
转移方向 | 从子树到父节点 | 从左上角向右下角 |
优化难点 | 合理选择路径的起点和终点 | 优化空间复杂度 |
时间复杂度 | ( O(n) ) | ( O(n^2) ) |
总结与扩展
总结
- 矩阵取数问题是“最大路径和”问题的二维拓展,核心仍然是动态规划。
- 状态转移方程清晰,适合由浅入深引导学习。
- 动态规划思路灵活,稍作调整可用于更复杂的二维路径问题。
扩展问题
- 如果允许向四个方向移动(上下左右),如何解决?
- 如果要求路径不能重复访问某些特定的格子,该如何修改算法?
希望这篇博客对你理解“最大路径和”问题的多种变体有所帮助!