数字三角形问题/数塔问题(杭电2084)


最近在做动态规划相关的算法题,记录一下这个经典的动归算法题。

问题描述

给出一个数塔(数字三角形),要求从数塔的顶端数走到数塔底端,每一步只能走到下一行的相邻位置,求经过结点的数字之和最大值。(杭电2084)
在这里插入图片描述

算法思路与代码实现

动态规划算法

为了方便描述,约定用high表示数塔高度,用data[1] [1] 表示数塔最高层(第一层)的数。

假设要走出一条经过结点的数字之和最大的“最优路径”,则每次往下走都必须选择:在此结点往下以最佳路径走能得到的数字之和较大的结点。

例如:以上图为例子,数塔顶端走的下一个位置,可以选择‘12’和‘15’ , 则使数字之和最大的最优路径的选择一定是‘12’和‘15’中,往下走能得到的最大数字之和中的较大的那一个。

用dp[i] [j] 表示以data[i] [j]位置为顶的数塔,一直走到底行位置得到的各节点数值之和的最大值(即从data[i] [j]位置从下走所能得到问题的最优值)。显然底层中dp[high] [j] = data[i] [j]即只有一个数的情况下,节点数字之和就是其本身代表的数。

在这种表示下,要求解的问题转化为求dp[1] [1],易得其状态转移方程如下:

d p [ i ] [ j ] = { d a t a [ i ] [ j ] + m a x { d p [ i + 1 ] [ j ] ,   d p [ i + 1 ] [ j + 1 ]   } 1 ≤ j ≤ i , 1 ≤ i ≤ h i g h − 1 d a t a [ h i g h ] [ j ] 1 ≤ j ≤ h i g h   ,   i = h i g h dp[i][j]= \left\{ \begin{aligned} &data[i][j]+max\{dp[i+1][j],\ dp[i+1][j+1]\ \} &\quad 1\leq j \leq i, 1\leq i \leq high-1\\ &data[high][j]&\quad 1\leq j \leq high\ ,\ i = high\\ \end{aligned} \right. dp[i][j]={data[i][j]+max{dp[i+1][j], dp[i+1][j+1] }data[high][j]1ji,1ihigh11jhigh , i=high

所以,我们可以从数组底部出发,利用动态转移方程就可以解决这个问题

至于得到具体的转移路径,可以通过dp[i] [j]的计算数据(dp[i] [j]数塔)得到。即由上到下可走路线中的较大值就是最佳路径走过的点。

代码

带路径输出,便于理解的代码版本。

用path数组存储最佳路线,path[i]表示应该选择第i行的第path[i]个数作为路线上的数。

// Number of tower数塔问题
#include<stdio.h>
#include<stdlib.h> //standard_library
int max(int a,int b){
    return (a>b)?a:b;
}

int main(){
    // 数据输入
    int data[101][101];
    int high;   
    printf("h = "); scanf("%d",&high);
    printf("输入数据:\n");
    for(int i=1;i<=high;i++){
        for(int j=1;j<=i;j++){
            scanf("%d",&data[i][j]);
        }
    }

    // 动态规划求解
    int dp[101][101];
    for(int i=1;i<=high;i++)dp[high][i]=data[high][i];
    for(int i=high-1;i>=1;i--){
        for(int j=1;j<=i;j++){
            dp[i][j] = data[i][j] + max(dp[i+1][j], dp[i+1][j+1]); // 状态转移方程   
        }
    }
    // 输出结果和dp数塔
    printf("sum = %d\n",dp[1][1]);
    for(int i=1;i<=high;i++){
        printf("%*c",(high-i)*2, ' ');
        for(int j=1;j<=i;j++){
            printf("%-3d ",dp[i][j]);
        }printf("\n");
    }

    // 寻找路线
    int i,j;
    int *path = (int*)malloc(sizeof(int)*high);path[0]=1;
    for(i = 1,j = 1;i<high;i++){
        if(dp[i+1][j+1] > dp[i+1][j]){
            j++;
        }path[i] = j;
    }
    printf("path[i] = ");
    for(int i=0;i<high;i++)printf("%d ",path[i]);puts("");
    
}
/*
一个测试用例
5
    7
   3 8 
  8 1 0
 2 7 4 4
4 5 2 6 5

一个测试用例
5
      9
    12 15
   10  6  8
  2  18  9  5
 19  7  10 4 16
*/
杭电提交版本

实际上,要解决这个问题并不需要开辟两个数组,只需要在一个数组内就可以解决。

#include<stdio.h>
int max(int a, int b){
    return (a > b) ? a : b;
}
int main(){
    // 数据输入
    int n;
    scanf("%d", &n);
    int dp[101][101];
    int high;
    for(int i = 0; i<n; i++){
        scanf("%d", &high);
        for(int i = 1; i <= high; i++){
            for(int j = 1; j <= i; j++){
                scanf("%d", &dp[i][j]);
            }
        }
        // 动态规划求解
        for(int i = 1; i <= high; i++)dp[high][i] = dp[high][i];
        for(int i = high-1; i >= 1; i--){
            for(int j = 1; j <= i; j++){
                dp[i][j] += max(dp[i + 1][j], dp[i + 1][j + 1]); // 状态转移方程   
            }
        }
        // 输出结果
        printf("%d\n", dp[1][1]);
    }
    return 0;
}

代码测试

测试用例

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

结果:

在这里插入图片描述

算法心得和复杂度分析

复杂度分析:

该动态规划算法的时间复杂度主要取决于状态转移方程的计算次数,以及单次计算所耗费的时间。在这个问题中,状态转移方程的计算次数显然就是数塔中数据的个数即0.5 * h * h个,而一次计算操只需要常数时间即可完成。所以算法的时间复杂度为: O ( h 2 ) O(h^2) O(h2)

心得体会:

这个问题可以直接在原数据内进行动归计算,而不用自己另外开辟空间。
根据状态转移方程可以知,每次的dp式的结果只与上一次的dp式结果有关,如果数据从塔底处开始给出,我们就能将空间消耗缩减为O(h)。

分享的同时记录学习,有问题欢迎交流指正。
觉得有用的话就点个赞吧!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值