前言
在计算机科学与算法研究中,动态规划是一种强大且广泛应用的技术。它通过将复杂问题分解为较小的子问题,利用已解决的子问题的解来构建整体解,从而有效地优化计算过程。本题目集聚焦于路径优化与价值计算的多个经典动态规划问题,旨在帮助读者深入理解这一技术的核心思想及其应用。
在现代应用场景中,如机器人路径规划、网络数据传输和资源分配等,动态规划提供了可行的解决方案。通过系统地分析和解决问题,动态规划不仅提升了效率,还为各种复杂问题的求解提供了直观的思路。
本题目集包括多个相关问题,例如独特路径、带障碍物的路径、最小路径和、地下城游戏等。这些问题涵盖了不同的情境和约束条件,展现了动态规划在多种问题中的灵活性和实用性。通过逐步解决这些问题,读者将能够掌握动态规划的基本原理,培养算法思维,并为更复杂的算法挑战奠定基础。
不同路径
解题思路:
- 动态规划: 创建一个二维数组 dp,dp[i][j] 表示从 (0, 0) 到 (i, j) 的路径数。
初始化: 第一行和第一列的路径数均为 1(只有一条路径到达)。
状态转移方程: dp[i][j] = dp[i-1][j] + dp[i][j-1],表示当前点的路径数等于上方和左方路径数之和。
最终结果: 返回 dp[m-1][n-1]。
class Solution
{
public:
int uniquePaths(int m, int n)
{
// 创建一个 (m+1) x (n+1) 的二维数组 dp,用于存储到达每个位置的路径数量
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
// 初始化起点 (0, 1),表示到达 (1, 0) 的路径数量为 1
dp[0][1] = 1;
// 遍历整个 dp 数组,从 (1, 1) 开始到 (m, n)
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
// 当前格子的路径数量等于上方格子和左方格子的路径数量之和
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
// 返回到达 (m, n) 的路径数量
return dp[m][n];
}
};
不同路径||
解题思路:
- 动态规划: 使用与上一题相同的方法,但需考虑障碍物。
初始化: 如果起点或终点有障碍物,直接返回 0。初始化时,遇到障碍物时将 dp[i][j] 设为 0。
状态转移方程: 如果 grid[i][j] 是障碍物,则 dp[i][j] = 0;否则,dp[i][j] = dp[i-1][j] + dp[i][j-1]。
最终结果: 返回 dp[m-1][n-1]。
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
// 获取网格的行数和列数
int m = obstacleGrid.size(), n = obstacleGrid[0].size();
// 创建一个 (m+1) x (n+1) 的二维数组 dp,用于存储到达每个位置的路径数量
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
// 初始化起点 (0, 1),表示到达 (1, 0) 的路径数量为 1
dp[0][1] = 1;
// 遍历整个 dp 数组,从 (1, 1) 开始到 (m, n)
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
{
// 检查当前格子是否为障碍物
if(obstacleGrid[i - 1][j - 1] == 0)
// 当前格子的路径数量等于上方格子和左方格子的路径数量之和
dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
}
// 返回到达 (m, n) 的路径数量
return dp[m][n];
}
};
珠宝的最高价值
解题思路:
- 动态规划: 创建一个一维数组 dp,dp[j] 表示当前背包容量为 j 时的最大价值。
状态转移方程: 遍历每个物品和每个可能的背包容量,从后向前更新:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])。
最终结果: dp[maxWeight] 为最大价值。
class Solution
{
public:
int jewelleryValue(vector<vector<int>>& frame)
{
// 获取矩阵的行数和列数
int m = frame.size(), n = frame[0].size();
// 创建一个 (m + 1) x (n + 1) 的二维数组 dp,用于存储最大价值
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
// 遍历每个格子,计算最大价值
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 - 1][j - 1];
// 返回到达右下角的最大价值
return dp[m][n];
}
};
下降路径最小和
解题思路:
- 动态规划: 创建一个二维数组 dp,dp[i][j] 表示到达 (i, j) 的最小路径和。
初始化: 第一行直接为矩阵的第一行。
状态转移方程: dp[i][j] = grid[i][j] + min(dp[i-1][j-1], dp[i-1][j], dp[i-1][j+1]),需处理边界情况。
最终结果: 返回最后一行的最小值。
class Solution
{
public:
int minFallingPathSum(vector<vector<int>>& matrix)
{
// 获取矩阵的行数
int m = matrix.size();
// 创建一个 (m + 1) x (m + 2) 的二维数组 dp,初始化为 INT_MAX
vector<vector<int>> dp(m + 1, vector<int>(m + 2, INT_MAX));
// 初始化第 0 行,所有元素设为 0
for(int j = 0; j < m + 2; j++)
dp[0][j] = 0;
// 从第 1 行开始填充 dp 数组
for(int i = 1; i <= m; i++)
for(int j = 1; j <= m; j++)
// 当前格子的最小下降路径和为上方三个相邻格子的最小值加上当前格子的值
dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i - 1][j + 1]) + matrix[i - 1][j - 1];
// 初始化返回结果为 INT_MAX
int ret = INT_MAX;
// 从最后一行找出最小的下降路径和
for(int i = 1; i <= m; i++)
ret = min(ret, dp[m][i]);
// 返回最小下降路径和
return ret;
}
};
最小路径和
解题思路:
- 动态规划: 使用类似于 Minimum Falling Path Sum 的方法。
初始化: dp[0][0] 为网格的左上角值。
状态转移方程: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])。
最终结果: 返回 dp[m-1][n-1]。
class Solution
{
public:
int minPathSum(vector<vector<int>>& grid)
{
// 获取网格的行数和列数
int m = grid.size(), n = grid[0].size();
// 创建一个 (m + 1) x (n + 1) 的二维数组 dp,初始化为 INT_MAX
vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
// 初始化起点 (0, 1) 和 (1, 0) 为 0
dp[0][1] = dp[1][0] = 0;
// 遍历每个格子,计算最小路径和
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 - 1][j - 1];
}
}
// 返回右下角的最小路径和
return dp[m][n];
}
};
地下城游戏
解题思路:
扫描二维码关注公众号,回复:
17408915 查看本文章

- 动态规划: 从右下角开始逆向计算所需的生命值。
初始化: dp[m-1][n-1] 至少需要 1 生命值。
状态转移方程: 从右、下方推导当前格子的生命值:dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])。
最终结果: 返回 dp[0][0]。
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>>& dungeon)
{
// 获取地下城的行数和列数
int m = dungeon.size(), n = dungeon[0].size();
// 创建一个 (m + 1) x (n + 1) 的二维数组 dp,初始化为 INT_MAX
vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
// 初始化右边界和下边界的值为 1
dp[m - 1][n] = dp[m][n - 1] = 1;
// 从右下角开始逆向遍历每个格子
for(int i = m - 1; i >= 0; i--)
{
for(int j = n - 1; j >= 0; j--)
{
// 当前格子的最小生命值等于下方和右方的最小值减去当前格子的值
dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j];
// 确保最小生命值至少为 1
dp[i][j] = max(1, dp[i][j]);
}
}
// 返回从起点到终点所需的最小生命值
return dp[0][0];
}
};