数字三角形
有一些非负数组成的三角形,第一行只有一个数,除了最下行之外每个数左下方和右下方各有一个数如下
1
3 2
4 10 1
4 3 2 20
从第一行的数开始。每行可以左下或右上走一格,直到走到最下行,把沿途经过的数全部加起来,最大数会为多少?
(0,0) | |||
(1,0) | (1,1) | ||
(2,0) | (2,1) | (2,2) | |
(3,0) | (3,1) | (3,2) | (3,3) |
分析:显然n层数字三角形的走法有2^n种,于是穷举法显然不行。用d[i][j]表示从格子(i,j)出发时获得的最大效益,于是问题的解为d[0][0]的解的效益,那摩下一步有两种选择,左下走或者右下走,如果左下走那摩问题转化为求从(i+1,j)走到底层的最大效益即为d[i+1,j],如果右下方走那摩转换为从(i+1,j+1)走到底层的效益,即为d[i+1,j+1],那摩我们选择的自然是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]};
每一个子序列必须是最优子结构,全局最优解包含局部最优解
直接用递归的方法计算效率非常低,原因是相同的之问题被重复计算了多次,于是选择将需要的子结构记忆性存起来,选择递推
#include<iostream>
using namespace std;
int Max(int a,int b){
return a > b ? a : b;
}
#define n 4
int a[n][n];
int d[n][n];
int main(){
int i, j;
for (i = 0; i < n; i++)
for (j = 0; j <= i; j++)
cin >> a[i][j];
for (i = 0; i < n; i++)
/*边界初始化*/
d[n - 1][i] = a[n - 1][i];
for (i = n - 2; i >= 0; i--)
for (j = 0; j <= i; j++)
d[i][j] = a[i][j] + Max(d[i + 1][j], d[i + 1][j + 1]);//自低向上计算
cout << d[0][0];
return 0;
}
方法二,记忆化搜索,使用递归
#include<iostream>
using namespace std;
int Max(int a,int b){
return a > b ? a : b;
}
#define n 4
int a[n][n];
int d[n][n];
int D(int (*a)[n], int i, int j);
int main(){
int i, j;
memset(d, -1, sizeof(d));//批量赋值为-1
for (i = 0; i < n; i++)
for (j = 0; j <= i; j++)
cin >> a[i][j];
cout << D(a,0, 0) << endl;
return 0;
}
int D(int (*a)[n],int i, int j){
if (d[i][j]>0)return d[i][j];//如果d[i][j]>0说明d[i][j]已经计算过,无需再计算
/*赋值表达式的值为左值,递归调用*/
return d[i][j] = a[i][j] + (i == n - 1 ? 0 : Max(D(a, i + 1, j), D(a, i + 1, j + 1)));
}
如果已经计算过的就为非负数,于是不需要在计算,可以算到每个节点访问一次,时间复杂度为O(n^2),称为记忆法