数字三角形(进阶版)
问题描述
(图3.1-1)示出了一个数字三角形。 请编一个程序计算从顶至底的某处的一条路
径,使该路径所经过的数字的总和最大。
●每一步可沿左斜线向下或右斜线向下走;
●1<三角形行数≤100;
●三角形中的数字为整数0,1,…99;

文件中首先读到的是三角形的行数。
接下来描述整个三角形
输出格式:
最大总和(整数)
样例输入
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
样例输出
30
1、首先我们考虑能否使用搜索去做,因为每一步可沿左斜线向下或右斜线向下走,也就是说我们到达了一个位置(i,j),接着可以到达(i+1,j)或者(i+1,j+1);那么根据这个规律,就可以进行递归搜索,代码如下:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;
int val[101][101];
int n,ans,cnt;//ans位搜索的最大值,cnt为当前最大值
void dfs(int i,int j)//从(i,j)开始向下搜索
{
if(i==n+1)//最后一行,终止条件
{
if(cnt>ans)
{
ans=cnt;
}
return;
}
cnt+=val[i][j];//加上当前位置的数值
dfs(i+1,j);
dfs(i+1,j+1);
cnt-=val[i][j];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>val[i][j];
}
}
dfs(1,1);
cout<<ans;
return 0;
}
复杂度很明显是2的n次方,时间很容易超限
原因分析如下:
当我们要计算(i,j)这个位置时,这个位置是由(i-1,j)转移过来的,我们已经求出了所有的(i.j)下面的位置,我们向上返回到(i-1,j)这个位置,我们向下就到达了(i,j+1)。我们发现(i,j)可以到达(i+1,j+1)并且(i,j+1)也可以到达(i+1,j+1)。那么(i+1,j+1)这个点我们就计算了两次,会导致大量的重复计算,效率低下,导致时间超限。因此我们考虑,如果有一个数组用过来保存(i,j)能够得到的最大值,那这样我们就不用重复计算,那么就是把整个数组遍历一遍,时间复杂度就是等差数组(n+1)*n/2,这样时间复杂度就大大降低了。我们把这个方法叫做记忆化,这样整个方法就叫做记忆化搜索。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;
int val[101][101];
int f[101][101];
int n,ans,cnt;
int dfs(int i,int j)
{
if(f[i][j]!=-1)
{
return f[i][j];
}
if(i==n+1)
{
return f[i][j]=val[i][j];
}
return f[i][j]=max(dfs(i+1,j+1),dfs(i+1,j))+val[i][j];
}
int main()
{
memset(f,-1,sizeof(f));//初始化数组f
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>val[i][j];
}
}
cout<<dfs(1,1);
return 0;
}
接着优化,因为递推要比递归快得多,因此平时我们要尽量将递归改造为递推,我们之前f[i][j]表示的是从(i,j)出发能够得到的最大的值,现在改造一下,将f[i][j]表示为从(1,1)出发到(i,j)能够得到的最大的值。而这样就能使用递推,因为f[i][j] 可以由f[i-1][j-1] 和f[i-1][j]得到,而这就是一个递推式,因此当我们计算(i,j)的时候只要保证(i-1,j-1)和(i-1,j)已经计算并且保存就行。而这种形式我们采用的是从上到下的顺推形式。代码如下:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;
int val[101][101];
int f[101][101];
int n,ans;
int main()
{
memset(f,0,sizeof(f));//初始化数组f
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>val[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
if(j==1)
{
f[i][j]=val[i][j]+f[i-1][j];
}
else if(j==n)
{
f[i][j]=val[i][j]+f[i-1][j-1];
}
else f[i][j]=max(f[i-1][j-1],f[i-1][j])+val[i][j];
}
}
for(int i=1;i<=n;i++)
{
ans=max(ans,f[n][i]);
}
cout<<ans;
return 0;
}
接着我们继续优化如果我们从上到下求最大值,转化为从最后一排出发到达最后一层的最大值,那么对于(i,j)来说,可以从(i+1,j+1) (i+1,j)得到,那么我们就可以将整个递推倒过来写,省区最后的一个for循环
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <math.h>
using namespace std;
int val[101][101];
int f[101][101];
int n;
int main()
{
memset(f,0,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>val[i][j];
}
}
for(int i=n;i>=1;i--)
{
for(int j=1;j<=i;j++)
{
f[i][j]=max(f[i+1][j+1],f[i+1][j])+val[i][j];
}
}
cout<<f[1][1];
return 0;
}

2622

被折叠的 条评论
为什么被折叠?



