问题描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的样例中,路径 7→3→8→7→5 的数字和最大
输入格式
第一个行一个正整数 N,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
输出格式
单独的一行,包含那个可能得到的最大的和。
输入输出样例
输入:
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
输出:
30
数据范围
对于100%的数据,1 ≤ N≤ 1000,所有输入在 [0,100] 范围内。
思路分析:
1.数塔用二维数组来存储,定义dp[1000][1000],前一个对应数塔的行数,后一个对应数塔中每一行的个数;
2.找最大数的路径(递归:从上至下)
两层数塔,相要找到它的最大和,只要比较1下面一层的两个数谁最大相加即可;
即dp[0][0]+max(dp[1][0]+dp[1][1])
1 | |
2 | 3 |
三层数塔:用dfs(0,0)表示该数找到的最大路径
对于下面的数塔就可以转化为的子问题即为:dfs(0,0)=dp[0][0]+max(dfs(1,0)+dfs(1,1));
dfs(1,0)=dp[1][0]+max(dp[2][0]+dp[2][1]);dfs(1,1)=dp[1][1]+max(dp[2][1]+dp[2][2])
1 | ||
2 | 3 | |
4 | 5 | 6 |
......以此类推
·数塔中的一个数和它下面对应的两个数在二维数组的下标关系:dp[i][j]
左下方:dp[i+1][j];右下方:dp[i+1][j+1]
实现代码:
#include<iostream>
using namespace std;
int dp[1000][1000];
int N;
int dfs(int i,int j)
{
if(i==N-1)
return dp[i][j];//递归边界即最后一行直接返回
else
return dp[i][j]+max(dfs(i+1,j),dfs(i+1,j+1));
}
int main(void)
{
scanf("%d",&N);//运行速度比cin快
for(int i=0;i<N;i++)
{
for(int j=0;j<=i;j++)
scanf("%d",&dp[i][j]);
}
int max=dfs(0,0);
cout<<max;
return 0;
}
用递归后,容易发现如果数据量过大,重复的计算太多,就需要大量的时间,这就很容易超时,所以可以用动态规划(从下到上的解决问题)
3.动态规划的基本思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。我们需要保存已解决的子问题的答案,这样可以避免大量的重复计算,节省时间。我们会用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中,而在需要时再找出已求得的答案。
#include <iostream>
using namespace std;
int main(void)
{
int dp[1000][1000];
int f[1000][1000]={0};
int N;
scanf("%d",&N);
for (int i = 0;i <= N;i++)
{
for (int j = 0;j <= i;j++)
scanf("%d",&dp[i][j]);
}
for (int i = N-1;i >= 0;i--)//用i来表示行,且从最后一行开始
{
for (int j = 0;j <= i;j++)//每行有i列,且从第一列开始
f[i][j] = max(f[i + 1][j],f[i + 1][j + 1]) + dp[i][j];//自底向上计算最长路径
}
cout << f[0][0];//当for循环结束完毕之后f[0][0]:从数塔第一层开始的最长路径
return 0;
}