动态规划
动态规划:通过组合子问题的解来求解原问题,常用来求解最优化问题。常用来解决以下几类问题,但不是说遇到类似问题必须用动态规划来解决,可以往这方面去想:
1.计数问题,如有多少种方式走到右下角,有多少种方法选出k个数使得和是sum;
2.求最大最小值,如从左上角走到右下角路径的最大数字和
3.求存在性,如取石头游戏,先手能否必胜,如能不能选出k个数使得和是sum
动态规划问题的四个解决步骤:
1.确定状态:研究最优策略的最后一步是什么,并化为子问题
2.转移方程:根据子问题定义直接得到
3.初始条件和边界条件
4.计算顺序,从已经推导出的部分去得到未知部分
下面利用一些例子来说明动态规划问题如何解决
第一个问题:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。如数组[-2,1,-3,4,-1,2,1,-5,4],最大连续子数组 [4,-1,2,1] 的和最大,为 6。
这是一个求最大最小值类型,按四个解决步骤来进行:
假设数组A有n+1个数,序号从0开始,sum数组存放当前元素结尾的最大连续子数组的和,如sum[2]表示以序号2元素结尾的最大连续子数组的和。
首先,确定状态。最优策略的最后一步是判断以A[n]结尾的连续子数组的最大和,有两种可能,A[n]+sum[n-1]和A[n],如果A[n]+sum[n-1]大于A[n],则sum[n] = A[n]+sum[n-1],反之sum[n] = A[n]
第二步,状态转移矩阵。sum[n-1]>0,sum[n] = A[n]+sum[n-1],反之,sum[n] = A[n]
第三步,初始条件,sum[0] = A[0],只有一个元素,则最大值为本身
最后一步,计算顺序。从第二步可以发现,计算顺序是从左到右。
具体代码
int maxSubArray(vector<int>& nums) {
int len = nums.size();
int sum;
int max = nums[0];
sum = nums[0];
for (int i = 1; i < len; i++)
{
if (sum > 0)
sum += nums[i];
else
sum = nums[i];
if (sum > max)
max = sum;
}
return max;
}
第二个问题:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?这是一个计数问题,假设路径数量矩阵A[m-1][n-1],序号从0开始。
第一步,确定状态。最后一步一定是判断到finish这个格子需要多少步。由于只能向下或者像右走,那么走到finish这个格子之前的那步不是它左边那个格子就是它上面那个格子。所有走到finish格子的路径数量等于走到它左边格子的路径数量加上走到它上面格子的路径数量。
第二步,状态转移方程,A[m-1][n-1] = A[m-1][n-2] + A[m-2][n-1]
第三步,初始条件和边界条件。初始条件为A[0][0] = 1, start格子只有一种到达方法,然后是第一行与第一列也只有一种到达方式,因为只能一直往右走或者一直往下走才能到达。
最后一步,计算顺序。从状态转移矩阵可以发现计算顺序是从左往右,从上往下。
具体代码
int uniquePaths(int m, int n) {
vector<vector<int>> r(m, vector<int>(n));
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (i == 0 || j == 0)
r[i][j] = 1;
else
r[i][j] = r[i - 1][j] + r[i][j-1];
}
}
return r[m - 1][n - 1];
}
从两个例子中可以发现,最优策略结果的计算可以转化为一个或多个子问题,而子问题的解也是该子问题的最优的解,并且后面问题的计算过程不会影响到前面子问题的结果(无后序性)。如果采用迭代的方法来计算结果,会有很多很多重复计算,而通过动态规划,可以避免这些重复计算,只需要计算一次,从而大大降低了时间。