1. 简介
动态规划(Dynamic Programming,DP)算法目的为解决多阶段决策最优化问题,采取的方法是将待求解的问题分解为多个子问题,按顺序求解每一个子问题,当前子问题的解将由前一个子问题的解推导出,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中,以便下一次求解同一子问题时直接查表。
动态规划求解最优化问题仅需多项式时间复杂度,比回溯法、暴力法等效率更高,当问题具有多个可行解,且要求寻找多个可行解中的最大值或者最小值时,往往可采取动态规划求解。
2. 设计步骤
动态规划的设计步骤如下:
- 寻找最优子结构:按照问题的时间或空间特征,把问题分为若干个阶段。划分后的阶段一定要有序或者可排序,否则无法求解;
- 设计递归公式,递归地定义最优值的值;
- 以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值;
- 根据计算得到的信息,构造一个最优解。
3. 实例讲解
题目:给定一个三角形队列,从最顶端往下走,寻找和最小的路径。
分析:
由于从顶端往下走有多条路径,问题具有多个可行解,并且每一条路径的和不一样,而题目要求和最小,显然该问题属于动态规划问题。
下面,按照动态规划的设计步骤解决该问题:
1. 寻找最优子结构
除了最底层外,每一层的每个节点均有两个选择,从“2”出发,可选择“3”或者“4”,因此从“2”出发的最优路径,为从“3”出发或者从“4”出发的最优路径中最小的一条。而从“3”出发的最优路径,为从“6”出发或者从“5”出发的最优路径中最小的一条。
2. 设计递归公式
根据上述分析,用表示从点出发的最优路径的和,可得递归公式:
其中,表示点的值。
3. 计算最优值
我们分别以自底向上或自顶向下两种方法求解该问题:
自底向上:由于上一层的最优解取决于下一层,因此从最底层开始计算比较容易理解,最底层的最优值就是节点本身的值,可从倒数第二层开始求解。
int minimumTotal(vector<vector<int> > &triangle) {
int row=triangle.size();
vector<vector<int> > f(triangle);
for(int x=row-2;x>=0;x--)
for(int y=0;y<=x;y++)
f[x][y]=min(f[x+1][y],f[x+1][y+1])+triangle[x][y];
return f[0][0];
}
自顶向下:该方法是递归形式的,且携带备忘录,不会计算重复子问题。
int minimumTotal(vector<vector<int> > &triangle) {
int row=triangle.size();
vector<vector<int> > f(triangle);
for(int m=0;m<row-1;m++)
for(int n=0;n<=m;n++)
f[m][n]=INT8_MAX; //与自底向上的方法不同,备忘录法必须将其初始化为标识值,以便“查找备忘录”
f[row-1]=triangle[row-1]; //最后一行保持原值
return dp(0,0,triangle,f); //从根出发
}
int dp(int x,int y,vector<vector<int> > &triangle,vector<vector<int> > &f){
if (f[x][y] != INT8_MAX)
return f[x][y]; //查找备忘录,如果已经计算过,直接返回,避免重复计算
f[x][y]=min(dp(x+1,y,triangle,f),dp(x+1,y+1,triangle,f))+triangle[x][y];
return f[x][y];
}