有形如下图的一个数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一直走到底层,要求找出一条路径,使路径上的数值和最大。
分析:贪心算法不适用于此题,因为每一层的选择会影响到其下一层的选择,子解是相互影响的,很喜欢老师上课讲的一句话:在这题中我们不应该盲目地去取每一层的最优解,而是携带着先前的状态去做当前层的决策。这很好地讲述了动态规划问题的精髓,我们由下而上的解决问题时,一要保存子问题的状态,二要利用子问题的状态解决当前问题。
法一:自上而下的动态规划
在这种动态规划中,起始点唯一,终点为n
设置初始条件O(1) 取解O(n)
由于数塔结构需要考虑边界条件
#include <iostream>
#include <algorithm>
#define n 5
using namespace std;
int nums[n][n] = {
{9},
{12, 5},
{10, 6, 8},
{2, 18, 9, 5},
{19, 7, 10, 4, 16}
};
int main() {
int dp[n][n];
int res = 0;
dp[0][0] = nums[0][0]; // 初始条件:第一层只有一个节点 直接保存到dp
for (int i = 1; i < n; i++)
for (int j = 0; j <= i; j++)
if (j == 0 || j == i) // 边界条件:从塔尖到塔边路径唯一
dp[i][j] = (j == 0 ? dp[i - 1][j] : dp[i - 1][j - 1]) + nums[i][j];
else // 状态转移:其余节点挑选来源的两条路径的较大值
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + nums[i][j];
for (int j = 0; j < n; j++) // 遍历出口取最大值
if (dp[n - 1][j] > res)
res = dp[n - 1][j];
cout << "max:" << res << endl; // 输出最大数值:59
system("pause");
return 0;
}
法二:自下而上的动态规划
在这种动态规划中,起始点为n,终点唯一
设置初始条件O(n) 取解O(1)
不需要考虑边界条件
#include <iostream>
#include <algorithm>
#define n 5
using namespace std;
int nums[n][n] = {
{9},
{12, 5},
{10, 6, 8},
{2, 18, 9, 5},
{19, 7, 10, 4, 16}
};
int main() {
int dp[n][n];
int res = 0;
for (int j = 0; j < n; j++) // 初始条件:将底层的每个出发点 直接保存到dp数组
if (dp[n - 1][j] > res)
dp[n - 1][j] = nums[n - 1][j];
for (int i = n - 2; i >= 0; i--) // 边界条件:无
for (int j = 0; j <= i; j++) // 状态转移:其余节点挑选来源的两条路径的较大值
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + nums[i][j];
res = dp[0][0]; // 终点唯一
cout << "max:" << res << endl;
system("pause");
return 0;
}
总结:
①如需比较到达不同终点状态的各个路径时,使用顺序法较好。
②如需知道塔中每一点到最下层的最大值时,使用逆序法较好。