最小路径和
题目描述
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小,每次只能向下或者向右移动一步。
这个题目是一个典型的动态规划(Dynamic Programming, DP)问题,它涉及到在给定网格中寻找一条从左上角到右下角的路径,使得路径上经过的数字总和最小。由于每次只能向下或向右移动,我们可以利用这一特性来构建一个DP解决方案。
解题思路
- 定义状态:
- 我们定义一个二维数组
dp
,其中dp[i][j]
表示从网格的左上角(0, 0)
到网格中的点(i, j)
的最小路径和。
- 我们定义一个二维数组
- 初始化状态:
- 起点
dp[0][0]
的值就是网格左上角grid[0][0]
的值,因为从起点到起点的路径只有它自己。 - 第一行(
dp[0][j]
)和第一列(dp[i][0]
)上的每个点都只能从左或上到达(除了起点外),因此它们的最小路径和可以通过累加它们之前的元素来计算。例如,dp[0][j] = dp[0][j-1] + grid[0][j]
(对于j > 0
),同理对于第一列。
- 起点
- 状态转移方程:
- 对于网格中的其他点
(i, j)
(即i > 0
且j > 0
),我们可以从它的上方(i-1, j)
或左方(i, j-1)
到达。因此,dp[i][j]
的值取决于这两个方向中哪个方向到达该点的路径和更小,并加上当前点的值。即:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
。
- 对于网格中的其他点
- 边界条件:
- 已经在初始化时处理了第一行和第一列的边界情况。
- 结果:
- 最终,
dp[m-1][n-1]
将包含从左上角到右下角的最小路径和,其中m
和n
分别是网格的行数和列数。
- 最终,
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
// 初始化起点
dp[0][0] = grid[0][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) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
// 填充剩余的dp数组
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
// 返回结果
return dp[m - 1][n - 1];
}
};
在动态规划(DP)问题中,为了简化边界条件的处理,有时我们会在DP表(或称为DP数组)的外部多添加一行和一列。这种方法可以有效地避免在编写状态转移方程时需要考虑起点和边界的特殊情况。然而,需要注意的是,这些额外的行和列通常被初始化为一个足够大的值以确保它们不会被选择为到达网格中某个点的最优路径的一部分。
对于从左上角到右下角的路径和最小化问题,我们可以这样做:
-
初始化额外的行和列:在DP表的第一行和第一列(实际上是索引为0的行和列)上,除了左上角的
(0, 0)
点之外,将所有其他元素初始化为一个足够大的数(表示这些点是不可达的或者路径和极大)。 -
初始化起点:将左上角的
(1, 1)
点初始化为grid[0][0]
的值,因为它是路径的起点。 -
填充DP表:从
(1, 0)
和(0, 1)
开始,按照DP状态转移方程填充DP表的其余部分。注意,由于我们已经初始化了额外的行和列,所以在填充过程中不需要再考虑边界条件。 -
读取结果:最后,DP表的右下角(
dp[m][n]
,其中m
和n
分别是原始网格的行数和列数)将包含从左上角到右下角的最小路径和。
但是,请注意,由于我们在DP表中多添加了一行和一列,因此在访问DP表的元素时,我们需要使用偏移量。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m + 1; i++) {
dp[i][0] = INT_MAX;
}
for (int i = 0; i < n + 1; i++) {
dp[0][i] = INT_MAX;
}
for (int i = 1; i < m + 1; i++) {
for (int j = 1; j < n + 1; j++) {
if (i == 1 && j == 1)
dp[i][j] = grid[i - 1][j - 1];
else {
dp[i][j] =
min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};
珠宝的最高价值
题目描述
现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为:
- 只能从架子的左上角开始拿珠宝
- 每次可以移动到右侧或下侧的相邻位置
- 到达珠宝架子的右下角时,停止拿取
注意:珠宝的价值都是大于 0 的。除非这个架子上没有任何珠宝,比如 frame = [[0]]。
LCR 166. 珠宝的最高价值 - 力扣(LeetCode)
解题思路
该题与上一题相似,实际上是计算“最大路径和”。具体细节不再过多赘述。
class Solution {
public:
int jewelleryValue(vector<vector<int>>& frame) {
if (frame.empty() || frame[0].empty())
return 0; // 如果frame为空,则没有珠宝可拿
int m = frame.size();
int n = frame[0].size();
// 创建一个DP数组,初始化为0
vector<vector<int>> dp(m, vector<int>(n, 0));
// 初始化起点
dp[0][0] = frame[0][0];
// 初始化第一行
for (int j = 1; j < n; ++j) {
dp[0][j] = dp[0][j - 1] + frame[0][j];
}
// 初始化第一列
for (int i = 1; i < m; ++i) {
dp[i][0] = dp[i - 1][0] + frame[i][0];
}
// 填充剩余的dp数组
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + frame[i][j];
}
}
// 返回右下角位置的最大价值
return dp[m - 1][n - 1];
}
};
解法二:
class Solution {
public:
int jewelleryValue(vector<vector<int>>& frame) {
int m = frame.size();
int n = frame[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m + 1; i++) {
dp[i][0] = INT_MIN;
}
for (int i = 0; i < n + 1; i++) {
dp[0][i] = INT_MIN;
}
for (int i = 1; i < m + 1; i++) {
for (int j = 1; j < n + 1; j++) {
if (i == 1 && j == 1)
dp[i][j] = frame[i - 1][j - 1];
else {
dp[i][j] =
max(dp[i - 1][j], dp[i][j - 1]) + frame[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};