动态规划解决剑指 Offer 47题:礼物的最大价值
1. 题目链接
2. 题目描述
在一个 m x n
的棋盘的每一格都放有一个礼物(价值非负),从棋盘的左上角出发,每次向右或向下移动一格,直到到达右下角。给定一个棋盘,计算从起点到终点所能拿到的礼物价值的最大总和。
示例:
- 输入:
[ [1,3,1], [1,5,1], [4,2,1] ]
- 输出:
12
- 解释:最优路径为
右 → 右 → 下 → 下
,总和为1 + 3 + 5 + 2 + 1 = 12
。
3. 示例分析
以示例输入为例:
- 起点
(0,0)
的值为1
,下一步可以选择向右到(0,1)
或向下到(1,0)
。 - 动态规划表
dp
会记录每个位置的最大累计值。 - 最终
dp[3][3]
(对应棋盘右下角)的值为12
,即最优路径的总和。
4. 算法思路
动态规划状态定义
- 定义
dp[i][j]
表示从起点(0,0)
到达棋盘位置(i-1, j-1)
的最大累计价值(索引从1
开始以避免越界)。
状态转移方程
- 每个位置的最大价值等于其左侧或上侧的最大值加上当前格子的价值:
dp[i][j] = frame[i-1][j-1] + max(dp[i-1][j], dp[i][j-1])
初始化技巧
- 由于棋盘起点为
(0,0)
,对应的dp[1][1]
初始化为frame[0][0]
。通过将dp
数组的维度设为(m+1) x (n+1)
,并隐式初始化为0
,避免了显式处理边界条件。
5. 边界条件与注意事项
- 空输入处理:若棋盘为空(
m=0
或n=0
),需返回0
(代码中未显式处理,实际应用需补充)。 - 单行或单列棋盘:路径唯一,直接累加所有值即可。
- 负价值场景:题目假设礼物价值非负,若允许负值需考虑路径选择逻辑是否变化。
6. 代码实现
class Solution {
public:
int jewelleryValue(vector<vector<int>>& frame) {
int m = frame.size(), n = frame[0].size();
// 处理空输入的特殊情况
if (m == 0 || n == 0) return 0;
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = frame[i-1][j-1] + max(dp[i-1][j], dp[i][j-1]);
}
}
return dp[m][n];
}
};
代码解析
- 输入检查:添加了对空棋盘的处理,避免访问非法内存。
- 动态规划表填充:双重循环遍历每个位置,通过
max
函数选择左侧或上侧的最大路径值。 - 返回值:
dp[m][n]
对应棋盘右下角的最大累计价值。
时间复杂度:O(mn)
,需遍历整个棋盘。
空间复杂度:O(mn)
,动态规划表的空间开销。
通过动态规划逐格递推,代码简洁且高效,完美解决了棋盘最大路径和问题。