| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 数据结构与算法之动态规划 做题思路总结 附详解 -> 正文阅读 |
|
[数据结构与算法]数据结构与算法之动态规划 做题思路总结 附详解 |
个人学习 代码随想录 的做题笔记,如果对你有帮助,请一键三连(点赞+收藏+关注)哦~ 感谢支持!欢迎各位在评论区与博主友好讨论!缓慢更新中…… 一般从以下几点分别考虑:
1.斐波那契数列: 0,1,1,2,3…… 求前两个数之和可得此数列。
实际上实现这个数列用a,b,result三个值就可以了。 比如:
2.最小路径和: 依题意,到F(i,j)能从上面或左面,需要前面的值才能得出接下来的值,建立二维数组存储对应格子的最短路径和?
有障碍版本: 自己写的繁琐:?
大佬写的:?
?for循环跳出,进行下一次循环的条件: if (obstacleGrid[i][j] == 1) continue;
即如果当前格子有障碍,就不算谁能到达它了,此障碍需要被略过。? 3.爬楼梯问题 使用最小花费爬楼梯,带权值楼梯。 依旧是从前一个台阶或前两个台阶可以到达当前台阶。
4.?整数拆分
5.不同的二叉搜索树 1个节点:1个二叉树 2个节点:2个二叉树 3个节点:5?个二叉树 ? 可以发现: 1为头结点:2个右子树的布局与2个节点时相同; 2为头结点:左子树和右子树与1个节点时相同; 3为头结点:2个左子树的布局与2个节点时相同 此时便可想到一种递推关系: F(1)=1, F(2)=F(1)*F(0)+F(1)*F(0)=2,?即左子树为1节点*右子树0个节点的树的个数+左子树为0节点*右子树1个节点的树的个数 F(3)=F[2] * F[0] + F[1] * F[1] + F[0] * F[2]=2*1+1*1+1*2=5 模拟一下该过程可得递推公式,i,j的范围
?背包问题:背包:最大容量;物品:价值、体积、每个物品的数量 01背包:每个物品的数量只有一个,放一个或者不放入背包 完全背包:每个物品的数量有无数个,放几个或者不放入背包 二维数组的01背包:
?? 1.子状态:f[i][j]:前i个物品放入大小为j的背包中所获得的最大价值 2.递推状态:装不下和装得下时放还是不放: ①背包容量不够不能放入第i号物品,f[i-1][j];? ②背包容量够可以放入第i号物品,包括两种状态:放进去或者不放进去,选择一种最终价值最大的。 f[i-1][j]:不放; f[i - 1][j - W[i]] + V[i]:先为第i号物品腾出空间,即减去该物品的容量后包内还剩多少容量,再加上i号物品的价值。 f[i][j]=max(f[i-1][j], f[i - 1][j - W[i]]? + V[i])。 3.初始值:f[i][0]=0:0~i个物品,背包容量为0时,一个物品都放不进去,价值仍为0。 f[0][j]=0:没放物品价值也为0 4.遍历顺序:双重循环,先遍历谁都行。从递推公式可得f[i][j]由(i-1,j),和(i-1,j - W[i])得到的。在左上角方向(包括正上方向)。 5.返回结果:f(N-1,m);
一维数组01背包: 上面的算法在计算第i行元素时,只用到第i-1行的元素,所以二维的空间可以优化为一维空间。 1.子状态:f[j]:大小为j的背包中所获得的最大价值 2.递推状态:装不下和装得下时放还是不放: ①背包容量不够不能放入第i号物品,f[[j];? ②背包容量够可以放入第i号物品,包括两种状态:放进去或者不放进去,选择一种最终价值最大的。 f[j]:不放,容量不变; f[ j - W[i] ] + V[i]:先为第i号物品腾出空间,即减去该物品的容量后包内还剩多少容量,再加上i号物品的价值。 f[ j ]=max( f[ j ], f[ j - W[ i ] ] + V[ i ])。 3.初始值:都初始为0 f[0][j]=0:没放物品价值也为0 4.遍历顺序:双重循环,只能先遍历物品再遍历背包容量,因为如果先遍历背包,就是在当前容量中,现有物品中选出价值最大一个的放入。倒叙遍历是保证物品i只被放入一次。如果正序遍历物品 i 就会被重复加入多次。 二维数组正序遍历是因为f[ i ][ j ]通过上一层f[i - 1][j]计算而来,本层的f[ i ][ j ]不会被覆盖。 5.返回结果:f(m);
6.分割等和子集 每一个数字只能选一次,有最大和的限制-->01背包存在。分割成两个子集,和相等。那直接找和为sum/2的子集就行了。
7.最后一块石头的重量 背包最值:每次选2个石头,最终让差值最小。那么把石头分为两堆,每堆石头都选接近(所有石头总重量/2)。把所有石头放进容量为sum/2的背包,求放进去的石头的最大重量x。差值就是sum-2x。 8.目标和 背包组合:要求装满背包容量有几种方法。数组和sum,要被加的数字之和a,要被减的数字(正数)之和b,题目求的目标和 t。 a+b=sum,a-b=t。两式联立求得a=(sum+t)/2,由此可知sum+t为偶数,a也为偶数。
9.一和零 dp[i][j],i个0和j个1时对应的最多子集。 递推:可以从放入当前子集,或者不放入,选出最大的。dp[i][j]不放入,dp[i-zero][j-one]+1在 放入后还应该有的0,1的个数对应的子集数的基础上+1。 三维数组表示: dp[k][i][j] 表示在前 i个字符串中,使用 i 个 0 和 j 个 1 的情况下最多可以得到的字符串数量。 那去掉一个维度,最外层遍历的就是strs中k个字符串。每个字符串统计0,1个数,遍历物品。内层遍历背包容量i,j,倒序遍历。 10.零钱兑换II 看了题解中有说本题像?爬楼梯 这个题。爬楼梯扩充成每次可走1,2……m阶台阶,到达n级台阶有几种方法,是个排列问题。 每种钱可以用无数次,最终和为已给容量大小。题目给出的样例是组合而不是排列,意为 2+2+1与1+2+2是相同的。外部物品,内部背包,且物品的顺序是不变的。 物品无限次选取,而01背包只能选一次物品,保证下标j之前的dp[j]都是经过计算的就行。
11.组合总和 物品无限,有确定的总和即容量,求排列数,完全背包。按排列计算,不同的排列方式就是不同的方法。
12.零钱兑换? 每种硬币的数量是无限的,凑成总金额所需的?最少的硬币个数?:典型完全背包问题。样例显示不强调组合还是排列,硬币有顺序和没有顺序都不影响硬币的最小个数,for循环内外可交换。
13.完全平方数 乍一看,看不出来什么。模拟一遍过程,就看出来是零钱兑换的相似问题了。 注意以下几点: 1.dp[0]=0。为了能进循环并赋值后面的数,实际无意义。除此之外都初始化为INT_MAX,因为求的是最小值 ?2.物品遍历,本来想的是再建一个物品数组,看了答案才知道可以直接dp[j-i*i]. 3.先遍历背包:i从1开始,因为完全平方数从1开始
先遍历物品:
14.单词拆分 ?字符串是背包,字典是物品。字典里的单词可以无限取出放入背包内。
?背包做了这些题,稍微一变是不是感觉自己就不会了呢? 15.打家劫舍 通过前面的计算后面的值,该类题也是典型动态规划问题。
3.初始值:dp[0]=nums[0],dp[1]=max(nums[0],nums[1]) 4.遍历顺序:?从前到后 5.返回结果:dp[nums.size()-1] 16.打家劫舍II 本题可以转为两个子问题。 从下标0号开始考虑,到倒数第二个,即不选最后一个。 从下标1号到最后一个,即不选第一个。 也可以第一个和最后一个都不选,但这种情况包含在上述两种中了,即他们中不一定非选第一个或是最后一个,具体情况具体分析。 分别求他俩的最大值(和上面的题思路一样,不过是取值范围不同),再算他俩谁大。 17.打家劫舍III 只想到了后序遍历……? 当前节点被偷,它的子节点不能偷,子节点的子节点可以考虑。不偷当前节点,可以偷它的子节点。子节点的子节点的子节点……递归! 不由得想到三种遍历方法,要分别在左子树,右子树找大的。因为通过递归函数的返回值来做下一步计算。 定义一个数组a,下标0表示不偷,下标1为偷。 如果当前为空就返回{0,0}。 递归遍历左子树,右子树。 单层遍历:当前节点被偷,它的子节点不能偷。不偷当前节点,可以选最大子节点(子节点的子节点)偷。 最终返回{单层遍历的两个值} 18.买卖股票的最佳时机 暴力解非常明显的超时了……
3.初始值:dp[0][0]-=prices[0]:第一天先买股票,dp[0][1]=0:没有股票 4.遍历顺序:?从前到后 5.返回结果:dp[prices.size()-1] 19.买卖股票的最佳时机II 可以多次买卖,那么第i天新买的股票就可能是从前几天没有股票时获得的利润里扣钱,即使最终结果是负值也是正常的。
?20.买卖股票的最佳时机III 仅限两笔买卖,一共五种状态。 0:无操作 1:第一次买入 2:第一次卖出 3:第二次买入 4:第二次卖出 ? 1.子状态:dp[i][0],第i天拥有股票时现金的最大金额,dp[i][1],第i天没有股票时现金的最大金额 2.?递推状态:
3.初始值:dp[0][0]=0:dp[0][1]-=prices[0],dp[0][3]-=prices[0] 买入,现金就减少。卖出,现金增加,并且最终取的是最大值,收获利润小于0也没必要计算。 4.遍历顺序:?从前到后 5.返回结果:dp[prices.size()-1][4],因为最后一次卖出的价格一定比第一次卖的贵 21.买卖股票的最佳时机III ?通过上题找规律。 0:无操作 1:第一次买入 2:第一次卖出 3:第二次买入 4:第二次卖出 5:第三次买入 6:第三次卖出 …… 偶数卖出,奇数买入。 1.子状态:dp[i][j],第i天状态j时现金的最大金额 2.?递推状态:
3.初始值:dp[0][0]=0:dp[0][1]-=prices[0],dp[0][3]-=prices[0]……奇数买入, 买入,现金就减少。卖出,现金增加,并且最终取的是最大值,收获利润小于0也没必要计算。 4.遍历顺序:?从前到后 5.返回结果:dp[prices.size()-1][2*k],因为最后一次卖出的价格一定比第一次卖的贵 22.买股票冷冻期 看了好多题解,分了4个状态:今天买,不持有股票有两种:(前天卖今天保持,今天卖 ),今天冷冻期。 感觉还是两种状态简洁一点,也好理解。但上面这种分多种状态讨论的思想要学会。 0:有股票
1:没股票
初始值第一天买入即为负值?
23.买股票手续费 比买股票II多了在卖出时减去手续费,因为买卖结束后才算一笔交易,所以不能在买入时就减手续费。 24.最长递增子序列 经典题目!一定要会! 1.子状态:dp[i],前i个字符里最长递增子序列的长度 2.递推状态:如果nums[i]>nums[i-1],dp[i]可由dp[i-1]+1得出,即前i-1个字符再加上第i个字符。 i循环遍历nums数组,令0<= j <=i-1。每次还要比较当前dp[i]和dp[i-1]+1哪一个更大,也就是更新最长子串。 3.初始值:每个字符都设为1个长度。 4.遍历顺序:由dp[i-1]得到所以从前往后。 5.结果:返回的是最长。滚动数组过程中无法确定i遍历完后就是最大,所以要判断遍历完j后当前数组的dp[i]是否最大。
25.最长连续递增子序列 关键在于递增,比较dp[i],dp[i-1]的值。递推公式不用再取max了,因为dp[i]意为:以i结尾的递增子序列长度=以i-1结尾的递增序列长度+1。 最终结果要取max,因为自己遍历一遍数组发现最大值不在数组末尾。 递推公式实现后一定要自己算一遍答案。 26.最长公共子序列 1.子状态:两个数组长度不一,采用二维数组分别代表两数组长度。dp[i][j]:n1数组前i个字符与n2数组前j个字符拥有的公共子序列长度。 2.递推状态:题意可知,答案是不连续。从下标1开始。 n1[i-1]与n2[j-1]相等时:dp[i][j]=dp[i-1][j-1]+1。即前从开始到前一个字符得到的最大长度+1 不相等:dp[i][j]=max(dp[i-1][j],dp[i][j-1])?从n1[0,i-2]与n2[0,j-1],n1[0,i-1]与n2[0,j-2]的最长子序列选最大的 3.初始值:0 4.遍历顺序:正序 5.结果dp[n1.size()][n2.size()] 27.判断子序列 编辑距离系列开始啦! 1.子状态:两数组长度不一,采用二维数组分别代表两数组长度。不连续,dp[i][j]:n1数组前i个字符与n2数组前j个字符拥有的公共子序列长度。 2.递推状态: 相等:a[i][j]=a[i-1][j-1]+1;? 不相等:要删除当前字符(j-1),即a[i][j]?要看s[i - 1]与 t[j - 2]的比较结果了:a[i][j] = a[i][j - 1]; 3.初始值:a[i][0]表示以i-1为下标结尾的字符串,它与空串的相同序列长度为0 4.遍历顺序:可知a[i][j]来自a[i-1][j-1],a[i][j - 1],所以为正序 5.结果:a[s.size()][t.size()]表示s与t的最长公共序列,如果和s长度相同就得到结果了 其实本题用双指针更简单一点吧~
28.不同的子序列? 如果单独拿出来这题,这类题做得少的话,估计不容易想到动态规划的做法(好吧,博主在说博主自己呜呜)。 1.子状态:两个字符串长度不一,二维数组。dp[i][j]为s以下标为i-1?出现和?t以下标j-1结尾的序列相同的个数。 2.递推状态:要得到dp[i][j],注意dp数组从1开始 相等:s[i-1]==t[j-1]:可能使用s[i-1]--->s[0,i-1]匹配t[0,j-1]--->dp[i-1][j-1],也可能不用--->s[0,i]匹配t[j-1],就是去用s数组里 i 前面的字符--->dp[i-1][j]
不相等:dp[i][j]=dp[i-1][j],既然不相等,就不用s[i-1]来匹配了。 3.初始值:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];???dp[i][j] = dp[i - 1][j];?? 那么第0行第0列都得初始化: dp[0][j]:s为空,无法匹配t,=0; dp[i][0]:t为空,空集是任何字符串的子集,或者理解为,s数组删n个字符,就能得到空串与空串t匹配,=1; dp[0][0]也和dp[i][0]的理解一致,=1。 4.遍历顺序:来自左上,上面,所以正序。 5.结果:dp[s.size()][t.size()] 放个代码: 发现如果用int类型会溢出,那就用long?long吧。
?29.两个字符串的删除操作 本题可以理解为:两个字符串的公共子序列,最后返回两个数组长度相加-公共子序列*2。就能得到最短操作步数。 两个数组都能删了。
30.编辑距离? 1.子状态:dp[i][j]? w1数组下标i-1结尾,与 以j-1下标结尾的w2,的最近编辑距离。 2.递推状态: w1[i-1]==w2[j-1]:不操作,就等于dp[i-1][j-1] w1[i-1]!=w2[j-1]:删除、增加、替换 删除:w1删除1个:dp[i-1][j]+1 删除和增加是一样的:w1: ja 和? w2: a,ja删除j或者a增加j,编辑距离一样?dp[i][j-1]+1 替换:w1替换掉w1[i-1],让他与w2[j-1]相等,即w1数组下标i-2结尾,与 以j-2下标结尾的w2,的最近编辑距离。dp[i-1][j-1]+1,在进行替换的数组里:dp[i-1][j-1]表示前面的已经相等了,再加当前i-1下标的字符被替换。 3.初始值: dp[i][0]:以下标i-1的w1,与空字符串的?最短编辑距离:删除i个 dp[0][j]:以下标j-1的w2,与空字符串的?最短编辑距离:删除j个 4.遍历顺序: 来自左边,上边,左上,所以正序 5.结果:遍历一遍即可得到最后的答案在尾部。 31.回文子串 动态规划和双指针都行,先来看看双指针: 分别以s的每个字符为中心点,向两边扩散,找两边相等的,这是奇数个字符时的做法。 偶数个字符,比如caac,则要以两个中心字符向两边扩散。 ? 动态规划: 1.子状态:dp[i][j]:[i,j]闭区间内是否为回文子串 2.递推状态: s[i]==s[j]: ①i==j:a是回文子串 ②i+1=j:aa也是 ③j-1>i:已经s[i]==s[j],下标i与j差了很多,隔了很远,就得缩短空间-->dp[i+1][j-1]判断是否文回文子串。 s[i]!=s[j]: dp[i][j]=false 3.初始值: 都为false 4.遍历顺序: dp[i][j]由dp[i+1][j-1],也就是左下方推出,就得从下往上遍历。 5.结果: 数一数总共几个true
32.最长回文子序列 1.子状态:dp[i][j],区间内回文子序列的长度 2.递推状态: s[i]==s[j]: dp[i][j]=dp[i+1][j-1]+2:加上相等的两字符 s[i]!=s[j]: 更新最大值,dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);? 既然两边不相等,那就去掉一边看看是否能为最大值。
3.初始值: 式子无法推出dp[i][i],那就初始为1,其他都为0. 4.遍历顺序: 来自左下,左,下。从下往上遍历。 5.结果: 下->上遍历,左->右,结果应该在右上角。dp[0][s.size()-1] 如果对你有帮助,请一键三连(点赞+收藏+关注)哦~ 感谢支持!让更多的人看到~ 欢迎各位在评论区与博主友好讨论!(????) ? ?(表情包来源网络) |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 1:26:19- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |