动态规划--数字三角形问题

动态规划的核心是状态和状态转移方程。


问题描述与状态定义

动态规划的典型问题是数字三角形问题,如上图的图1所示,有一个非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,从第一行的数开始,每次可以往左下或者右下走一格,知道走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大。

分析

  • 解决这个问题的时候,每次有两种选择:左下或者右上。如果用回溯法求出所有可能的路线,就可以从中选出最优的路线。但是和往常一样,回溯法的效率太低:一个n层数字三角形的完整路线有2^(n-1)条,当n很大时回溯法的速度将变得很慢。
  • 为了得到高效的算法,需要抽象的方法思考问题:把当前得位置(i,j)看成是一个状态,然后定义状态(i,j)的指标函数d(i,j)为从格子(i,j)出发时能得到的最大和,a(i,j)为格子本身的值。
  • 从格子(i,j)出发有两种决策,如果往左走,则走到(i+1,j)后需要求“从(i+1,j)出发后能得到的最大和”这一问题,即d(i+1,j),类似的,往右走之后需要求解d(i+1,j+1)。总结为下面的状态转移方程: d(i,j)=a(i,j)+max{d(i+1,j),d(i+1,j+1)}

方法                  

   1.递归计算

int solve(int i,int j){
    return a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)))
}

  • 这样做是正确的,但是时间效率太低了,其原因在于重复计算,如图2中solve(1,1)对应的调用关系,solve(3,2)被计算了2次。

     2.递推计算
int i,j;
for(int j=1;j<=n;j++)
    d[n][j]=a[n][j];
for(int i=n-1;i>=1;i--){
    for(int j=1;j<=i;j++){
        d[i][j]=a[i][j]+max(d[i+1][j],d[i+1][j+1])
    }
}
  • 程序的时间复杂度是O(n^2),这样计算的原因在于i是逆序枚举的,因此计算d[i][j]前,它所需要的d[i+1][j]和d[i+1][j+1]一定已经计算出来了。
  • 在多数情况下,递推的时间复杂度是:状态的总数X每个状态的决策个数X决策时间。

    3:记忆化搜索
  • 程序分为两部分:第一部分把d全部初始化为-1。
memset(d,-1,sizeof(d))
  • 然后编写递归函数。
int solve(int i,int j){
    if(d[i][j]>=0)
        return d[i][j];
    return d[i][j]=a[i][j]+(1==n?0:max(solve(i+1,j),solve(i+1,j+1)));
}
  • 上述程序依然是递归的,但是同时也把计算结果保存在数组d中。题目中说各个数都是非负数,因此如果已经计算过某个d[i][j],则它应该是非负的,这样通过判断d[i][j]是否大于等于0来得知它是否被计算过。时间复杂度是O(n^2)。

  • 上述的方法称为记忆化,如图2所示,它虽然不像递推法那样显式的指明了计算顺序,但仍然可以保证每个节点只访问一次。                                                                                                                             
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值