动态规划总结

动态规划

概述

​ 动态规划区别于贪心的是它的当前每一个状态(除了初始化)一定是由上一个状态推导出来的。
​ 动规解题步骤:1:确定dp数组及其下标的含义;2:确定递推公式;3:dp数组如何初始化;4:确定遍历顺序;5:举例推导dp数组。

基础题目

​ 509.斐波那契数:这道题比较简单,可以作为熟悉动规步骤的题目来做,只需要维护两个变量就可以了。
​ 70.爬楼梯:这道题虽然和上面那道题相同,但是又要难一点,因为你要自己分析出来递推公式,也可以改进一点,即一步可以走的楼梯增加一个,这样可以用一个双重循环,在第二重循环依次加前面的台阶的方法数。
​ 746.使用最少花费爬楼梯:也是简单的动规问题,也可以对空间进行优化,反正考虑花费然后选取最小的即可。注意要遍历到size。
​ 62.不同路径:这道题也是比较普通的动规题目,锻炼一下写动规的步骤,不过可以使用一个一维数组来解决问题,只要当前状态的推导只用到上一行以及当前行前面的,就可以用一维数组。
​ 63.不同路径II:这道题和上面也差不多,只是在遍历的时候要检查一下是否有障碍,且使用一维数组的话,初始化要小心一点,而且在遍历的过程中还要计算一下dp[0]。
​ 343.整数拆分:感觉这道题应该算是有点难度的,还是按照步骤解题,dp[i]表示i的最大拆分相乘值,它的递推方程要带一个循环,因为要把它一直拆分然后循环比较最大的结果,注意拆分也要比较是直接相乘大还是用dp相乘大。其实这种带循环的递推方程有很多,例如前面前面爬楼梯的进化版本,一般带循环都是要多次比较或者多次相加。
​ 96.不同的二叉搜索树:这道题还是比较有难度,重点是当新加一个数后,怎么利用前面的结果算出新加这个数后的结果。首先新加的这个数可以在前面每一种情况下加入,所以我们要清除前面有多少种情况,那么如何算前面的情况呢,其实是分类讨论的思想,即前面的数依次做头结点,我们把每一个数做头结点的搜索树的数量算出来,然后当前数再添加结果数也不变,那么如何算每一个数做头结点的树的数量呢,其实就是该树左子树的数量乘以右子树的数量,根据头结点可以知道左子树的结点数和右子树的结点数,又因为我们已经知道了前面的结果,所以直接相乘再相加即可,最后讨论新加入的数作为头结点也是相同的道理。所以这相当于带循环的递推方程。

背包问题

01背包

​ 理论基础

​ 01背包可概括为有一容量有限的背包,去装一系列物品,这些物品有相应的大小和价值,每个物品只能装一次,问怎么装是背包中物品价值总和最大。如果是使用二维数组来解决01背包问题,那么要注意初始化,遍历顺序倒是怎么都可以,还要注意要判断当前重量是否大于当前物品重量。如果是用一维数组来初始化,一定要先遍历背包再倒序遍历重量,防止上一行的数据被覆盖。关于遍历顺序以及是否可以使用一维数组的问题,主要是心里面要想到那个矩阵以及看看每一个元素的推导需要矩阵哪里的元素作为基础。
​ 416.分割等和子集:首先要看出来这是一道01背包的题目,好久没写01背包了,写这个还花了蛮长的时间,之后就用一维数组来写吧,二维数组有点麻烦。
​ 1049.最后一块石头的重量II:这道题和416是一样的,可能有一点区别的就是现在是求一半重量的背包大小最多能放多少重量石头,当然也要自己清楚这么转化才做得出来。
​ 494.目标和:这道题要转化成01背包还是有点难度,其实要凑成目标和就是在nums里面找一些正数然后减去剩下的负数,即positive - negative = target,而position + negative = sum,所以就是positive - (sum - positive) = target,容易看出positive = (sum + target) / 2;所以我们想算出positive,然后用nums里面的值来装positive看看有多少种装法,这个问有多少种装法的递推公式是dp[j] += dp[j - nums[i]]。
​ 474.1和0:这个题要比普通的01背包难一点,因为01背包要考虑的主要是背包的重量,即dp数组可以是一维的,而这道题dp数组至少得是两维的。因为限制有两个,写循环的时候小心一点,两维都能倒着循环。

完全背包

​ 理论基础
​ 完全背包和01背包不同的地方就在于完全背包的每个物品可以无限取,所以如果用二维数组来做的话,会再加一个循环,即循环判断不取当前物品,取一个、两个…直到装不下,如果用一维dp的话就比较简单,直接遍历重量的时候从小到大遍历即可,注意一维的就可以更换物品和背包的遍历顺序了。但是先遍历物品再遍历背包求的是组合的情况,而先遍历背包再遍历物品求的是排列的情况。
​ 518.零钱兑换:经典的完全背包问题,首先还是用一维数组要简单写,其次求方法要用加法,最后就是这是组合所以要先遍历物品再遍历背包。
​ 377.组合总数IV:这道题名为组合总数,其实求的是排列总数,还是用完全背包,注意可能会超过INT_MAX。所以要做一个判断。
​ 322.零钱兑换:这道题主要有两个容易错的点,一是初始化时除了dp[0]其他全部要初始化为INT_MAX,第二是循环中要判断是否超出INT_MAX了。
​ 279.完全平方数:这道题可以看出是一道完全背包问题,关键是物品是什么,当然,物品是完全平方数,但是这个完全平方数我们怎么得到的,其实就是依次遍历,然后每次把物品看作是i*i,这样就可以得到物品,注意物品那一层的循环终止条件是i * i <= n,这题不用区分遍历顺序。
​ 139.单词拆分:这道题是把单词作为物品,然后字符串作为背包,思维清晰点还是比较容易做出来,但是要注意,虽然这道题没有要求排列组合什么的,但是遍历顺序要先背包后物品,因为这样才能穷尽字典组合的可能性,例如applepenapple那个例子,如果先物品后背包,就只能组成apple…pen…这种格式的,所以要用排列的方式来做。

背包问题总结

​ 每个物品只能放一次
​ 01背包
​ 二维数组的做法顺序颠倒可以,一维数组的做法必须先物品后背包,且背包必须从大到小遍历。
​ 每个物品可以无限放
​ 完全背包
​ 一维,二维数组的做法顺序颠倒可以,但是遍历顺序由差别,如果是求组合就先物品后背包,如果是求排列就先背包后物品。
​ 每个物品最多有Mi个可以用
​ 多重背包
​ 把物品全部摊开就变成了01背包,一般不考。
​ 如果是求方法数,组合数这种,递推公式一般是dp[j] += dp[j - nums[i]]。

打家劫舍问题

​ 198.打家劫舍:比较简单的动态规划问题,递推公式只需要考虑偷这件屋子和不偷这间屋子哪个收益高即可。
​ 213.打家劫舍II:这道题和上面的区别就在于这道题成环了,但其实成环影响的就第一家和最后一家,所以其实分两种情况讨论就行,要偷第一家,这时候我们再考虑第三家到倒数第二家的收益即可;或者不偷第一家,这时候我们考虑第三家都最后一家的收益即可。
​ 337.打家劫舍III:这道题可以用二叉树的后序遍历的思路做,也可以用动规的方法来做,不过都是后续遍历的思想,二叉树的做法就是区分情况,按选当前和不选当前的思想来遍历,而且要记忆已经搜过的。动规的方法是设立一个vector数组,第一个元素存储偷当前,第二个元素存储不偷当前,总之写这种递归吧,想好单层的逻辑即可,不用想的太深。
​ 其实打家劫舍问题都能用偷当前或不偷当前来解决,其实上面三道题这种思想由浅到深,不过前面两题可以用更简洁的写法而已。

股票问题

​ 121.买卖股票的最佳时机:这道题用贪心效率最高,就是找左边最小有右边最大的。动态规划的话属于那种分情况动规的,即分为当天持有股票和当前不持有股票,所以dp一定会有两个维度。注意持有股票是当天买的那种情况直接就是-price[i]因为只能买一次。
​ 122.买卖股票的最佳时机II:这道题之前用贪心做过,贪心比较好想,动规的话也比较简单,和上面一样,知识在持有股票是当天买的那种情况和上面不一样。
​ 123.买卖股票的最佳时机III:这道题也是用分情况的方式进行动规,不过情况相对于前面两道更多。仔细一点。还有就是使用滚动数组那种省空间的方法。
​ 188.买卖股票的最佳时机IV:这道题是上面那道题的抽象,用循环来给dp赋值即可,不过要讨论当前是偶数还是奇数,不过可以用每次+2的技巧跳过讨论。
​ 309.买卖股票的最佳时机含冷冻期:买卖股票问题应该属于动规里面那种用不同的状态数来动态规划的题目了,这道题也是,我是自己划分的状态,把每天分为3种状态,一是持有股票,二是不持有股票且不是今天卖出,三是不持有股票且是今天卖出,然后再写递推方程。然后把状态之间的转化搞明白就行。当天买当天卖这种情况一般不会影响结果,考虑进去也不会影响结果。
​ 714.买卖股票的最佳时机含手续费:这道题用动态规划没什么好说的,挺简单的,但是用贪心的话还是有点难想,不过贪心的效率确实高点。
​ 总结:买卖股票问题基本都是划分每天的状态,然后递推方程就是状态之间的转化,还有一些题目其实也属于这种类型,或者说动态规划里面状态划分以及转换的题目还是比较多的。

子序列问题

子序列(不连续)

​ 300.最长递增子序列:这道题的递推方程属于那种有循环的,需要和前面的每一个进行比较取最大值。
​ 1143.最长公共子序列:这道题和718有点像,但是它是不连续的,所以递推公式就有变化了,即当两个元素不相等的时候不是为0,而是从上面和左边选一个最大的,且不能再用一维数组了,一维要同时用到上面的和左边的。
​ 1035:不相交的线:这道题就是最长公共子序列…如果做了上面那题,肯定不难,难得是想出来是上面那道题。

子序列(连续)

​ 674.最长连续递增子序列:这道题和300的区别就在于要求是连续的,不过更简单了,因为300不连续要和之前的每一个进行比较,而这个连续的只需要和前一个进行比较就行了。
​ 718.最长重复子数组:这道题我觉得还是有点难,其实我觉得不太容易想到,如果按照常规的做法的话初始化要注意一点,不过也可以在dp数组外围再加一层,这样比较好些,而且可以压缩为一维数组。
​ 53.最大子序和:我发现这种连续的问题递推公式都有一个特征,就是dp[i]代表一定是包含i的那种情况。这道题其实贪心还是蛮好想到的。

编辑距离

​ 392.判断子序列:这个问题当然可以用最长公共子序列来解,但是我们也可以用编辑距离的视角来看它,即我们应该对t进行哪些操作才能使得s和t一样,在这道题中,我们需要把t中不符合的删去,所以在不相等的地方dp[i][j] = dp[i][j - 1],即s[i - 1]和t[i - 2]的值。
​ 115.不同的子序列:这个题和之前的题不同,之前的题都是求最长公共子序列的长度,这道题是求个数,所以递推公式要变一下,且初始化也要变一下,也算是编辑距离的题,因为删除s来达到t。
​ 583:两个字符串的删除操作:还是之前一样,考虑相同的情况和不同的情况,然后写递推公式,注意初始化。
​ 72.编辑距离:这道题其实改和删的情况不难,关键是增的情况的公式是什么比较难想到,其实word2删就相当于word1减,还有,min和max函数可以对一个列表求最值。
​ 总结:编辑距离的题目都是用一个扩大了一圈的二维dp数组,初始化要看题目,循环中主要分相等和不相等两种情况来讨论,不相等的情况又要考虑增删改的公式。注意循环的时候终止条件是<=.

回文问题

​ 647.回文子串:这道题还是用动规来做,不过递推公式和前面的有些不一样,而且dp数组不能直接设为求数量,而是求是否为回文,然后另设变量来判断数量,由于递推公式是用的左下方的那个,所以要先遍历列再遍历行,而且由于全遍历的话初始化很麻烦,所以我们要发现只用填一般的矩阵就好,即j始终要大于等于i。这样基本就不用初始化了。还可以用双指针法从中心扩展的思想来做,时间和空间都会好点。
​ 516.最长回文子序列:这道题不是求连续的子串了,而且是求最长了,所以递推公式要变一下,不过也挺好想,但是遍历的顺序也要变了,先遍历列,再遍历行,且列是从下到上,行是从左到右,也是只求一般的矩阵即可。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值