![](https://img-blog.csdnimg.cn/20201014180756738.png?x-oss-process=image/resize,m_fixed,h_64,w_64)
动态规划
abant2
世界上最菜的arcem
展开
-
acwing 2 普通背包 2维做法的坑
接下来看二维的做法,首先讲一下,为什么需要二维的做法,因为一维无法满足部分题目的条件,比如说,如果需要我们找到放进背包具体是哪几个物品,一维的是无法保存历史信息的,在这个例子下我们就需要保存二维矩阵,再进行回溯。了,我们也不能跳出循环(这个会多消耗一些时间,但影响不大)而是要把上面的结果复制下来,算是和一维最重要的区别了。这个定义非常的巧妙并且很难直接想到,因此,很多dp题目会以背包作为变种,因此,背包是dp中非常经典的例子。首先看一维的做法,如下所示,比较简单,注意细节:在内层循环中,如果。原创 2023-02-15 18:14:44 · 353 阅读 · 0 评论 -
leetcode 2216 美化数组的最少删除数 dp&贪心两种做法
解法一:动态规划(dp)一般这类线性问题都可以用dp解决,我们定义 dp[i]dp[i]dp[i] 为前 iii 个元素组成美化数组的最少删除数。根据题目定义,如果是奇数个元素,那么就要删除一个元素,所以 dp[0]=1dp[0] = 1dp[0]=1。前两个元素可以根据相不相同来进行删除,如果两个相同必须全部删除,公式如下所示:dp[1]={2dp[0] == dp[1]0dp[0] != dp[1]dp[1]=\begin{cases}2& \.原创 2022-04-01 22:26:41 · 335 阅读 · 1 评论 -
leetcode 2218 分组背包
此题只能从上往下挖,可以将每个坑看做一个组,从而转化为分组背包问题,即每组最多取一个物品。我们将其转化为01背包的思路,dp[i][j]dp[i][j]dp[i][j] 表示前 iii 组物品在容量为 jjj 的背包中得到的最大价值。由于每组最多之恶能选一个,如果不选,则dp[i][j]=dp[i−1][j−1]dp[i][j] = dp[i-1][j-1]dp[i][j]=dp[i−1][j−1]如果选的话,只能选其中一个,我们需要枚举每一个物品的 www 和 vvv,dp[i][j]=max(dp.原创 2022-04-01 16:49:22 · 4842 阅读 · 0 评论 -
LCP 47 困住无辜的人 背包装满方案数
此题的队列是完全没用的数据结构,栈的作用是困住无辜的人,容量为 bbb 的栈,会困住 b−1b-1b−1 个无辜的人,所以我们需要把数组中每个数减一,然后看他们如何拼满 kkk 个人。此题最重要的是进行题目解析,减一后转换为背包装满问题就很简单了。之前比赛时压力比较大,理解为从 nnn 个物品中做选择,如果选 mmm 个,求出能装满容量为 m+km+km+k 的背包的方案数。其实就每个减一就行,但是脑子没有转过弯,就很难受。class Solution {public: int secur.原创 2022-03-24 15:50:43 · 877 阅读 · 0 评论 -
leetcode 2212 二维01背包回溯
此题要射箭,而且必须比Bob多一支,所以如果决定了要射哪个靶子,那要射的箭的数目是一定的。所以,我们可以将这个问题转化为01背包问题。注意,此题需要做回溯,而背包做回溯和最长上升子序列不一样,是需要存二维数组然后回溯的。我们应该知道,一维01背包是由二维背包转化而来的。背包问题的核心是选或者不选,dp[i][j]dp[i][j]dp[i][j] 表示前i个物品的最佳组合在容量为 jjj 的背包中取得的最大价值,即dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i]).原创 2022-03-24 14:52:08 · 229 阅读 · 0 评论 -
leetcode 2188 归约后线性dp
此题第一次看较为复杂,但有几个条件比较重要,一个是每种轮胎都是无限个,这个减少了太多难度,此题最难想的地方是,不要想选哪个轮胎,走多久,而是看一段固定的距离,哪个轮胎走花的时间最短。因为最终一定是分成一段一段的。之后,我们要考虑这个固定的距离最远有多长,由于指数的底最小是2,可以算出20即可满足要求。之后,下一个难点就是要考虑线性dp,从第一个开始,枚举前20个算出每个的最优,dp[i]dp[i]dp[i]定义为走iii圈花费的最短时间。考虑好这些后,代码就较为简单了,还有就是要用ll防止爆int。.原创 2022-03-07 16:14:35 · 104 阅读 · 0 评论 -
leetcode 808 概率dp及递推证明过程
此题是典型的概率dp,概率dp需要从小往大走,n方次循环即可,比搜索简单很多,由于题目中均为25的倍数,我们可以将所有参数除以25。此题的递推公式是dp[i][j]=0.25∗(dp[i−4][j]+dp[i−3][j−1]+dp[i−2][j−2]+dp[i−1][j−3])dp[i][j] = 0.25*(dp[i-4][j] + dp[i-3][j-1] + dp[i-2][j-2] + dp[i-1][j-3])dp[i][j]=0.25∗(dp[i−4][j]+dp[i−3][j−1]+dp[i.原创 2021-09-23 21:59:55 · 233 阅读 · 0 评论 -
leetcode 494 目标和 背包装满的种数
此题一看就是一个典型的dfs,我上来就是一个回溯,超级快。class Solution {public: void dfs(vector<int>& nums, int i, int res, int &ans) { if(i >= nums.size()) { if(res == 0)ans += 1; return; } dfs(num.原创 2021-09-14 19:26:57 · 99 阅读 · 0 评论 -
leetcode 416. 分割等和子集 背包装满问题
这题看着挺费劲的,但其实是需要一定的转换,即首先,数组的和一定是个偶数,而我们要找一个子数组,看能不能等于这个数组和的一半,所以,就变成了一个简单的背包装满问题。class Solution {public: bool canPartition(vector<int>& nums) { int sum = 0; for(auto num: nums)sum += num; if(sum&1)return false;.原创 2021-09-14 19:12:20 · 65 阅读 · 0 评论 -
leetcode 5852 矩阵每行选一个构建目标和
这个题如果直接暴力或者回溯,是做不了的,复杂度太高。但是一看数据范围,target最高800,每个数最高90,这样正确的范围应该是在900内的。唯一要考虑的情况是最小可能和大于target,这样就不用再算了。所以,考虑范围,用类似dp来做即可。class Solution {typedef tuple<int, int, int> T;int m, n;public: int minimizeTheDifference(vector<vector<int>..原创 2021-08-22 19:39:56 · 76 阅读 · 0 评论 -
leetcode 279 完全平方数 背包问题
此题一看就属于没法做那种,和丑数还是不太一样的。丑数要用质因数,此题就是可以转化为背包来做。考虑dp[i]是i的最少数量,dp[i]可以由dp[i-j*j]得到。也和最长上升子序列有些类似。class Solution {public: int numSquares(int n) { vector<int> dp(n+1, 1e5); dp[0] = 0; for(int i=1;i<=n;i++) { .原创 2021-08-03 15:10:51 · 88 阅读 · 0 评论 -
剑指offer 49 找第n个丑数
这个题之前在热题做过了,不过题就是要多做嘛。此题我们可以先用数学的方法尝试一下排序:1,2,3,5,4,6,10上述排序是比较直观的,类似于队列的形式。每次排出一部所有,然后都乘2,3,5. 但是从上述例子就可以看出是不对的,因为4应该在5前面。由于丑数是累乘得到的,我们可以用3个指针分别存2,3,5指向的位置,因为每个数都要乘所有之前的丑数。每次通过循环取最小的即可。去重:由于可能有重复丑数出现,我们每次要判断三个指针有没有指向重复的元素,有的话,就都向前移动指针。比map去重高效很多。ty.原创 2021-08-03 13:03:38 · 73 阅读 · 0 评论 -
leetcode 221 最大正方形 很妙的dp
此题和leetcode 85 最大矩形几乎是一样的题目,只是那个题是矩形。矩形的题目用单调栈做,而此题一看就是要dp的。然而,此题的dp定义并不好想。我们定义 dp[i][j]dp[i][j]dp[i][j] 表示以 i,ji,ji,j 结尾的最大边长,则dp[i][j]=min(dp[i][j−1],dp[i−1][j],dp[i−1][j−1])+1dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1])+1dp[i][j]=min(dp[i][.原创 2021-08-02 18:24:17 · 78 阅读 · 0 评论 -
leetcode 214 左边插入构建最短回文串 马拉车+kmp两种解法
解法一:马拉车首先要把这个题剖析一下,变得简单。其实这个题是一个找最长回文串前缀的题,比如aabaa + cc这个字符串,我们找到最长的回文前缀aabaa,然后只要把后面两个字符反转一下加在前面即可。根据上面的分析,就可以使用马拉车算法计算dp[i] = i的下标,并记录最大,时间复杂度为 O(N)。class Solution {public: string shortestPalindrome(string s) { string ss = "#"; f.原创 2021-08-02 14:23:47 · 121 阅读 · 0 评论 -
马拉车算法 正确简洁代码版
马拉车算法要计算的是一个dp数组,数组中每个元素代表以当前元素为下标,可扩展的最大半径。注:在我们通过井号预处理后,这个最大半径就是回文串的长度。状态转移:我们持续更新当前能到达的最远右端,如果新元素包含在内,可以通过看对称元素的回文长度减少运算量,若不包含只能从头开始。在包含的情况下,只有回文长度等于maxRight-i时才需要重新走,可以看我之前写的leetcode 5里面的解释。在写代码时,我们不用考虑这些,如果i在R内部,就考虑dp[mirror]和maxRight-i的最小值,之后一律做中心原创 2021-08-02 13:32:01 · 137 阅读 · 0 评论 -
leetcode 139 单词拆分
此题和最长上升子序列类似,也有点像背包的装满问题。如果前面匹配好了并且后面的可以匹配上,就可以加起来,套路是一样的。dp[i]的定义是以i结尾的字符串是否能由字典中的词构成。class Solution {public: bool wordBreak(string s, vector<string>& wordDict) { unordered_set<string> ms; for(auto t: wordDict)ms.ins.原创 2021-07-29 13:35:53 · 60 阅读 · 0 评论 -
leetcode 96 不同的二叉搜索树 dp+数学推导
此题也和二叉搜索树相关,但是又和排列组合有点关系,是挺难想的一个题目。二叉搜索树这个点应该是最先该入手的点。假设n个节点可能性是 dp[n]dp[n]dp[n],假设以节点i为根节点的可能性是 f(n)f(n)f(n),那么dp[n]=f(1)+f(2)+...+f(n)dp[n] = f(1)+f(2)+...+f(n)dp[n]=f(1)+f(2)+...+f(n)对于 f(i)f(i)f(i),由于加了二叉搜索树的限制,左子树和右子树包含的节点数可以完全确定,即f(i)=dp[i−1]∗dp[n.原创 2021-07-22 21:32:08 · 102 阅读 · 0 评论 -
leetcode 98 验证二叉搜索树
解法一:前序遍历 树形dp前序遍历是最常见的遍历方式,此处也是可以使用的。我们每次记录左子树和右子树的最大值和最小值。返回后,我们只需判断左子树和最大值是否小于中间值,或右子树的最小值是否大于最小值即可。typedef long long ll;class Solution {typedef vector<ll> V;public: V dfs(TreeNode *n) { if(!n)return V{1, (ll)INT_MAX+1, (ll)I.原创 2021-07-22 20:55:00 · 65 阅读 · 0 评论 -
KMP算法详解 理论+代码
此篇博客将KMP算法在字符串匹配中的应用。例:文本串:aabaabaaf模式串:aabaaf1️⃣ 理论篇正常的匹配思路,对文本串的每一个位置,往右遍历模式串长度,若匹配则返回,不匹配则继续文本串下一个位置,复杂度 O(mn)O(mn)O(mn)KMP算法:设置双指针,分别指向文本串和模式串的其中一个位置,文本串指针永不回溯,当不匹配时回溯模式串指针,复杂度 O(n)O(n)O(n)在讲解KMP算法前,先介绍什么是前缀和后缀,对于字符串aab,前缀:a,aa,后缀:b,ab我们定义这样一个结构原创 2021-07-12 21:58:26 · 201 阅读 · 0 评论 -
leetcode 1931 二维涂色问题 降维dp解法+状态压缩
题目的话,就是咱们高中经常做的涂色问题,一共三种颜色。最直观的感觉:不就是dp吗,考虑左边和上面的元素不就行了。但这样完全是不行的,2*2的我还能做,但是3*3除了枚举我已经无法得出正确答案了。这个题正确的做法是降维,因为有一个维度题目给的数据范围是 <= 5的,就针对这个处理。我们考虑一整行的状态转移,对于维度m,一行的可能性一共有3∗2m3*2^m3∗2m种。对每一行,我们可以用一个数字来存,因为只有5位,用10进制状态压缩即可。判断是否可转移,我们只需要判断每一位是否相等,都不相等说明可.原创 2021-07-11 18:04:28 · 368 阅读 · 3 评论 -
leetcode 32 dp+栈+两次遍历 多种解法
这个题真的是一道好题,看起来如此简单,实际却如此复杂(应该说包含了很多知识)。解法一:dp此题的dp定义是比较大胆的(其实也挺常规的),就是以当前字符结尾的最长有效长度。说他大胆是因为这样定义的话,所有以左括号结尾的dp值都为0.状态转移:如果当前字符是右括号,并且上一个是左括号,那么比较简单,dp[i]=dp[i−2]dp[i] = dp[i-2]dp[i]=dp[i−2]接下来就是核心:现在只剩一种情况,就是当前字符是右括号,上一个也是右括号。此时咱们看dp[i−1]dp[i-1]dp[i−1.原创 2021-07-10 23:56:32 · 178 阅读 · 1 评论 -
leetcode 1888 反转构造交替字符串
解法:动态规划考虑类型1:会把字符串分成n种状态,计算出初始状态后,我们可以通过状态转移方程来获得其余状态的值。只需考虑上一位的值即可,代码如下:class Solution {public: int minFlips(string s) { int n = s.size(); int dp[n][2]; memset(dp, 0, sizeof(dp)); for(int i=0;i<n;i++) {.原创 2021-07-09 19:49:18 · 104 阅读 · 0 评论 -
leetcode 72 编辑距离
编辑距离首先是对称的,一个字符串增加字符相当于另一个字符串减少字符,相应的,改也是一个对称操作。如果对应位置的字符相同,可直接领dp[i][j] = dp[i-1][j-1],因为肯定能少一次。此题和最长公共子序列很像。最长公共子序列也是看前三个元素,但是这个要三个一起比较,LCS是没必要看左上角的元素。class Solution {public: int minDistance(string word1, string word2) { int m = word1.siz原创 2021-06-29 14:01:19 · 65 阅读 · 0 评论 -
leetcode 1857 拓扑排序+dp
问题如上图所示,求每条路径的最大节点数目求max。做法是拓扑排序+dp。每个节点用上一个节点存的各个节点的数量进行更新,这个是核心。class Solution {map<int, vector<int>> adj;public: int largestPathValue(string colors, vector<vector<int>>& edges) { int n = colors.size(); .原创 2021-06-17 12:09:53 · 256 阅读 · 0 评论 -
leetcode 1871 动态前缀和,差分,dp,贪心 无敌题目
思路一:对于任意一个位置iii,我们判断它能不能跳到,要看i−maxJumpi-maxJumpi−maxJump和i−minJumpi-minJumpi−minJump区间内是否有1,有的话就可以跳到。这个可以用前缀和来做。但是,问题是我们要看的不是sss数组中的1,而是对dpdpdp数组求前缀和。但我们还没有dpdpdp数组。由于此题只取决于前面的dpdpdp,我们可以直接对dp数组做前缀和。用前缀和时要注意,由于前缀和数组第一个是0,当想取[l,r][l,r][l,r]区间内数时,需要dp[r+1].原创 2021-05-23 22:09:44 · 124 阅读 · 2 评论 -
leetcode 1035 LCS变种 不相交的线
这个题一看确实没什么思路,但是经过高手一提醒才发现,这个就是LCS问题。考虑必须是顺序的相等。dp的定义还是比较巧妙的,考虑两个数组0-i和0-j长度的LCS,这也是我们要考虑的子结构,之后填充就从上往下即可。class Solution {public: int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) { int m = nums1.size(), n = num原创 2021-05-22 16:47:18 · 57 阅读 · 0 评论 -
LCP 34 二叉树染色 树形dp
此题和打家劫舍问题有一点相似,但还是差的挺多的。主要是dp状态的定义,是以当前节点结尾的相连i个元素的最大可能值。更新时,如果不涂节点,返回两边最大值的和,如果涂的话,用两边的可能性组合进行更新,比打家劫舍还是要难的。/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int原创 2021-05-19 19:20:58 · 105 阅读 · 0 评论 -
leetcode 337 打家劫舍3 树形dp入门
经典的选或者不选问题。这个问题应该是自底向上的一个过程,因为我们最终只看根节点状态就可以知道结果,而不用统计所有底部信息,是较为方便的。之后我们考虑dp数组怎么存,一种使用树形数组存,另外就是dfs过程中存。对于这个题,dfs是一种很方便的方式,前序遍历就很方便,左右处理完后,中间看两边取或者不取的状态的最优值,这个和普通的打家劫舍定义不太一样。普通的一个数就记录了,这边要两个数,还是比较有趣的,也不用看上一个,思想相当于后序遍历。/** * Definition for a binary tree原创 2021-05-19 18:26:21 · 192 阅读 · 0 评论 -
leetcode 53,152 最大子数组和,最大子数组乘积 相反两次遍历问题
最大子数组和:其实这个问题较为贪心,如果你前面的>0,那你肯定要拿过来用,否则就不用,这个是核心。可以定义dp数组定义为以当前下标结尾的最大和,无需考虑开始元素,因为会贪心的选择。当前面<0时,自然就不用了。class Solution {public: int maxSubArray(vector<int>& nums) { int n = nums.size(); int ans = nums[0]; vect原创 2021-05-18 23:05:36 · 91 阅读 · 0 评论 -
leetcode 1143 最长公共子序列
此题最有趣的点在于,不需要对dp数组做初始化。只需定义dp下标为长度即可,这样凡是和0相关的都为0,咱们从一开始递推即可,很简单。这个操作看似简单,但可以省很多事,比如三维的LCS问题,就可以省一大堆麻烦。class Solution {public: int longestCommonSubsequence(string text1, string text2) { int n1 = text1.size(), n2 = text2.size(); int d原创 2021-05-05 22:15:12 · 47 阅读 · 0 评论 -
leetcode 263 264 313 丑数问题
丑数:质因数只有质数的数问题一:判断一个数是否为丑数直接除就好,因为一个数连乘or连除是logn的,根本不怕。class Solution {public: bool isUgly(int n) { if(n == 0)return false; while(n%2 == 0)n /= 2; while(n%3 == 0)n /= 3; while(n%5 == 0)n /= 5; return n==1;原创 2021-04-29 13:40:42 · 89 阅读 · 0 评论 -
求字典序最小的最长上升子序列
此题如背包问题一样,回溯是可以解决的,我们需要先得到以当前字符结尾的最大长度数组,即dp数组。我们可以用dp+二分方法,在得到单调栈数组的同时仍可以获得dp数组(这是一个技巧)之后便可以做回溯了,我们可以使用贪心的方式进行回溯。每次只需要找到最靠右的元素即可。因为若两个长度相同,靠前的一定比靠后的大,不然两个不可能相等,所以每次选靠后的就可以解决问题。class Solution {public: /** * retrun the longest increasing subsequ原创 2021-04-28 14:14:08 · 548 阅读 · 0 评论 -
leetcode 673 LIS个数
此题中,定义状态非常重要。定义ct[i]为以i序号结尾最长子序列的个数。状态的更新和dp数组是一起的。当dp[j] + 1 > dp[i]时,以i为结尾的字符首次达到他的最大值,直接赋值。当dp[j]+1 == dp[i]时,说明还有其他点可以满足。注意,由于以当前字符为结尾的最大长度只和前面的元素有关,因此遍历顺序和基本的LIS问题是一样的。class Solution {public: int findNumberOfLIS(vector<int>& nums) {原创 2021-04-28 13:14:14 · 103 阅读 · 0 评论 -
leetcode 377 组合总和4 背包装满问题 不同顺序方案个数
这个题是相当的神奇,相对于背包装满问题的方案个数,需要将两个循环的位置进行交换。和完全背包和01背包的关系有些相似,代码如下:typedef unsigned long long ll;class Solution {public: int combinationSum4(vector<int>& nums, int target) { ll dp[target+1]; memset(dp, 0, sizeof(dp)); d原创 2021-04-27 19:41:22 · 154 阅读 · 0 评论 -
多重背包问题
解法一:转化为01背包问题,直接把k个物品放置k次即可。#include <bits/stdc++.h>using namespace std;int main(){ int N, W; scanf("%d %d", &N, &W); int dp[W+1]; memset(dp, 0, sizeof(dp)); for(int i=0;i<N;i++) { int v, w, s;原创 2021-04-26 19:42:12 · 86 阅读 · 0 评论 -
leetcode 87 字符串多次翻转 分治+区间dp
解法一(错误):分治开始的想法很朴素,先找到切分位置,然后切分后处理,其中,用素数存状态来判断是否一个分割位置合理。这种做法有个问题就是,一旦有一次切分错了,可能就回不来了,这个很致命。所以应该考虑每种切分情况,但这样是一定会超时的...原创 2021-04-16 15:33:31 · 248 阅读 · 0 评论 -
剑指offer 14 切绳子求最大乘积 快速幂求余
此题要把绳子切成m段,求最大乘积。很直观的就是,要尽量相等的绳子才能有更大的乘积。关键是要切成多长的。从这个题解中,我们可以看到,切成3的就很好。这是从5开始的情况,3-4由于没法切3,或者切3不是最好,要用2切,而2需要用1切,这些特殊情况处理好就没问题了。值得注意的是,e真是个神奇的数字。...原创 2021-03-17 13:52:06 · 126 阅读 · 0 评论 -
leetcode5 最长回文子串
解法1:中心扩散简单,不提解法2:动态规划,从上往下填表很重要import numpy as npclass Solution: def longestPalindrome(self, s: str) -> str: dp = np.zeros((len(s), len(s))) l_str = s[0] l_len = 1 # 状态转移 for i in range(len(s)):原创 2020-10-20 09:26:09 · 72 阅读 · 0 评论 -
leetcode 514 自由之路 维特比算法
思路一(错误):贪心此题要做出来,只需要知道,当前字母到下一个字母最短位置。由于字母可以重复,每次选最近的字母的贪心策略可以产生局部最优,但全局的步数并不是最少的。class Solution: def get_step(self, idx1, idx2, length): min0, max0 = min(idx1, idx2), max(idx1, idx2) return min(max0-min0, length-max0+min0) def f原创 2020-11-11 14:16:01 · 231 阅读 · 0 评论 -
leetcode 28 字符串匹配 O(N)复杂度解法
解法一:暴力遍历长串,匹配到短串停止。此处提供一种比暴力稍好的策略,即不用每次都往前查找匹配,只有最后一个字母和中间字母都匹配再查找,这样能优化很多复杂度,最优复杂度为O(N),最坏仍为O(N(N-M))class Solution: def strStr(self, haystack: str, needle: str) -> int: if needle == '': return 0 def judge(pos): if ha原创 2020-11-28 16:04:12 · 315 阅读 · 0 评论