Hello! 亲爱的小伙伴们,大家好呀!欢迎观看本篇博客,接下来让我们一起来学习一道经典的动态规划算法题—— 数字三角形 吧!祝你有所收获!
文章目录
数字三角形问题描述
给定一个由 n行数字组成的数字三角形如下图所示。试设计一个算法,计算出从三角形
的顶至底的一条路径(每一步可沿左斜线向下或右斜线向下),使该路径经过的数字总和最大。
输入格式:
输入有n+1行:
第 1 行是数字三角形的行数 n,1<=n<=100。
接下来 n行是数字三角形各行中的数字。所有数字在0…99 之间。
输出格式:
输出最大路径的值。
输入样例:
在这里给出一组输入。例如:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
在这里给出相应的输出。例如:
30
动态规划解决问题
- 个人理解:动态规划是带备忘的递推求解过程。要先将先前计算出来的结果进行保存,并在后面的计算中充分利用已经计算的值,从而避免重复的计算,达到提高效率的目的。
- 由上分析,我们就知道动态规划问题的递推式应该这样写:当前计算结果 与 先前计算结果的关系式。这是大白话,稍微正式一点的说法就是:当前规模问题的解 用 更小规模问题的解来表示。
解题步骤
1.问题结构分析:最优解是什么,最优值是什么
在这道题目中,给定了一个由 n
行数字组成的数字三角形。要求我们计算出从三角形的顶点到底部的一条路径,使得路径经过的数字总和最大。我们只能从某个数字走到下方相邻的数字(左斜线或右斜线方向)。
最优解:从顶到底的一条路径,使得路径上经过的数字和最大。
最优值:路径和的最大值。
2. 递推关系建立:求解递推关系式
在考虑求解结果的递推式的时候,为了能够充分利用已经计算过的值,我们应该从底部开始,用前面的结果表示当前要求的结果。
为了计算最大路径和,我们可以定义一个二维数组 dp[i][j]
,表示从三角形的顶点到达位置 (i, j)
的最大路径和。
递推关系式:
- 对于三角形中的每一个元素
(i, j)
,我们可以从上一行的(i-1, j-1)
或(i-1, j)
到达该位置。也就是说,当前位置的路径和是从上一行左斜下方或右斜下方的位置的最大值加上当前的值:
d p [ i ] [ j ] = triangle [ i ] [ j ] + max ( d p [ i − 1 ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) dp[i][j] = \text{triangle}[i][j] + \max(dp[i-1][j-1], dp[i-1][j]) dp[i][j]=triangle[i][j]+max(dp[i−1][j−1],dp[i−1][j])
单独挑出特殊情况:
-
如果是最左边的元素(
j == 0
),只能从右上方到达:d p [ i ] [ j ] = triangle [ i ] [ j ] + d p [ i − 1 ] [ j ] dp[i][j] = \text{triangle}[i][j] + dp[i-1][j] dp[i][j]=triangle[i][j]+dp[i−1][j]
-
如果是最右边的元素(
j == i
),只能从左上方到达:
d p [ i ] [ j ] = triangle [ i ] [ j ] + d p [ i − 1 ] [ j − 1 ] dp[i][j] = \text{triangle}[i][j] + dp[i-1][j-1] dp[i][j]=triangle[i][j]+dp[i−1][j−1]
确定初始值:
- 初始化时,
dp
数组的第一行就是三角形的第一行,即 d p [ 0 ] [ 0 ] = t r i a n g l e [ 0 ] [ 0 ] dp[0][0]=triangle[0][0] dp[0][0]=triangle[0][0]然后逐行计算每个元素的最大路径和。
3. 自顶向下分析,自底向上求解
动态规划是一种“从底至顶”的方法:从最小子问题的解开始,迭代地构建更大子问题的解,直至得到原问题的解。
比如在本问题中,最小子问题应该是数字三角形只有第一层的时候,这时候不用进行计算就可以的出问题的解 d p [ 0 ] [ 0 ] = t r i a n g l e [ 0 ] [ 0 ] dp[0][0]=triangle[0][0] dp[0][0]=triangle[0][0],然后就要分析题目得到的子问题的解与更大子问题的解直接的递推关系,进一步求出更大子问题的解,直达求出原问题的解。
由于动态规划不包含回溯过程,因此只需使用循环迭代实现,无须使用递归。
4. 追踪最优解
在计算过程中,我们不仅可以计算每个位置的最大路径和,还可以追踪最优解的路径,即追踪每个位置的最大路径是通过哪个方向(左斜线或右斜线)到达的。通过这种方式,我们可以重构出实际的最大路径。
代码实现
以下是基于上述分析的 C++ 代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
// 输入数字三角形
vector<vector<int>> triangle(n);
for (int i = 0; i < n; ++i) {
triangle[i].resize(i + 1);
for (int j = 0; j <= i; ++j) {
cin >> triangle[i][j];
}
}
// 动态规划数组,用来存储每个位置的最大路径和
vector<vector<int>> dp(n);
for (int i = 0; i < n; ++i) {
dp[i].resize(i + 1);
}
// 初始化第一行的最大路径和
dp[0][0] = triangle[0][0];
// 动态规划从第二行开始填充dp数组
for (int i = 1; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
if (j == 0) {
// 如果是最左边的元素,只能从左上角到达
dp[i][j] = dp[i - 1][j] + triangle[i][j];
} else if (j == i) {
// 如果是最右边的元素,只能从右上角到达
dp[i][j] = dp[i - 1][j - 1] + triangle[i][j];
} else {
// 其它位置可以从上面两个位置过来
dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
}
}
}
// 计算最后一行的最大路径和
int max_sum = *max_element(dp[n - 1].begin(), dp[n - 1].end());
cout << max_sum << endl;
return 0;
}
解释
- 输入部分:我们首先读取数字三角形的大小
n
,然后通过循环读取每行的数据并存储在triangle
二维数组中。 - 动态规划部分:我们用
dp
数组来存储从顶到底每个位置的最大路径和。对于每个位置(i, j)
,我们根据递推关系更新dp[i][j]
的值。 - 输出部分:最后,输出
dp[n-1]
中的最大值,即从顶到底的最大路径和。
复杂度分析
- 时间复杂度:由于我们需要遍历每个元素,并且每个元素的计算只需要常数时间,因此总的时间复杂度是 O ( n 2 ) O(n^2) O(n2),其中 n n n是三角形的行数。
- 空间复杂度:我们使用了一个二维数组
dp
来存储路径和,因此空间复杂度也是 O ( n 2 ) O(n^2) O(n2)。
好啦,本篇文章到这里就结束啦,感谢小伙伴的观看!祝所有小伙伴学有所成!!
有任何想法,欢迎评论区留言讨论哦~