代码随想录算法训练营day38|第九章 动态规划part01

本文介绍了动态规划的基础理论,强调了状态转移的重要性,并通过斐波那契数和爬楼梯问题展示了动规五步曲的应用,包括确定dp数组、递推公式、初始化和遍历顺序。
摘要由CSDN通过智能技术生成

理论基础 

无论大家之前对动态规划学到什么程度,一定要先看 我讲的 动态规划理论基础。 如果没做过动态规划的题目,看我讲的理论基础,会有感觉 是不是简单题想复杂了? 其实并没有,我讲的理论基础内容,在动规章节所有题目都有运用,所以很重要!  如果做过动态规划题目的录友,看我的理论基础 就会感同身受了。

文章:

代码随想录

视频:从此再也不怕动态规划了,动态规划解题方法论大曝光 !| 理论基础 |力扣刷题总结| 动态规划入门_哔哩哔哩_bilibili

如果某一问题有很多重叠子问题,使用动态规划是最有效的。动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。

动态规划问题五步曲:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

——递推公式很重要,但是dp数组的初始化以及正确的遍历顺序也很重要。

做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果。如果代码没通过就打印dp数组,动态规划debug最好的方式是把dp数组打印出来,看看究竟是不是按照自己思路推导的。

通过不了,就思考这三个问题:

  • 这道题目我举例推导状态转移公式了么?
  • 我打印dp数组的日志了么?
  • 打印出来了dp数组和我想的一样么?

509. 斐波那契数 

很简单的动规入门题,但简单题使用来掌握方法论的,还是要有动规五部曲来分析。

文章:

代码随想录

视频:手把手带你入门动态规划 | LeetCode:509.斐波那契数_哔哩哔哩_bilibili

这道题比较简单,主要是熟悉一下动规五部曲。对于这道题,可以指维护两个值,因为只要知道前一个数和前前一个数即可算出。除此之外还可以使用递归函数来解决。

int fib(int N) {
        if (N <= 1) return N;
        vector<int> dp(N + 1);
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= N; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[N];
    }
int fib(int N) {
        if (N <= 1) return N;
        int dp[2];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= N; i++) {
            int sum = dp[0] + dp[1];
            dp[0] = dp[1];
            dp[1] = sum;
        }
        return dp[1];
    }

70. 爬楼梯   

本题大家先自己想一想, 之后会发现,和 斐波那契数 有点关系。

文章:

代码随想录

视频:带你学透动态规划-爬楼梯(对应力扣70.爬楼梯)| 动态规划经典入门题目_哔哩哔哩_bilibili

这道题就是要考虑到dp数组的意义是每一层的方法数,这样问题就迎刃而解,当前楼层的方法数就是前一层的方法数+前前一层的方法数(因为这道题一次只能上一个或两个台阶,所以只有这层楼的前一个和前前一个才能直接登上这层楼,考虑的是哪一层是倒数第二层,而倒数第二层只能有一种方法登上这一层(并不是说经过前前一层只能有一种方法到达这一层,实际上并不是,而是以前前一层为倒数第二层这样直接到达当前层,考虑前一种的话,实际上是将dp[n-2]的一部分也算进去了,就重复了),故而其实和斐波那契数列很相似了。(所以也可以像前一道题一样简化)

int climbStairs(int n) {
        if (n <= 1) return n; // 因为下面直接对dp[2]操作了,防止空指针
        vector<int> dp(n + 1);
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) { // 注意i是从3开始的
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

这是文章里面的拓展,题目变成了一次不一定迈多少阶楼梯,其实大差不差,需要考虑到这一层的方法数是从迈最大步子能一步到达这一层的楼层的方法数累加起来,和上面一个道理。

int climbStairs(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) { // 把m换成2,就可以AC爬楼梯这道题
                if (i - j >= 0) dp[i] += dp[i - j];
            }
        }
        return dp[n];
    }

746. 使用最小花费爬楼梯 

这道题目力扣改了题目描述了,现在的题目描述清晰很多,相当于明确说 第一步是不用花费的。 更改题目描述之后,相当于是 文章中 「拓展」的解法 

文章:

代码随想录

视频讲解:动态规划开更了!| LeetCode:746. 使用最小花费爬楼梯_哔哩哔哩_bilibili

  1.  dp数组的意义稍有区别,代表到达每一层的花费,注意dp数组的长度是cost长度+1,这样dp[n]才能代表到达第cost.size()+1层的花费。
  2. 递推公式发生了改变,要取min(到达前一层/ 前前一层的花费 + 从前一层/ 前前一层到达这一层的花费),取最小花费。
  3. 初始化也改变,这道题相当于是从0层开始上,从0层开始到达第一/ 二层都是不花费的,但是从第一/ 二层往下一层走就要花费了,相当于买路钱,留了买路钱才能从这里过,但是如果不过去,只是到达这里的话,是不需要花费的。
  4. 最后要返回dp数组的最后一个值,也就是到达最后一层的最小花费。
  5. 同样因为当前dp数组值推导只需要它的前两个值,所以也可以化简。
int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size() + 1);
        dp[0] = 0; // 默认第一步都是不花费体力的
        dp[1] = 0;
        for (int i = 2; i <= cost.size(); i++) {
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[cost.size()];
    }
int minCostClimbingStairs(vector<int>& cost) {
        int dp0 = 0;
        int dp1 = 0;
        for (int i = 2; i <= cost.size(); i++) {
            int dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2]);
            dp0 = dp1; // 记录一下前两位
            dp1 = dpi;
        }
        return dp1;
    }

拓展的解法其实就是换了dp数组的意义,但是还是能通过的。

注意到这次dp数组的长度是cost.size()了,是因为这次dp数组不再是到达本层需要的花费了,而是到达本层而且还要往上走的花费,所以最后返回的不直接是是dp数组的值了,而是最后二者取较小者(到达顶层之后就不需要往上走了)。

int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size());
        dp[0] = cost[0]; // 第一步有花费
        dp[1] = cost[1];
        for (int i = 2; i < cost.size(); i++) {
            dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
        }
        // 注意最后一步可以理解为不用花费,所以取倒数第一步,第二步的最少值
        return min(dp[cost.size() - 1], dp[cost.size() - 2]);
    }
  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值