动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种情况下,分治法会做许多不必要的工作,它会反复地求解那些公共子子问题。而动态规划算法对每个子子问题只求解一次,将其解保存在一个表格中,从而无需每次求解一个子子问题时都重新计算,避免了这种不必要的计算工作。
我们通常按如下4个步骤来设计一个动态规划算法:
1.刻画一个最优解的结构特征。
2.递归地定义最优解的值。
3.计算最优解的值,通常是用自底向上的方法。
4.利用计算出来的信息构造一个最优解。
步骤1~3是动态规划算法求解问题的基础。如果我们仅仅需要一个最优解的值,而非解本身,可以忽略步骤4。如果确实要做步骤4,有时就需要在执行步骤3的过程中维护一些额外信息,以便用来构造一个最优解。
自底向上方法:这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。因而我们可以将子问题按规模排序,按由小至大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它时,它的所有前提子问题都已求解完成。
下面介绍几道线性规划的例题。
1.最经典的动态规划问题:爬楼梯问题
题目:有n阶楼梯,每次可以爬一阶楼梯或者两阶楼梯,则爬完n阶楼梯总共有多少种方法。
解析:爬n阶楼梯的方法数等于爬n-1阶楼梯方法数与爬n-2阶楼梯方法数的和。当n=1,则只有1种方法;当n=2时有2种方法。据此我们可以归纳出公式如下:
f(i)=f(i-1)+f(i-2) 3<=i<=n
f(i)=2 i=2
f(i)=1 i=1
采用自底向上的方法,从i=3开始向上递归求解即可。代码如下:
class Solution {
public:
int climbStairs(int n) {
int *result=new int[n];
result[0]=1;
result[1]=2;
for(int i=2;i<n;i++)
{
result[i]=result[i-1]+result[i-2];
}
return result[n-1];
}
};
2.m*n阶宫格求最优解问题
题目:给定m*n阶宫格,每个宫格内都填充了非负数。从宫格的左上角移动到右下角,只允许向下或者向右移动。找到一条路径,使得经过该路径的所有数字之和最小。
解析:到第i行第j列宫格的最小和为到第i行第j-1列宫格的最小和与到第i-1行第j列宫格的最小和的较小值。代码如下:
class Solution {
public:
int minPathSum(vector<vector<int> > &grid) {
int m=grid.size();
int n=grid[0].size();
int **f=new int *[m];
for(int i=0;i<m;i++)
f[i]=new int [n]; //声明二维动态数组
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
f[i][j]=0; //二维动态数组全部置0
f[0][0]=grid[0][0];
for(int i=1;i<m;i++)
f[i][0]=f[i-1][0]+grid[i][0]; // 第1列只能从上向下移动
for(int j=0;j<n;j++)
f[0][j]=f[0][j-1]+grid[0][j]; //第1行只能从左向右移动
for(int i=1;i<m;i++)
for(int j=1;j<n;j++)
{
if(f[i-1][j]<f[i][j-1])
f[i][j]=f[i-1][j]+grid[i][j];
else
f[i][j]=f[i][j-1]+grid[i][j]; //取f[i][j-1]和f[i-1][j]的较小值
}
return f[m-1][n-1];
}
};
3.求数组中连续子数组的最大和
举例,给定如下数组:[-2,1,-3,4,-1,2,1,-5,4],则连续子数组[4,-1,2,1]具有最大的和,为6。
解析:已知了前k个元素的最大子序列和为maxSub(已经被记录下来了),以及一个临时和sum,如果添加了第k+1这个元素,由于是连续子序列这个限制,所以如果k+1这个元素之前的和是小于0的,那么对于增大k+1这个元素从而去组成最大子序列是没有贡献的,所以可以把sum 置0。代码如下:
class Solution {
public:
int maxSubArray(int A[], int n) {
int sum=A[0],max=A[0];
for(int i=1;i<n;i++)
{
sum=sum<0?A[i]:sum+A[i];
max=sum>max?sum:max;
}
return max;
}
};