问题描述:
有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大
若熟悉回溯法,会发现这是个动态的决策问题:每次有两种选择—左下或右下。若用回溯法求出所有可能的路线,便可从中找出最优路线,但这样做的效率很低:一个n层数字三角形的完整路线有2^n-1条,当n很大时回溯法的
速度将让人无法忍受
具体思路:将当前位置(i,j)看成一个状态,然后定义状态(i,j)的指标函数d(i,j)为从格子(i,j)出发时能得到的最大和(包括格子(i,j)本身),在这个状态定义下,原问题的解是d(1,1)
那么,从格子(i,j)出发有两种决策,如果往左走,最好情况是(i,j)格子中的值与"从(i+1,j)出发的最大总和"之和,同理,如果往右走,最好情况是(i,j)格子中的值与“从(i+1,j+1)出发的最大总和”之和
相应状态转移方程:d(i,j)=a(i,j)+max( d(i+1,j) , d(i+1,j+1) )
方法一:(递归计算)(代码中的n为三角形层数,二维数组a中保存了三角形各个位置的数字)
int solve(int i,int j){
return a[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));
}
该方法可行但时间效率太低,原因是相同的子问题会被重复计算多次
方法二:递推计算(需注意边界处理)
int i,j;
for(i=1;i<=n;i++){
d[n][i]=a[n][i];//将三角形最后一层的数据保存下来
}
for(i=n-1;i>=1;i--){
for(j=1;j<=i;j++){
d[i][j]=a[i][j]+max(d[i+1][j],d[i+1][j+1];//从倒数第一层开始计算每个状态的最大和
}
}
方法三:记忆化搜索
将计算结果保存在数组d中,题目已知三角形中每个数均非负,故先将d中所有元素初始化为-1,若已计算过某个d[i][j],则它应是非负的,可以保证每个结点只访问一次。
int solve(int i,int j){
if(d[i][j]>=0)return d[i][j];
return d[i][j]=a[i][j]+( i == n ? 0 : max( solve( i+1,j ),solve( i+1,j+1 ) ) );
}
最终代码:
//方法一
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=105;
int array[maxn][maxn];
int d[maxn][maxn];
int n;
int main(){
int i,j;
cin>>n;
for(i=1;i<=n;i++){//读入三角形
for(j=1;j<=i;j++){
cin>>array[i][j];
}
}
for(i=1;i<=n;i++){//将三角形最后一行的数据保存下来,最后一行有n个元素,故从1到n遍历
d[n][i]=array[n][i];
}
for(i=n-1;i>=1;i--){//从倒数第二行开始计算当前最大值
for(j=1;j<=i;j++){
d[i][j]=array[i][j]+max(d[i+1][j],d[i+1][j+1]);
}
}
cout<<d[1][1];
return 0;
}
//方法二
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105;
int array[maxn][maxn];
int d[maxn][maxn];
int n;
int solve(int i,int j){
if(d[i][j]>=0)return d[i][j];//若d[i][j]已经计算,则直接返回
return d[i][j]=array[i][j]+(i==n?0:max(solve(i+1,j),solve(i+1,j+1)));//返回从当前位置出发最大的和
}
int main(){
int i,j;
memset(d,-1,sizeof(d));
cin>>n;//输入三角形层数
for(i=1;i<=n;i++){//注意第i行有i个元素
for(j=1;j<=i;j++){
cin>>array[i][j];
}
}
solve(1,1);
cout<<d[1][1]<<endl;
return 0;
}