怎么用代码做网站,wordpress目录,portfolio做网站,网站建设人才有哪些动态规划理论基础
动态规划刷题大纲 什么是动态规划
动态规划#xff0c;英文#xff1a;Dynamic Programming#xff0c;简称DP#xff0c;如果某一问题有很多重叠子问题#xff0c;使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的英文Dynamic Programming简称DP如果某一问题有很多重叠子问题使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的这一点就区分于贪心贪心没有状态推导而是从局部直接选最优的
在关于贪心算法你该了解这些 (opens new window)中卡哥举了一个背包问题的例子。
例如有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i]得到的价值是value[i] 。每件物品只能用一次求解将哪些物品装入背包里物品价值总和最大。
动态规划中 dp[j] 是由 dp[j-weight[i]] 推导出来的然后取max(dp[j], dp[j - weight[i]] value[i])。
但如果是贪心呢每次拿物品选一个最大的或者最小的就完事了和上一个状态没有关系。
所以贪心解决不了动态规划的问题。
不用死扣动规和贪心的理论区别慢慢的就知道了
动规是由前一个状态推导出来的而贪心是局部直接选最优的
动态规划的解题步骤
状态转移公式递推公式是很重要但动规不仅仅只有递推公式。
对于动态规划问题卡哥将拆解为如下五步曲这五步都搞清楚了才能说把动态规划真的掌握了
确定dp数组dp table以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组
为什么要先确定递推公式然后在考虑初始化呢
因为一些情况是递推公式决定了dp数组要如何初始化
动态规划应该如何debug
找问题的最好方式就是把dp数组打印出来看看究竟是不是按照自己思路推导的 做动规的题目写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍心中有数确定最后推出的是想要的结果。
然后再写代码如果代码没通过就打印dp数组看看是不是和自己预先推导的哪里不一样。
如果打印出来和自己预先模拟推导是一样的那么就是自己的递归公式、初始化或者遍历顺序有问题了。
如果和自己预先模拟推导的不一样那么就是代码实现细节有问题。
这样才是一个完整的思考过程而不是一旦代码出问题就毫无头绪的东改改西改改最后过不了或者说是稀里糊涂的过了。
这也是卡哥为什么在动规五步曲里强调推导dp数组的重要性。
举个例子哈在「代码随想录」刷题小分队微信群里一些录友可能代码通过不了会把代码抛到讨论群里问我这里代码都已经和题解一模一样了为什么通过不了呢
发出这样的问题之前其实可以自己先思考这三个问题
这道题目我举例推导状态转移公式了么我打印dp数组的日志了么打印出来了dp数组和我想的一样么
如果这灵魂三问自己都做到了基本上这道题目也就解决了或者更清晰的知道自己究竟是哪一点不明白是状态转移不明白还是实现代码不知道该怎么写还是不理解遍历dp数组的顺序。
然后在问问题目的性就很强了群里的小伙伴也可以快速知道提问者的疑惑了。
注意这里不是说不让大家问问题哈 而是说问问题之前要有自己的思考问题要问到点子上
我们工作之后就会发现特别是大厂问问题是一个专业活是的问问题也要体现出专业
如果问同事很不专业的问题同事们会懒的回答领导也会认为你缺乏思考能力这对职场发展是很不利的。
所以大家在刷题的时候就锻炼自己养成专业提问的好习惯。 今天开始新的征程了加油
509. 斐波那契数
思路
动态规划
动规五部曲
这里我们要用一个一维dp数组来保存递归的结果
1、确定dp数组以及下标的含义
dp[i]的定义为第i个数的斐波那契数值是dp[i]
2、确定递推公式
为什么这是一道非常简单的入门题目呢
因为题目已经把递推公式直接给我们了状态转移方程 dp[i] dp[i - 1] dp[i - 2];
3、dp数组如何初始化
题目中把如何初始化也直接给我们了如下
dp[0] 0;
dp[1] 1;4、确定遍历顺序
从递归公式dp[i] dp[i - 1] dp[i - 2];中可以看出dp[i]是依赖 dp[i - 1] 和 dp[i - 2]那么遍历的顺序一定是从前到后遍历的
5、举例推导dp数组
按照这个递推公式dp[i] dp[i - 1] dp[i - 2]我们来推导一下当N为10的时候dp数组应该是如下的数列
0 1 1 2 3 5 8 13 21 34 55
如果代码写出来发现结果不对就把dp数组打印出来看看和我们推导的数列是不是一致的。
代码如下
class Solution {public int fib(int n) {if(n 0 || n 1){return n;}//1.确定dp数组下标含义//2.确定递推公式 dp[i] dp[i-1] dp[i-2];//3.dp数组初始化int[] dp new int[n1];dp[0] 0;dp[1] 1;//4.确定遍历顺序从前往后for(int i 2;i n;i){dp[i]dp[i-1]dp[i-2];}//5.举例推导dp数组return dp[n];}
} 70. 爬楼梯
思路
举几个例子就可以发现其规律。
爬到第一层楼梯有一种方法爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 第二层楼梯再跨一步就到第三层。
所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来那么就可以想到动态规划了。
我们来分析一下动规五部曲
定义一个一维数组来记录不同楼层的状态
1/确定dp数组以及下标的含义
dp[i] 爬到第i层楼梯有dp[i]种方法
2/确定递推公式
如何可以推出dp[i]呢
从dp[i]的定义可以看出dp[i] 可以有两个方向推出来。
首先是dp[i - 1]上i-1层楼梯有dp[i - 1]种方法那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2]上i-2层楼梯有dp[i - 2]种方法那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和
所以dp[i] dp[i - 1] dp[i - 2] 。
在推导dp[i]的时候一定要时刻想着dp[i]的定义否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性
3、dp数组如何初始化
再回顾一下dp[i]的定义爬到第i层楼梯有dp[i]种方法。
那么i为0dp[i]应该是多少呢这个可以有很多解释但基本都是直接奔着答案去解释的。
例如强行安慰自己爬到第0层也有一种方法什么都不做也就是一种方法即dp[0] 1相当于直接站在楼顶。
但总有点牵强的成分。
那还这么理解呢卡哥就认为跑到第0层方法就是0啊一步只能走一个台阶或者两个台阶然而楼层是0直接站楼顶上了就是不用方法dp[0]就应该是0.
其实这么争论下去没有意义大部分解释说dp[0]应该为1的理由其实是因为dp[0]1的话在递推的过程中i从2开始遍历本题就能过然后就往结果上靠去解释dp[0] 1。
从dp数组定义的角度上来说dp[0] 0 也能说得通。
需要注意的是题目中说了n是一个正整数题目根本就没说n有为0的情况。
所以本题其实就不应该讨论dp[0]的初始化
我相信dp[1] 1dp[2] 2这个初始化大家应该都没有争议的。
所以卡哥的原则是不考虑dp[0]如何初始化只初始化dp[1] 1dp[2] 2然后从i 3开始递推这样才符合dp[i]的定义。
4、确定遍历顺序
从递推公式dp[i] dp[i - 1] dp[i - 2];中可以看出遍历顺序一定是从前向后遍历的
5、举例推导dp数组
举例当n为5的时候dp tabledp数组应该是这样的 如果代码出问题了就把dp table 打印出来看看究竟是不是和自己推导的一样。
此时发现了这不就是斐波那契数列么
唯一的区别是没有讨论dp[0]应该是什么因为dp[0]在本题没有意义
代码如下
class Solution {public int climbStairs(int n) {if(n 1 || n2){return n;}int[] dp new int[n1];dp[1]1;dp[2]2;for(int i 3;in;i){dp[i]dp[i-1]dp[i-2];}return dp[n];}
} 746. 使用最小花费爬楼梯
思路
修改之后的题意就比较明确了题目中说 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯” 也就是相当于 跳到 下标 0 或者 下标 1 是不花费体力的 从 下标 0 下标1 开始跳就要花费体力了。
1、确定dp数组以及下标的含义
使用动态规划就要有一个数组来记录状态本题只需要一个一维数组dp[i]就可以了。
dp[i]的定义到达第i台阶所花费的最少体力为dp[i]。
对于dp数组的定义一定要清晰
2、确定递推公式
可以有两个途径得到dp[i]一个是dp[i-1] 一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] cost[i - 1]。
dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] cost[i - 2]。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢
一定是选最小的所以dp[i] min(dp[i - 1] cost[i - 1], dp[i - 2] cost[i - 2]);
3、dp数组如何初始化
看一下递归公式dp[i]由dp[i - 1]dp[i - 2]推出既然初始化所有的dp[i]是不可能的那么只初始化dp[0]和dp[1]就够了其他的最终都是dp[0]dp[1]推出。
那么 dp[0] 应该是多少呢 根据dp数组的定义到达第0台阶所花费的最小体力为dp[0]那么有同学可能想那dp[0] 应该是 cost[0]例如 cost [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 的话dp[0] 就是 cost[0] 应该是1。
这里就要说明本题力扣为什么改题意而且修改题意之后 就清晰很多的原因了。
新题目描述中明确说了 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 也就是说 到达 第 0 个台阶是不花费的但从 第0 个台阶 往上跳的话需要花费 cost[0]。
所以初始化 dp[0] 0dp[1] 0;
4、确定遍历顺序
最后一步递归公式有了初始化有了如何遍历呢
本题的遍历顺序其实比较简单简单到很多同学都忽略了思考这一步直接就把代码写出来了。
因为是模拟台阶而且 dp[i]由dp[i-1]dp[i-2] 推出所以是从前到后遍历cost数组就可以了。 但是稍稍有点难度的动态规划其遍历顺序并不容易确定下来。 例如01背包都知道两个for循环一个for遍历物品嵌套一个for遍历背包容量那么为什么不是一个for遍历背包容量嵌套一个for遍历物品呢 以及在使用一维dp数组的时候遍历背包容量为什么要倒序呢 这些都与遍历顺序息息相关。当然背包问题后续「代码随想录」都会重点讲解的
5、举例推导dp数组
拿示例2cost [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 来模拟一下dp数组的状态变化如下 如果代码写出来有问题就把dp数组打印出来看看和如上推导的是不是一样的。
代码如下
class Solution {public int minCostClimbingStairs(int[] cost) {int[] dp new int[cost.length1];dp[0] 0;dp[1] 0;for (int i 2; i cost.length; i) {dp[i] Math.min(dp[i-1] cost[i-1],dp[i-2]cost[i-2]);}return dp[cost.length];}
} 第一次接触动态规划对于一些概念和逻辑不清晰但经过第一天的引导之后整体对dp有了一个整体的认识。