问题描述与状态定义
数字三角形问题。 有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,如图所示。
从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走才能使得这个和尽量大?
分析
首先我们遇到这种问题的时候,先对问题进行抽离出本源,再去对问题求解。
针对这道题目,我们读完题目大概了解道: 我们需要对图里面的元素进行相应的操作,使得连续的元素之和达到最大。这个时候,我们可以将图中元素抽离为二维数组里面的每一个元素。
如图所示
编号规则:第一行第一个为a[1][1],第二行第一个为a[2][1],第二行第二个为a[2][2]…依次类推。
这里的二维数组a[i][j]是编号为i,j里面的数,而二维数组D[i][j]是编号为i,j的这个节点下面最大的和。
我们通过图可以清楚的了解到这是一类最优子问题,在这里需要用到动态规划的相关问题了,而对于动态规划而已,首先最关键的就是状态和状态转移方程,在这里可以知道
状态: (i,j)格子的最优解就是当前格子的元素加上“从(i,j)格子出发的最大的和”
状态转移方程:D[i][j]= a[i][j] + max{D[i+1][j] , D[i+1][j+1]}
这里的D[i+1][j]和D[i+1][j+1]分别是a[i][j]的左右元素。
记忆化搜索与递推
有了状态转移方程,那么应该怎么计算呢?
方法一: 递归方法
int solve(int i, int j)
{
return a[i][j] + ( i == n ? 0 : max( solve(i+1,j),solve(i+1,j+1) ));
}
这里的递归方法主要就是在状态转移方程下增加了边界条件: 当遍历到最下面的一行时,使返回值为0, 这里就是用三目运算符实现的。
这里是完整的代码。
#include <iostream>
using namespace std;
int a[100][100]={0};
int b[100][100]={-1};
int n;
int solve(int i, int j);
int main()
{
cin>>n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
cin>>a[i][j];
cout<<solve(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) ));
}
输入:
4
1
3 2
4 10 1
4 3 2 20
预期输出结果:
24
这里通过了该组样例。
方法二: 递推计算
由于在第一种方法里面,会重复计算入度为2的节点,然后考虑到树的节点N和树的高度h之间的关系是指数关系,随着高度的增加,节点N会跟着爆炸式增长,那么原本N的二次方的时间复杂度就会变成2的N次方的时间复杂度。会大大增加,那么就会有大量的不必要的运算。所以第二种方法就是为了避免了这种情况的出现。
int i,j;
for(j = 1; j <= n;j++)
d[n][j] = a[n][j];
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是最后得到的结果数组,a为输入的数组。
完整的代码如下所示:
#include <iostream>
using namespace std;
int main()
{
int a[100][100] = {0};
int d[100][100] = {-1};
int n;
cin>>n;
for(int i = 1; i <= n; i++ )
for(int j = 1; j <= i; j++)
cin>>a[i][j];
int i,j;
for(j = 1; j <= n;j++)
d[n][j] = a[n][j];
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]);
cout<<d[1][1];
}
输入:
4
1
3 2
4 10 1
4 3 2 20
预期输出结果:
24
程序通过了该组样例。
方法三: 记忆化搜索
这个方法就是在第一种方法的基础上做出一点变化,在d的初始化时,给出初始值-1,由于计算过的d不太可能为负数,所以这也就成为了作为唯一判断的标准。
int solve(int i, int j)
{
if(d[i][j] > 0) return d[i][j];
return a[i][j] + ( i == n ? 0 : max(solve(i+1,j+1),solve(i+1,j)));
}
完整代码:
#include <iostream>
using namespace std;
int a[100][100]={0};
int d[100][100]={-1};
int n;
int solve(int i, int j);
int main()
{
cin>>n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
cin>>a[i][j];
cout<<solve(1,1);
}
int solve(int i, int j)
{
if(d[i][j] > 0) return d[i][j];
return a[i][j] + ( i == n ? 0 : max(solve(i+1,j+1),solve(i+1,j)));
}
该组数据通过了样例。
总结
最终用了三种方法得到了结果,分别是递归,递推,记忆性递归
后面两种方法都是在第一种方法的基础上得到的,并且都是旨在改进时间复杂度,建议把第一种弄懂,因为只要第一种懂了,那么第二种和第三种就都水到渠成了。
那么,小编分享的动态规划的题目就讲到这里了。请期待下一次的更新啦!