动态规划
是
baixiaofei567
如果十年前没种树,那最好的时间是现在
展开
-
1723. 完成所有工作的最短时间
一眼就感觉是dfs爆搜,肯定超时,正解应该是递归回溯+二分+剪枝(做不来hhh)超时版本:class Solution {public: //第一个参数是当前处理到的工作数量,第二个参数是当前所有人中的最大工作时间 void dfs(int u,int maxn,vector<int>& jobs,int k){ //剪枝 if(maxn >= res) return;//当前已经不可能成为最优解了 //递归边.原创 2021-05-08 23:28:00 · 251 阅读 · 0 评论 -
740. 删除并获得点数
评论说是打家劫舍4,很形象。这道题的题意是删除某个数字获得点数,然后强制删除i+1和i-1并且不能获得点数,我们要得到最大的点数。我们先遍历nums,记录每个数字出现的次数,并且记录下最大的数字。主要是看是否取当前数字,如果取了前面就不能取,如果没取前面就可以取,0就是没取,1就是取了状态定义:dp[i][1]就是删除当前数字,dp[i][0]就是不删除当前数字状态转移:如果取了前面就不能取(前面的被强制删除),如果没取前面就可取可不取,取了的话就=dp[i-1][0] + ima[i],用当前.原创 2021-05-08 00:13:59 · 178 阅读 · 0 评论 -
403. 青蛙过河
dp的hard题,写出状态定义和状态转移都不是最难的。basecase很难,而且最好从dfs->记忆化->dp的顺序循序渐进class Solution {public: bool canCross(vector<int>& stones) { //dfs超时就考虑dp //状态定义:dp[i][k]的含义是当前在第 i 个位置,并且是以步长 k 跳到位置 i 时,是否到达最后一块石子。 //状态转移方程:根据上..原创 2021-04-29 17:40:07 · 146 阅读 · 0 评论 -
最长公共子串
是那题我很喜欢的动态规划的升级版!!法一:二维dp法二:状压dp,从后往前,不用记录,因为不会改变左上的法三:状压dp,从前往后,要记录左上的。状压dp最重要的就是如果不相等记得将dp[j]设为0class Solution {public: /** * longest common substring * @param str1 string字符串 the string * @param str2 string字符串 the string * .原创 2021-04-16 00:47:08 · 140 阅读 · 0 评论 -
1143. 最长公共子序列
这种题目是真正的好题。二维dp不难,难的是状压dp,记录左上的是细节,细节中的细节class Solution {public: int longestCommonSubsequence(string text1, string text2) { //字符串子序列也就是匹配问题就考虑二维dp或者滑动窗口 //滑动窗口不能确保顺序,还是得dp //状态定义:dp[i][j]代表1的前i位和2的前j位最长的匹配长度 //状态转移:多种.原创 2021-04-05 02:02:11 · 136 阅读 · 0 评论 -
583. 两个字符串的删除操作
编辑距离的easyeasy版本,说到底就是求最长公共子序列,最长公共子序列当然是二维dp啦状态定义:dp[i][j]表示的是取1的前i位和2的前j位的最长公共序列状态转移:当word[1]==word[2]的时候,dp[i][j]等于dp[i-1][j-1]+1,打个比方就是abc和bbc,那么c相等的情况下就是求ab和bb的最长公共子序列然后加上这个c如果不相等,就继承dp[i-1][j]和dp[i][j-1]中较大的那个basecase:就是长度都为0的时候公共序列长度为0最后返回两个长度相.原创 2021-03-04 01:24:36 · 74 阅读 · 1 评论 -
188. 买卖股票的最佳时机 IV
和上一题一样,注意一个情况即可,就是第0笔交易的情况,如果当前最多只有0笔交易,且未持有股票,那么继承的也是前一天0笔交易且未持有。否则若持有,可能前一天就不持有,或者今天刚卖(因为卖了就有一笔交易了)class Solution {public: int maxProfit(int k, vector<int>& prices) { //k笔交易和2笔交易是一样的 //三维dp,第一维记录天数,第二维记录完成交易的次数,第三维记录当前是否.原创 2021-03-02 01:31:43 · 75 阅读 · 0 评论 -
123. 买卖股票的最佳时机 III
三维状态机dp可以考虑三维dp,第一维记录天数,第二维记录完成交易数0或者1或者2,第三维记录当前是否持有股票很重要的一点是第二维记录的是当天可以完成的最大交易数!!并不是当天一定完成了2笔交易的意思。所以basecase是第一天持有就是-prices[i],不持有就是0。不管今天最大交易数是几。状态转移就自己推就行了,最后返回的就是最后一天最多完成2次的不持有股票的情况class Solution {public: int maxProfit(vector<int>&am.原创 2021-03-02 01:28:24 · 108 阅读 · 0 评论 -
714. 买卖股票的最佳时机含手续费
股票直接状态机dp,第二维记录状态就行了我们在买入的时候就把手续费算上去(重要!!)如果当天未持有股票,可能是昨天就未持有,也有可能是今天卖掉的如果当天持有,可能是昨天就持有的,也有可能是今天刚买的最后返回最后一天未持有的情况class Solution {public: int maxProfit(vector<int>& prices, int fee) { //在卖出的时候要支付手续费,所以在利润小于2的时候不要卖出,尽量减少交易次数 .原创 2021-03-02 01:25:30 · 97 阅读 · 0 评论 -
377. 组合总和 Ⅳ
dfs超时,这不是普通的完全背包问题。还是考虑用记忆化搜索比较靠谱!!如果接下来要搜索的数字组合数搜索过了,就直接返回。记录mem[target]就是用一个res记录从当前的target dfs下去的所有可行的结果之和,把res赋值给mem[target],递归边界有两个,当target<0就返回0。或者mem[target]!=-1就返回,所以我们一开始就把mem[0]给赋值为1,这样当target为0时,证明有一种可行解,直接返回1了。class Solution {public: .原创 2021-03-02 01:21:07 · 90 阅读 · 0 评论 -
474. 一和零
dfs超时。考虑dp,三维dp,其实从本质来说还是01背包,只是要考虑的不只只是物品的重量,还有物品的大小(假设这样打比方),就是放进一个物品时,要考虑它的0和1。所以就是dp[i][j][k]表示在前i个字符串中,用j个0和k个1最多可以组成几个字符串。每一轮计算当前字符串的0和1的个数。状态转移:看看当前字符串取或者不取,如果取了没超的话就取dp[i][j][k] = dp[i-1][j-one][k-zero]+1如果不取就dp[i][j][k] = dp[i-1][j][k]可以考虑状压,.原创 2021-03-01 01:21:43 · 99 阅读 · 0 评论 -
494. 目标和
这不妥妥的dfs吗。对每一位有两种情况+和-。一直到数组结尾再判断s是否等于0,如果等于0相当于有一种情况,res++。class Solution {public: void dfs(vector<int>& nums, int index, int S){ if(index == nums.size()){ if(S == 0) res++; return; } dfs(nu.原创 2021-01-31 01:28:45 · 70 阅读 · 0 评论 -
376. 摆动序列
进阶居然要求O(n),菜鸡落泪。状态机dp。dp[i]表示以当前位为结尾能达到的最大摆动序列长度dp[i][1]代表之前是上升的,dp[i][0]代表之前是下降的。如果当前数字比前一个数字大,那么我们要的是前一个数字前面是下降状态的class Solution {public: int wiggleMaxLength(vector<int>& nums) { //边界条件也不能忘记啊,不能无脑把res设为1或者0,还是状态机dp好了 i.原创 2021-02-27 01:42:06 · 73 阅读 · 0 评论 -
646. 最长数对链
区间问题,用贪心肯定可以做,但是既然在练dp,就dp了。先排序,把第二位小的排前面,第二位小的在前面后面才能有更多的选择。dp[i]记录的是以当前为结尾能达到的最大值。class Solution {public: static bool cmp(const vector<int>& a, const vector<int>& b){ //把第二位小的排前面 return a[1] < b[1]; } .原创 2021-02-27 01:39:07 · 82 阅读 · 0 评论 -
91. 解码方法
三步走,定义状态,状态转移方程,basecase1.定义状态:dp[i]表示的是以s[i]为结尾可以有几种解码方法2.状态转移:如果s[i]不为0,那么dp[i]继承dp[i-1],也就是dp[i]=dp[i-1]1。如果s.substr(i-1,2)的数字>=10且<=26,那么证明又多了一种表示方法,就让dp[i] += dp[i-2]1。加入126dp[0]=1,dp[1]=2,dp[3]就等于dp[2]的两种方式s.substr(3)这种方式+dp[1]的一种方式s.substr..原创 2021-02-27 01:35:24 · 302 阅读 · 0 评论 -
343. 整数拆分
题解明天写class Solution {public: // int dfs(int n){ // //这就是递归边界了,因为已经拆分不下去了 // if(n == 2) return 1; // if(dic[n] != 0) return dic[n]; // int res = 0; // for(int i = 1; i <= n; ++i){ // //可以将n-i继续拆.原创 2021-02-26 11:26:23 · 78 阅读 · 0 评论 -
413. 等差数列划分
暴力也挺快的,但是最好还是用dpdp[i]代表的是以nums[i]为结尾可以有几个等差数列如果nums[i]-nums[i-1] == nums[i-1]-nums[i-2],那么dp[i]就等于dp[i-1]+1状态转移方程要注意如果dp[i-1]为0,那么一直到dp[i]相当于有一个长度为3的等差数列了.如果dp[i-1]为1,那么证明到dp[i]有长度为4的等差数列,长度为4的等差数列有3种情况,所以dp[i]=2,加上dp[i-1]=1,就是三种情况class Solution {p..原创 2021-02-26 01:15:03 · 226 阅读 · 0 评论 -
303. 区域和检索 - 数组不可变
前缀和,注意的是sum[j]-sum[i-1],如果i为0的话,直接返回sum[j]class NumArray {public: //前缀和思想? //前缀和在初始化的时候就计算出来就行了 //不用每次都for循环计算 NumArray(vector<int>& nums) { sum = nums; for(int i = 1; i < sum.size(); ++i){ sum[i].原创 2021-02-26 01:13:22 · 139 阅读 · 0 评论 -
213. 打家劫舍 II
聪明的小偷系列,和打家劫舍1一样,记录dp[i-1]和iclass Solution {public: int rob(vector<int>& nums) { //第一间房和最后一间房只能偷一个,dp两次,分别dp的是偷第一间房和偷到倒数第二间以及不偷第一间房偷到最后一间 if(nums.size() == 0) return 0; if(nums.size() == 1) return nums[0]; if(n.原创 2021-02-26 01:10:11 · 76 阅读 · 0 评论 -
334. 递增的三元子序列
一开始用了最长增长子序列的dp方法,只要有一个点为结尾点可以达到长度>=3就直接true。进阶要求:空复O(1),时复O(n),只能线性扫描且不能有辅助空间。用了很巧妙的方法,只要出现了比b大的数字就一定可以了,因为出现了比b大的数字就证明出现过比b小的数字a,然后出现b,然后出现了c。if-else用的很巧妙class Solution {public: bool increasingTriplet(vector<int>& nums) { .原创 2021-02-17 01:01:46 · 101 阅读 · 1 评论 -
518. 零钱兑换 II
也是妥妥的完全背包,用二维和一维dp都可以。但是状态转移方程和表达式含义有所不同。dp[i][j]表示的是用前i枚硬币能组成金额j的种类。如果当前不可取就继承i-1时的种类。如果当前可取,就让[i-1]时的种类加上取了当前的种类。也就是dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]],也要从1到amount,因为要用这一层的情况来更新这一层的class Solution {public: int change(int amount, vector<int.原创 2021-02-16 01:45:19 · 94 阅读 · 0 评论 -
122. 买卖股票的最佳时机 II
这道题绝对有中等难度了。可以用状态机dp来做,因为只和前一天的两个状态有关,所以可以状态压缩,压缩到常量级别。最后返回的是最后一天不持有股票的情况。因为最后一天买了就卖不出去,所以肯定不会买,如果之前有,那么最后一天必须要卖掉,所以最后一天肯定无股票。贪心的应该是想不到的,只要当前这一天比前一天大,就让结果加上当前减去前一天的值class Solution {public: int maxProfit(vector<int>& prices) { //只..原创 2021-02-07 00:57:28 · 148 阅读 · 0 评论 -
44. 通配符匹配
和正则表达式差不多,记得初始化很重要。class Solution {public: bool isMatch(string s, string p) { //?不能匹配空,*可以匹配空,就是不匹配任何东西 //说到底就是kmp,直接二维dp解决 int sLen = s.length(), pLen = p.length(); //dp[i][j]表示的是p的前j位可以正常表示s的前i位 vector<v..原创 2021-02-03 01:13:56 · 184 阅读 · 0 评论 -
从零钱兑换引出的完全背包问题
转载自https://leetcode-cn.com/problems/coin-change/solution/wan-quan-bei-bao-wen-ti-shou-hua-dp-table-by-shixu/做这个题有一阵子了,当时直接套用了完全背包的模板了,最近发现,直接套模板容易让自己走捷径,但是对于背包的实质理解不清楚,特意推一下完全背包执行过程完全背包:有一个背包,体积为V,有物品(vi–体积 , wi–价值),问该如何拿,才能得到最高的价值。其中,如果每个物品最多只能拿一次,则称之转载 2021-01-29 11:44:09 · 117 阅读 · 0 评论 -
322. 零钱兑换
1.必须先dfs,这次的dfs+剪枝比dp还快。先sort,把大的排前面,大的能先用就多用一点。剪枝操作很重要,如果index超过硬币数组大小了||当前use的硬币个数+剩下需要的钱/当前硬币大小如果大于等于use,直接return,因为当前的硬币是接下来最大的,如果当前全取都优化不了的话,后面再搜索下去也是白搜索。如果取n个当前的可以把剩下的全消耗掉,也就是left%当前==0,那么就进行更新,看res和use+当前要取几个哪个更小。取更小的。如果不能刚好取完,那就要去小的继续搜索,因为是递归,..原创 2021-01-29 11:29:47 · 87 阅读 · 0 评论 -
416. 分割等和子集
子集问题,一定要考虑到背包问题,也就是动态规划。最后还可以进行状态压缩,简称状压dp这句话很重要,如果你要用多次的话,就不要反向遍历。相当于对每一层反向填表。最后主要是掌握!二维dp的使用class Solution {public: bool canPartition(vector<int>& nums) { //只要是取或者不取就直接背包问题 int sum = 0; for(auto x:nums) su.原创 2021-01-29 10:48:54 · 124 阅读 · 0 评论 -
312. 戳气球
最优解=dp。做不来!class Solution {public: int maxCoins(vector<int>& nums) { //戳了之后要把这个戳破了的气球删除,大的先不要戳破 //最优解=dp //dp[i][j]表示戳破i到j的气球能得到的最大值,而i和j是不会被戳破的 //nums[-1]=1,nums[n]=1都先写上,防止处理边界条件 nums.inse.原创 2021-01-28 01:03:08 · 102 阅读 · 0 评论 -
309. 最佳买卖股票时机含冷冻期
状态机+dp,根本不会做class Solution {public: int maxProfit(vector<int>& prices) { //最优解=dp,尽量多买多卖 if(prices.size() == 0 || prices.size() == 1) return 0; //dp[i][j]为在第i天第j种情况下的最大收益 int len = prices.size(); vec.原创 2021-01-28 01:02:22 · 121 阅读 · 0 评论 -
139. 单词拆分
不是chineseCode就是dpCode。看题才是真的麻烦法一:超时的dfs,搜索的题目不是dfs就是dp。此处的dfs的参数有当前遍历到的字符串的下标,如果遍历到字符串结尾就返回true。在dfs内部从index遍历到字符串末尾,如果从index到i这个字符串在字典中有,就遍历剩下的字符串。如果一直从index到结尾都找不到,就返回false,相当于回溯。回到上一层找别的情况。法二:dp,此处的dp[i]表示的是前i个字符串是否都能被正常表示,dp[0]等于true,因为0个字符一定能正常被表示。.原创 2021-01-28 00:58:29 · 160 阅读 · 0 评论 -
96. 不同的二叉搜索树
一看就知道是数学题,两种方法,备忘录+dfs或者dp备忘录+dfs:当结点有0或者1的时候,就是一种情况,这是递归边界也是初始化dp数组的关键。https://leetcode-cn.com/problems/unique-binary-search-trees/solution/bu-tong-de-er-cha-sou-suo-shu-cong-yuan-shi-de-di-/https://leetcode-cn.com/problems/unique-binary-search-trees/.原创 2021-01-27 01:23:58 · 96 阅读 · 0 评论 -
72. 编辑距离
光看题目是不可能做的出这道题的,两个字符串的最优解问题,一定要想到二维dp。dp[i][j]存的是第一个字符的前i位变到第二个字符串的前j位最少需要几步,因为""变到任意一个另外的字符串,需要的步骤都是另一个字符串的长度,所以先给dp数组赋初值。然后从1遍历到n和m,要等于,所以数组要开大一点。接下来有两种情况1.word[i-1]==word2[j-1],这样相当于不用做任何操作就匹配上了,它需要的总步长就是dp[i-1][j-1]的大小,只要前面匹配上了最后本来就是匹配的。2.如果不等于,那么有..原创 2021-01-26 01:46:39 · 113 阅读 · 0 评论 -
70. 爬楼梯
dp,先给前两个台阶初始化,从3跳到n。这里我们直接省略空间复杂度,因为只要记录当前台阶的前两个台阶的方法数即可,不用开数组了,容易写错的是,因为我们要将当前台阶赋给下面一个,下一个赋给下两个,顺序千万不能错,要先将下一个赋给下两个才行class Solution {public: int climbStairs(int n) { //dp即可,记录前一个台阶有几种方法能到达 if(n <= 2) return n; int lastTwo.原创 2021-01-25 01:07:10 · 76 阅读 · 0 评论 -
64. 最小路径和
很明显又是dp,我们防止越界先将第一行和第一列全部初始化,第一行的初始化值就是自己左边的和加上自身,第一列类似。因为是找最小路径和,所以我们的转移方程是,左边和上边较小的那个值+自身class Solution {public: int minPathSum(vector<vector<int>>& grid) { //和上一题很像,dp if(grid.size() == 0 || grid[0].size() == 0) re.原创 2021-01-25 01:03:00 · 69 阅读 · 0 评论 -
62. 不同路径
dfs超时,所以用了dp,其实这种看到这种题目,看到和前一个格子(状态)有关系就应该想到dp。这里防止越界要判断是否是第一行或者第一列,第一行和第一列都只有一种走法,其余格子的走法是左边的所有走法+上边的所有走法class Solution {public: /*void dfs(int& m, int& n, int row,int col){ if(row == m-1 && col == n - 1){ res++..原创 2021-01-25 01:00:54 · 108 阅读 · 0 评论 -
53. 最大子序和
class Solution {public: int maxSubArray(vector<int>& nums) { //最大=最优解=dp,子数组=dp //dp[i]代表以当前数字为结尾的最大连续和 //状态转移方程,dp[i] = max(dp[i-1]+nums[i],nums[i]); /*if(nums.size() == 0) return 0; vector<int>.原创 2021-01-22 02:19:51 · 108 阅读 · 0 评论 -
10. 正则表达式匹配
牛客的递归超时了,要用dp。这里也是二维dp,二维dp=填表。字符串好多都是用二维dp,这里是记录两个字符串的位置,回文子串是记录首尾下标。class Solution {public: /*bool matchCore(string s, string p, int sIndex,int pIndex){ //如果两者同时走到结尾就是true if(s.length() == sIndex && p.length() == pIndex) ..原创 2021-01-16 02:49:12 · 370 阅读 · 0 评论 -
5. 最长回文子串
三种方法1.暴力通过剪枝可以达到O(n^2)的时复2.二维dp,此处需要用到二维dp,dp[i][j]存的是以i为头,j为首的字符串是否为回文串。根据回文串的性质来写转移方程。1.回文串两头相等。2.回文串去掉头尾还是回文串。我们用maxLen和begin来记录子串的起始位置和最大长度,不要每次都substr。因为这里是二维dp,二维dp最最重要的就是填表顺序,这里我的填表方式是一列一列填表,因为dp[i][j]要根据dp[i+1][j-1]来推断,刚好是左下角,所以一列一列填表是可以,而且只要填右上.原创 2021-01-16 02:42:28 · 124 阅读 · 0 评论 -
3. 无重复字符的最长子串
dp时间复杂度是O(n^2),dp[i]记录的是以当前位为结尾的无重复子串的长度,所以状态转移方程是遍历i-1到i-dp[i-],如果有相等的,就退出往前遍历的循环,然后记录dp[i]=max(dp[i],i-j);j是找到的第一个和i相等的下标。这道题维护一个dp数组是记录要往前遍历几位。双指针法:感觉时间复杂度也很高。维护一个unordered_set来直接find有无该元素,只要O(1)的时复,i一直往前,也就是right。当find不到当前元素时,停止erase(s[left]),left一直往.原创 2021-01-15 01:54:30 · 142 阅读 · 0 评论 -
300. 最长递增子序列
我就说我怎么这么轻松做出了dp,原来最优解是贪心+二分,dp只是个O(n^2)的垃圾解法罢了。最长=最优解=dp,重点是不用连续,状态转移方程需要自己推导,dp[i]记录的是以当前为最后一位的最长序列,那么就要遍历i-1,如果前面某个数字比当前数字小,那么就判断dp[i]和dp[j]+1哪个大,每次遍历完,就记录较大的resclass Solution {public: int lengthOfLIS(vector<int>& nums) { //最长=最优.原创 2021-01-14 01:23:00 · 115 阅读 · 0 评论 -
279. 完全平方数
i-jj就是xhttps://leetcode-cn.com/problems/perfect-squares/solution/chao-zhi-bai-kao-zui-jiang-xiao-xue-sheng-du-neng-/又是dp,最少(最多)=最优解就要想到dp。此处dp[i]可能由dp[0]到dp[i-1]推出,因为i一定等于x+jj,那么dp[i]就是dp[x]+1,开一个大小为n+1的dp数组(为啥n+1自己想),dp[0]就等于0,从1遍历到n,在里面找i的最优解,j从1开始遍历.原创 2021-01-13 00:55:02 · 131 阅读 · 0 评论