动态规划 | 数字三角形

题目重述
描述
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
(图1)

图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。
输入
输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数。下面的N行给出数字三角形。数字三角形上的数的范围都在0和100之间。
输出
输出最大的和。
样例输入
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
样例输出
30
来源
openjudge 2760


解题思路

用二维数组来存放数字三角形:D[r][j]表示第r行第j个数字(r,j从1开始算)
maxsum(r,j):从D[r][j]到底边的各条路径中,最佳路径的数字之和
问题:求maxsum(1,1)


1.典型的递归问题
D[r][j]出发,下一步只能走D[r+1][j] 或者D[r+1][j+1]
所以可得出递归模型:

if(r == N)
maxsum(r,j) = D[r][j];
else
MaxSum(r,j) = Max{maxsum(r+1,j),maxsum(r+1,j+1)} + D[r][j];

程序代码:

#include <iostream>
using namespace std;
int MaxSum(int i,int j);//求第i行第j列到底边的最大值
int maxs(int x,int y);//返回最大值 
int D[101][101];//第i行第j列的数字
int n;//三角形列数
int main(){
    cin >> n;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            cin >> D[i][j];
    cout << MaxSum(1,1) << endl;
    return 0;
}
int maxs(int x,int y){
    return (x > y?x: y);
}
int MaxSum(int i,int j){
    if(i == n)
        return D[i][j];
    int x = MaxSum(i+1,j);
    int y = MaxSum(i+1,j+1);
    return maxs(x,y) + D[i][j];
}

这个程序的坏处:
进行了大量的重复计算,当数值大时将会超时。
假设三角形有5行,则计算的次数如下图所示(红色数字代表计算次数)

5364899301874B31965D61F0296DE010.jpg

将每一行的红色数字加起来,可得出它的时间复杂度为2^n。
计算量是巨大的。


2.改进版(1)
记忆递归型动规

思路:每算出来一个maxsum(r,j)之后就把它的值保存起来,当下次要用到该值的时候直接取用,就可以避免重复计算了,由于三角形的数字总数为n(n+1)/2。所以完成计算的时间复杂度为O(n^2)
记忆递归型动规程序如下:

#include <iostream>
#define Max  101
using namespace std;
int n;//三角形行数 
int D[Max][Max];//第i行第j列的数字
int MaxSum[Max][Max];//从第i行第j列到底部的最大值
int maxsum(int i,int j);//计算从第i行第j列到底部的最大值
int maxs(int x,int y);//返回2者中最大值 

int main(){
    cin >> n;
    //i和j从1开始 
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j){
            cin >> D[i][j];
            MaxSum[i][j] = -1;//赋初值-1   
        }
    cout << maxsum(1,1) << endl;
    return 0;
}

int maxsum(int i,int j){
    //已经计算过从第i行第j列到底部的最大值,直接拿来用,避免重复计算
    if(MaxSum[i][j] != -1)
        return MaxSum[i][j];
    
    if(i == n)//已经到底部了 
        MaxSum[i][j] = D[i][j];
    else{
        int x = maxsum(i+1,j);
        int y = maxsum(i+1,j+1);
        MaxSum[i][j] = maxs(x,y) + D[i][j];
    }
    return MaxSum[i][j];
 
         
}
int maxs(int x,int y){
    return (x > y ? x : y);
} 

3.改进版(2)
使用递推

MaxSum[][]这个数组是来存放最大值的
将递归转化为递推:先将数组的最后一行MaxSum[n][i]初始化:
MaxSum[n][i] = D[n][i]
然后再从后面递推上去

1.png

从i = n-1行开始遍历,从第1列开始,取第j列的max{正下方数字和右下方数字}(即max{MaxSum[i+1][j],MaxSum[i+1][j+1]},然后再将D[i][j]和此最大值相加,以此类推就可以得出来结果:


2.png

MaxSum[1][1]就是我们想要的答案(数组行和列都从1开始算)

程序代码:

#include <iostream>
#define Max 101
using namespace std;
int n;
int D[Max][Max];//第i行第j列的值
int MaxSum[Max][Max];//第i行第j列到底边的最大值 
int maxs(int x,int y);//返回两者的最大值 
int main(){
    cin >> n;
    for(int i=1;i<=n;++i)
        for(int j= 1;j<=i;++j)
            cin >> D[i][j];
    for(int i=1;i<=n;++i)
        MaxSum[n][i] = D[n][i];//赋初值 
    for(int i=n-1;i>=1;--i)
        for(int j = 1;j<=n;++j)
            MaxSum[i][j] = maxs(MaxSum[i+1][j],MaxSum[i+1][j+1]) + D[i][j];
    cout << MaxSum[1][1] << endl;
    return 0;
} 
int maxs(int x,int y){
    return (x > y ? x : y);
}

4.改进版(3)
空间优化

没必要用二维数组MaxSum来存储每一个maxsum(r,j),从底层一行行向上递推,只需要一个一维数组MaxSum[100]即可,即只要存储一行的maxsum值就可以

如果进一步考虑的话,可以连MaxSum数组都不要。直接用D的第n行替代maxsum即可。
该改进节省了空间,时间复杂度不变

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容