小小小幸运 发表于 2024-9-7 21:59:03

代码随想录Day 32|leetcode题目:501.斐波那契数、70.爬楼梯、746.使用最小

提示:DDU,供自己复习使用。接待各人前来讨论~

   
动态规划理论根本

开始动态规划算法
一、理论根本

1.1 什么是动态规划

​ 动态规划,英文:Dynamic Programming,简称DP,如果某一题目有很多重叠子题目,使用动态规划是最有效的。
​ 所以动态规划中每一个状态肯定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的
1.2 动态规划的解题步骤

对于动态规划题目,将拆解为如下五步曲,这五步都搞清晰了,才气说把动态规划真的把握了!

[*]确定dp数组(dp table)以及下标的含义
[*]确定递推公式
[*]dp数组怎样初始化
[*]确定遍历序次
[*]举例推导dp数组
1.3 动态规划应该怎样debug

找题目标最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
做动规的题目,写代码之前肯定要把状态转移在dp数组的上具体情况模拟一遍,胸有定见,确定最后推出的是想要的效果。
二、题目

题目一: 509. 斐波那契数

509. 斐波那契数
解题思路:

动态规划

动规五部曲:
这里我们要用一个一维dp数组来保存递归的效果

[*]确定dp数组以及下标的含义
dp的定义为:第i个数的斐波那契数值是dp

[*] 确定递推公式
[*] dp数组怎样初始化
int n = 4;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
      cout << i << " " << j << endl;
    }
}

[*]确定遍历序次
从递归公式dp = dp + dp;中可以看出,dp是依赖 dp 和 dp,那么遍历的序次肯定是从前到后遍历的

[*]举例推导dp数组
按照这个递推公式dp = dp + dp,我们来推导一下,当N为10的时间,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
如果代码写出来,发现效果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。
以上我们用动规的方法分析完了,C++代码如下
class Solution {
public:
    int fib(int N) {
      if (N <= 1) return N;
      vector<int> dp(N + 1);
      dp = 0;
      dp = 1;
      for (int i = 2; i <= N; i++) {
            dp = dp + dp;
      }
      return dp;
    }
};


[*]时间复杂度:O(n)
[*]空间复杂度:O(n)
我们只需要维护两个数值就可以了,不需要记录整个序列。
代码如下:
class Solution {
public:
    int fib(int N) {
      if (N <= 1) return N;
      int dp;
      dp = 0;
      dp = 1;
      for (int i = 2; i <= N; i++) {
            int sum = dp + dp;
            dp = dp;
            dp = sum;
      }
      return dp;
    }
};


[*]时间复杂度:O(n)
[*]空间复杂度:O(1)
递归解法



[*]递归函数的返回值以及参数
在这里要定义两个全局变量,一个用来存放符合条件单一效果,一个用来存放符合条件效果的集合。
class Solution {
public:
    int fib(int N) {
      if (N < 2) return N;
      return fib(N - 1) + fib(N - 2);
    }
};


[*]时间复杂度:O(2^n)
[*]空间复杂度:O(n),算上了编程语言中实现递归的体系栈所占空间
题目二:70. 爬楼梯

70. 爬楼梯
解题思路:

本题如果没有打仗过的话,会感觉比较难,多举几个例子,就可以发现其规律。
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。
所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来,那么就可以想到动态规划了。
动规五部曲:

[*]确定dp数组以及下标的含义
dp: 爬到第i层楼梯,有dp种方法

[*]确定递推公式
dp = dp + dp

[*] dp数组怎样初始化
[*] 确定遍历序次
从递推公式dp = dp + dp;中可以看出,遍历序次肯定是从前向后遍历的

[*]举例推导dp数组
举例当n为5的时间,dp table(dp数组)应该是这样的
https://img-blog.csdnimg.cn/img_convert/eb359367b63106da9e48a7eadab7c026.png // 版本一
class Solution {
public:
    int climbStairs(int n) {
      if (n <= 1) return n; // 因为下面直接对dp操作了,防止空指针
      vector<int> dp(n + 1);
      dp = 1;
      dp = 2;
      for (int i = 3; i <= n; i++) { // 注意i是从3开始的
            dp = dp + dp;
      }
      return dp;
    }
};


[*]时间复杂度:                                        O                            (                            n                            )                                  O(n)                     O(n)
[*]空间复杂度:                                        O                            (                            n                            )                                  O(n)                     O(n)
优化一下空间复杂度,代码如下:
// 版本二
class Solution {
public:
    int climbStairs(int n) {
      if (n <= 1) return n;
      int dp;
      dp = 1;
      dp = 2;
      for (int i = 3; i <= n; i++) {
            int sum = dp + dp;
            dp = dp;
            dp = sum;
      }
      return dp;
    }
};


[*]时间复杂度:                                        O                            (                            n                            )                                  O(n)                     O(n)
[*]空间复杂度:                                        O                            (                            1                            )                                  O(1)                     O(1)
题目三: 746. 使用最小泯灭爬楼梯

746. 使用最小泯灭爬楼梯
解题思路


[*] 定义dp数组:

[*]dp 表现到达第 i 个台阶所需的最少体力。

[*] 建立递推关系:

[*]每个台阶 i 可以从台阶 i-1 或 i-2 跳上来。
[*]dp = min(dp + cost, dp + cost)。

[*] 初始化dp数组:

[*]dp 和 dp 都初始化为 0,因为到达第一个台阶不需要体力。

[*] 确定遍历序次:

[*]从第一个台阶开始,逐个计算每个台阶的 dp 值。

[*] 举例说明:

[*]通过一个具体的例子(如 cost = ),展示 dp 数组怎样渐渐构建。
具体的效果如下图所示:

https://img-blog.csdnimg.cn/img_convert/ebf45390243858fc5832b7535d5d6c85.png class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
      vector<int> dp(cost.size() + 1);
      dp = 0; // 默认第一步都是不花费体力的
      dp = 0;
      for (int i = 2; i <= cost.size(); i++) {
            dp = min(dp + cost, dp + cost);
      }
      return dp;
    }
};


[*]时间复杂度:O(n)
[*]空间复杂度:O(n)
还可以优化空间复杂度,因为dp就是由前两位推出来的,那么也不用dp数组了,C++代码如下:
// 版本二
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
      int dp0 = 0;
      int dp1 = 0;
      for (int i = 2; i <= cost.size(); i++) {
            int dpi = min(dp1 + cost, dp0 + cost);
            dp0 = dp1; // 记录一下前两位
            dp1 = dpi;
      }
      return dp1;
    }
};


[*]时间复杂度:O(n)
[*]空间复杂度:O(1)
总结



[*]动态规划五部曲:动态规划数组和下标的定义,递归公式,动态数组的初始化,确定遍历序次,推导数组。
[*]动态规划的一些技巧,能维护变量就不要维护数组。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 代码随想录Day 32|leetcode题目:501.斐波那契数、70.爬楼梯、746.使用最小