一只一直没有搞清楚递归和动态规划小白,看了几篇博客之后,有了一点点感觉了,简单记录下
动态规划-钢条切割问题
长度为n的钢条可以有2n-1中切割方案,所以当n很大时用暴力求解的方法是行不通的。假设将该钢条切割成k(k的取值为[1,n])段,切割顺序为从钢条的左端开始,那么最有切割方案是:
n = len1+len2+len3+…+lenk
将钢条切割为长度是len1,len2,…,lenk的小段,得到最大收益
Rn = p len1+p len2+p len3+…+p lenk
思路一:
我们可以假设长度为n的钢条的最大收益值:
rn = max(pn,r1+rn-1,r2+rn-2,…,rn-1+r1),pn为不切割时的价格。
这样可以通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。
思路二:
将钢条从左边切割长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割(递归求解),对左边的一段则不再进行分割。
Rn = max(pi + Rn-i) (i取值范围[1,n])
在这个公式中,原问题的最优解只包含一个相关子问题(右端剩余部分)的解。
使用三种方法来实现思路二
方法一
1.自顶而下的递归
class Solution1{//递归求解;
public:
int maxProfit(vector<int> price,int n){
if (n <= 0)
return 0;
int profit = 0;
for (int i = 1; i <= n; ++i)
profit = max(profit, price[i - 1] + maxProfit(price,n - i));
return profit;
}
};
在递归实现中,有大量的重复计算,如当n==4时候,如下图所示:
时间复杂度约为2的n次方。
2. 带备忘录的自顶向下递归
因为方法一 中的递归有大量的重复计算,所以我们将每次计算的结果存下来,这样就避免了每次都要重复计算的问题
class Solution2{//带备忘录的自顶向下递归;
public:
int MemeriedMaxProfit(vector<int> price, vector<int> &memery, int n){
if (memery[n] > 0)
return memery[n];
int profit = 0;
for (int i = 1; i <= n; ++i)
profit = max(profit, price[i - 1] + MemeriedMaxProfit(price, memery, n - i));
memery[n] = profit;
return memery[n];
}
};
看到很多博客说这是动态规划实现另一个版本。我是这么理解的,我觉得动态规划的核心就是将记住已经解决过的子问题的解。从这个角度上说的话这种带备忘录的自顶而下的递归可是归为动态规划中(小白,纯属瞎掰 ,有问题欢迎指正,不胜感激)。
3. 自底向上的动态规划
int DPMaxProfit(vector<int> price, vector<int> &memery,int n){
if (n == 0)
return 0;
for (int i = 1; i <= n; ++i){
int profit = 0;
for (int j = 1; j <= i; ++j)
profit = max(profit, price[j - 1] + memery[i - j]);//子问题的最优解;
memery[i] = profit;
}
return memery[n];
}
动态规划原理
①最优子结构
用动态规划求解最优化问题的第一步就是刻画最优解的结构,如果一个问题的解结构包含其子问题的最优解,就称此问题具有最优子结构性质。因此,某个问题是否适合应用动态规划算法,它是否具有最优子结构性质是一个很好的线索。使用动态规划算法时,用子问题的最优解来构造原问题的最优解。因此必须考查最优解中用到的所有子问题。
②重叠子问题
如果递归算法反复求解相同的子问题,就称为具有重叠子问题(overlapping subproblems)性质。在动态规划算法中使用数组来保存子问题的解,这样子问题多次求解的时候可以直接查表不用调用函数递归。
对于一个动态规划问题
第一步先确定最优解的结构。如果一个问题的解结构包含其子问题的最优解,就称此问题具有最优子结构性质。使用动态规划算法时,用子问题的最优解来构造原问题的最优解。因此必须考查最优解中用到的所有子问题。
第二步定义最优解的计算公式
第三步根据得到的求最优解公式,计算出结果。
第四步构造出最优解。