数字三角形总结

题目链接 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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值