问题描述:有一个由非负整数组成的三角形,第一行有一个数,除了最下行以外每个数的左下方和右下方各有个数,如下所示:
问题:从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数字加起来,如何才能使得这个和尽量大?
分析:首先,规定状态,如下:
这道题是典型的动态决策问题,每次有两种选择,左下或者右下。
首先,定义问题的状态:将当前的位置(i,j)看成一个状态,定义d(i,j)是从(i,j)出发能获得的最大值和(包括(i,j)本身的值),在这个状态定义下,原问题的解就是d(1,1)。
再看状态转移过程:从格子(i,j)出发有两种决策。如果往左下走,则走到(i+1,j)需要求出“ 从(i+1,j)出发的最大和”这一子问题,即要求出d(i,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)}
(其中a(i,j) 是格子编号(i,j)值)
解法1:(从上往下直接递归法)
代码片段如下:
int DP_MaxPathSum(int i,int j)
{
if(i == n)
return a[i][j]; // 基础解,到最后一行则返回自身的值
return d[i+1][j+1] > d[i+1][j] ? d[i+1][j+1] :d[i+1][j];
}
这样做会重复计算子问题,效率低下。如下所示:
阴影部分会被计算多次。时间复杂度是O(2^n)。
解法2:(自底向上)
int DP_MaxPathSum()
{
int i,j;
for(i = 1;i<= n;i++)
d[n][i] = a[n][i];//基础解
for(i = n-1;i>0;i--)
for(j = 1;j<=i;j++){
d[i][j] = d[i+1][j] > d[i+1][j+1] ? d[i+1][j] : d[i+1][j+1];
}
return d[1][1];
}
自底向上,计算d(i,j) 时其子问题d(i+1,j)和d(i+1,j+1)已经计算出来,其时间复杂度是O(n^2)。
解法3:(自顶向下带备忘)
首先使用memset(d,-1,sizeof(d))对d全部初始化为-1。
int max(int i,int j)
{
return i > j?i:j;
}
int DP_MaxPathSum(int i,int j)
{
if(d[i][j] >= 0)
return d[i][j]; //直接返回重叠子问题
if(i == n) {
d[i][j] = a[i][j];
return d[i][j]; //基础解
}
d[i][j] = a[i][j] + max(DP_MaxPathSum(i+1,j+1),DP_MaxPathSum(i+1,j));
return d[i][j];
}
时间复杂度是O(n^2)。
输入是数字三角形,输出是最大和。
样例输入:1 3 2 4 10 1 4 3 2 20
样例输出:24
使用语言:C++,使用方法3,完整代码如下:
//数字三角形
#include
#include
#include
using namespace std;
int arr[10000][10000]; //存放输入
int dp[10000][10000];
int n;
int max(int value1,int value2)
{
return value1>value2?value1:value2;
}
int dp_maxpathsum(int x,int y)
{
if(dp[x][y] != 0)
return dp[x][y];
if(x == 4)
return arr[x][y];
else
dp[x][y] = max(dp_maxpathsum(x+1,y),dp_maxpathsum(x+1,y+1))+arr[x][y];
return dp[x][y];
}
int main()
{
int value;
memset(arr,0,sizeof(arr));
memset(dp,0,sizeof(dp));
//输入数据
vector vec;
while(cin >> value)
vec.push_back(value);
while((n*(n+1)/2) != vec.size())
n++;
int index = 0;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=i;j++)
arr[i][j] = vec[index++];
cout <