1 概述
先给出维基百科的概述:
动态规划Dynamic Programming常常适用于有重叠子问题和最优子结构性质的问题。
通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表,即前一子问题的解为后一子问题的求解提供了实用的信息。
综上:动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
可见,动态规划的本质就是备忘录。
2 适用情况
1、最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
2、无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。
3、子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
3 思想
动态规划算法的代码中经常看到dp
。可以把dp[n]理解为:当子问题的个数只有n个时,最优解的大小就是dp[n]。而当前的dp[n]会给后面的dp[n+1]、dp[n+2]…等提供参考,最终当n的值取得容器最大值时即得到最优解。
综上,使用动态规划解决问题时的思路如下:
1、将问题划分成若干小问题,且前一个小问题的结果可以给后面的问题提供信息(划分问题);
2、找规律,找出每个问题的结果对下一个问题的帮助(递推关系);
3、将最前面的几个最小的问题直接给出结果(边界情况);
4、根据递推关系,构造出每个问题的最优解(局部最优解);
5、求得全局最优解。
4 例子
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 0)
return 0;
else if (n == 1)
return nums[0];
int dp[n] = {0};
dp[0] = nums[0];
dp[1] = (dp[0] > nums[1]) ? dp[0]:nums[1];
int max = dp[1];
for(int i = 2; i < n; i++)
{
int temp = nums[i] + dp[i-2];
if(temp > dp[i-1])
dp[i] = temp;
else
dp[i] = dp[i-1];
max = dp[i];
}
return max;
}
};
dp[n]即到了第n家的时候最大偷窃金额。规律即 最大偷窃金额 = 两家前最大偷窃金额 + 当前这家可偷窃金额
。综上即可得到上边的源码。
下面再看一个例题:
max{不剪这段绳子的最大乘积, 剪这段绳子的最大乘积}
翻译为下面的代码
max(dp[n - 1], length * dp[n - length])
其中:n是绳子的长度,length是减去这一下的长度。由于减去了length,所以绳子的长度变为了n - length,这时我们来找n - length长度的绳子的最大面积就好了。这就是动态规划算法的最本质内容。
class Solution {
public:
int max(int a, int b)
{
return a > b ? a : b;
}
int cutRope(int number) {
if(number == 2 )
return 1;
vector<int> dp(number + 1,0);
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
if(number >= 3)
{
for (int n = 3; n <= number; n++)//绳子长度
{
int m = 0;
for (int length = 1; length < n; length++)
{
int temp = max(dp[n - 1], length * dp[n - length]);
if (temp > m)
m = temp;
}
dp[n] = m;
}
}
return dp[number];
}
};