题目链接 https://www.acwing.com/problem/content/900/
算法1(递归)
思路:
递归的思路是,对于f(i,j),即认为其是从f(i,j)到最底下一行最大的路径的值;
可以先画一下递归运行的图,方便理解;
当是最后一行时,那么就直接返回其位置的值;
否则,当前位置的值加上,下一行的j列,即f(i+1,j),和下一行的j+1列,即f(i+1,j+1),取两者较大的max(f(i+1,j),f(i+1,j+1));
最后f(i,j)=max(f(i+1,j),f(i+1,j+1))+D[i][j];
那么输入f(1,1),就可以得出结果。
#include<bits/stdc++.h>
//朴素递归写法
using namespace std;
int D[510][510];
int n;
int f(int i,int j)
{
if(i==n)
{
return D[i][j];
}
else
{
return max(f(i+1,j)+D[i][j],f(i+1,j+1)+D[i][j]);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
scanf("%d",&D[i][j]);
}
printf("%d\n",f(1,1));
return 0;
}
算法2(记忆化递归)
纯递归时间复杂度太高了,所以我们找一个备忘数组存已经算过的位置,下次再需要求的时候,先看有没有算过,如果算过就直接从备忘数组里找,否则再计算。
#include<bits/stdc++.h>
//记忆化递归
using namespace std;
int D[510][510];
int ans[510][510];
int n;
int f(int i,int j)
{
if(i==n)
{
return D[i][j];
}
if(ans[i+1][j]==-1)
{
ans[i+1][j]=f(i+1,j);
}
if(ans[i+1][j+1]==-1)
{
ans[i+1][j+1]=f(i+1,j+1);
}
if(ans[i+1][j]>ans[i+1][j+1])
return ans[i+1][j]+D[i][j];
else
return ans[i+1][j+1]+D[i][j];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
scanf("%d",&D[i][j]);
}
memset(ans,-1,sizeof(ans));
printf("%d\n",f(1,1));
return 0;
}
算法3(自底向上)
代码1:
由当前第i行,求第i-1行,这里矩阵从n+1,开始,最后一行添加一行0,这样就好算一点,这也是为什么算最大值的时候j<i;
#include<bits/stdc++.h>
//自底向上
using namespace std;
int D[510][510];
int ans[510][510];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
scanf("%d",&D[i][j]);
}
for(int i=n+1;i>=2;i--)
{
for(int j=1;j<i;j++)//这里j<i
{
ans[i-1][j]=max(ans[i][j]+D[i-1][j],ans[i][j+1]+D[i-1][j]);
}
}
printf("%d\n",ans[1][1]);
return 0;
}
代码2:
当前第i行,由i+1行来求,其实这样看来与上面并没有什么区别,但在书写时还是有一点区别的,还是要好好想想的;
#include<bits/stdc++.h>
using namespace std;
int D[510][510];
int ans[510][510];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
scanf("%d",&D[i][j]);
}
for(int i=n+1;i>=1;i--)//注意这里从n+1,这样就不用初始化
{
for(int j=1;j<=i;j++)
{
ans[i][j]=max(ans[i+1][j]+D[i][j],ans[i+1][j+1]+D[i][j]);
// ans[i][j] ans[i+1][j] ans[i+1][j+1]
}
}
printf("%d\n",ans[1][1]);
return 0;
}
两种代码都可以,只是思考的角度有点不一样;
自底向上变一维
由于其变化只牵涉上一行,所以可以变成一维;
#include<bits/stdc++.h>
//变一维
using namespace std;
int D[510][510];
int ans[510];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
scanf("%d",&D[i][j]);
}
for(int i=n+1;i>=2;i--)//注意这里从n+1,这样就不用初始化
{
for(int j=1;j<i;j++)
{
ans[j]=max(ans[j]+D[i-1][j],ans[j+1]+D[i-1][j]);
// ans[i][j] ans[i+1][j] ans[i+1][j+1]
}
}
printf("%d\n",ans[1]);
return 0;
}
算法4(动态规划)
思路:
状态表示:f[i][j];
集合:对于f[i][j],表示,从f[1][1]到f[i][j]的所有路径的最大值;(这里可以比较一下递归的假设)
属性:max;
状态计算:
集合的划分:思考一般情况,到f[i][j],这一位置,其上一步只有两种情况:从f[i-1][j-1]—>f[i][j];或者从f[i-1][j]—>f[i][j];
那么f[i][j],就是上一步的最大值max(f[i-1][j-1],f[i-1][j]);再加上i,j位置的数值即D[i][j];
最终:f[i][j]=max(f[i-1][j-1],f[i-1][j])+D[i][j];
这里还要注意的就是初始化问题,因为这个题目的数据有负数,那么如果不初始化其边界,是会造成错误的;
代码实现:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=550,INF=1e9;
int n;
int D[N][N];
int f[N][N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
scanf("%d",&D[i][j]);
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<=i+1;j++)
f[i][j]=-INF;
for(int i=1;i<=n;i++)//这里i只能从1开始,可以想想,因为他是第一个,而后面的边界需要初始化负无穷大;
{
for(int j=1;j<=i;j++)
{
f[i][j] = max(f[i-1][j-1],f[i-1][j])+D[i][j];
}
}
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[n][i]);//因为是f[1][1],到f[i][j]的最大值,所以只需要循环最后一行,找出最大就可;
printf("%d\n",res);
return 0;
}
最后可以想想这个动态规划的写法可以优化成一维吗?
答案是可以的,不过不太好搞,主要就是其初始化的问题;
代码:
#include<iostream>
#include<algorithm>
//优化成一维
using namespace std;
const int N=550,INF=1e9;
int n;
int D[N][N];
int f[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
scanf("%d",&D[i][j]);
}
}
for(int i=0;i<=n+1;i++)//这里初始化好难
f[i]=-INF;
f[1]=D[1][1];
for(int i=2;i<=n;i++)//这里i要从2开始,因为初始化的原因
{
for(int j=i;j>=1;j--)
{
f[j] = max(f[j-1],f[j])+D[i][j];
}
}
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[i]);
printf("%d\n",res);
return 0;
}
还有一个跟这个一样的题目,只是它的数据范围是正整数,相较这个简单一点
链接http://bailian.openjudge.cn/practice/2760/