当前位置:   article > 正文

算法设计思想——动态规划_动态规划法的基本思路

动态规划法的基本思路

动态规划Dynamic Programming,简称DP)

是一种常见的算法设计方法,用于解决一类重叠子问题的优化问题。他的基本思想是将问题分解成多个重叠的子问题,递归求解,并将子问题的求解缓存起来,避免重复计算,从而得到问题的解。

动态规划通常适用于以下两个条件的问题:
1.重叠子问题:原问题可以分解为若干个子问题,子问题之间存在重叠部分,即某些问题的解在求解过程中被多次引用。

2.最优子结构:原问题的最优解可以由子问题的最优解推导得到,即原问题的最优解包含子问题的最优解。

动态规划算法的基本步骤如下:

  1. 定义状态:将原问题划分为若干个子问题,并定义状态表示子问题的解。
  2. 定义状态转移方程:根据子问题之间的关系,建立子问题之间的递推关系式,即状态转移方程。
  3. 初始化状态:确定初始化的值,即最简单的子问题的解。
  4. 计算状态:按照状态转移方程,从简单的子问题开始递推,计算出所有子问题的解。
  5. 求解原问题:根据子问题的解,求出原问题的解 。

动态规划算法的时间复杂度通常为O\left ( n^{2} \right )O\left ( n^{3} \right ),取决于问题的规模和状态转移方程的复杂度。

下面来看例题:

假设你正在爬楼梯。需要n阶你才能到达楼顶。

每次你可以爬 1或 2 个台阶。那么一共有多少种不同的方法可以爬到楼顶呢?

下面以动态规划五部曲原则来进行构思:

  1. 定义状态:需要定义一个一维数组来记录不同楼层的状态,因此我们需要——确定St数组以及下标的含义        St[i]:爬到第i层,有St[i]种方法
  2. 确定递推公式:首先如果是 st[i-1],上 i-1 层楼梯,有 st[i-1] 种方法,那么再一步跳一个台阶就是 St[i];如果是 St[i-2],上 i-2 层楼梯,有 St[i-2] 种方法,那么再跳一步两个台阶就是St[i],于是st[i] = st[i-1] + st[i-2]。
  3. St数组的初始化:我们需要考虑为St为0的情况,按照题干来理解,当楼层为0的时候,我们什么也不用做,于是i就为 0。有一个争议点就是:当St[0]应该为1的理由其实是因为dp[0] =1的话在递推的过程中 i 从 2 开始遍历才能执行下去,实际从题干出发,n是正整数,那么i也应该为为正整数,所以不需要讨论St[0]的初始化,我们直接从dp[1] = 1;dp[2] =2,然后从 i = 3开始递推。
  4. 确定遍历顺序:由递推公式st[i] = st[i-1] + st[i-2]得到,遍历顺序从前向后
  5. 举例推导St数组:当例如,我们n = 6时,st table如下:

没错,这其实就是斐波那契数列(Fibonacci sequence),五部曲分析完成后,我们的代码如下:

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. class MyMethod {
  5. public:
  6. int clbStairs(int n){
  7. if (n == 1) return 1;
  8. if (n == 2) return 2;
  9. vector<int> st(n+1);
  10. st[1] = 1;
  11. st[2] = 2;
  12. for (int i = 3; i <= n; i++){
  13. st[i] = st[i-1] + st[i-2];
  14. }
  15. return st[n];
  16. }
  17. };
  18. int main(){
  19. int n = 6;
  20. int ret = MyMethod().clbStairs(n);
  21. cout << ret << endl;
  22. return 0;
  23. }

 时间复杂度(time complexity):O\left ( n \right )

空间复杂度(space complexity):O(n)

考虑到代码优化的话,我们用sum减少数据结构,使用常量来保存状态,代码如下:

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. class MyMethod {
  5. public:
  6. int clbStairs(int n){
  7. if (n == 1) return 1;
  8. if (n == 2) return 2;
  9. vector<int> st(n+1);
  10. st[1] = 1;
  11. st[2] = 2;
  12. for (int i = 3; i <= n; i++){
  13. int sum = st[1] + st[2];
  14. st[1] = st[2];
  15. st[2] = sum;
  16. }
  17. return st[2];
  18. }
  19. };
  20. int main(){
  21. int n = 6;
  22. int ret = MyMethod().clbStairs(n);
  23. cout << ret << endl;
  24. return 0;
  25. }

 时间复杂度(time complexity):O\left ( n \right )

空间复杂度(space complexity):O\left ( 1 \right )

总结

虽然这道题的实质是斐波那契数列,但理解到动态规划的程序设计思路其实没那么轻松,关键是能够迅速捕捉到这一概念,进行建模,按照动态规划五部曲的递推公式,逐步推导得到结果。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/601270
推荐阅读
相关标签
  

闽ICP备14008679号