子串、子数组与子序列类型问题的动态规划求解(Leetcode题解-Python语言)

一般来说,子串和子数组都是连续的,而子序列是可以不连续的,遇到子序列问题基本上都是用动态规划求解。

53. 最大子数组和(剑指 Offer 42. 连续子数组的最大和)

class Solution:def maxSubArray(self, nums: List[int]) -> int:n = len(nums)dp = [-10001] * (n+1)dp[0] =  nums[0]for i in range(1, n):dp[i] = max(dp[i-1] + nums[i], nums[i])return max(dp)

把 dp 数组定义为:元素 dp[i] 表示以 nums[i] 为结尾的数组的连续子数组最大和。则初始条件为 dp[0] = nums[0] (只有一个元素)。如果知道了 dp[i-1],则 dp[i] 只有两种取值:dp[i-1] + nums[i] 和 nums[i],取两者中的较大值即可。

152. 乘积最大子数组

class Solution:def maxProduct(self, nums: List[int]) -> int:n = len(nums)if n == 1:return nums[0]max_dp = [0] * nmin_dp = [0] * nmax_dp[0] = min_dp[0] = nums[0]for i in range(1, n):max_dp[i] = max(max_dp[i-1] * nums[i], min_dp[i-1] * nums[i], nums[i])min_dp[i] = min(max_dp[i-1] * nums[i], min_dp[i-1] * nums[i], nums[i])return max(max(max_dp), max(min_dp))

求乘积的话能不能照搬上面的思路呢?是不可以的,因为乘积可能为负数,每次只取较大值的话是不会选择负数的,但是最大乘积可能由负负得正而来。解决方法是使用两个 dp 数组,一个记录最大值一个记录最小值(负的最大),这样当第一次出现负数时,结果会被 min_dp 记录下来,而第二次出现负数的时候,结果又会进入 max_dp。

674. 最长连续递增序列

class Solution:def findLengthOfLCIS(self, nums: List[int]) -> int:n = len(nums)dp = [1] * nfor i in range(1, n):if nums[i-1] < nums[i]:dp[i] = dp[i-1] + 1return max(dp)

这题的关键词是连续,所以如果在位置 i 的数字大于前一个数字,就记录这个位置的序列长度(dp[i])为前一个位置序列长度加一( dp[i-1] + 1)

300. 最长递增子序列

class Solution:def lengthOfLIS(self, nums: List[int]) -> int:n = len(nums)dp = [1] * nfor i in range(n):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)

将 dp 数组定义为:元素 dp[i] 表示以 nums[i] 为结尾的数组的最长递增子序列长度。初始化 dp 数组的每个元素都是1(最小都是它本身,长度为1)。当遍历到 dp[i] 时,它前面的每个 dp[j] (j < i) 我们都是知道的,需要从中找到能构成递增关系的(nums[i] > nums[j]),最大长度 max(dp[i], dp[j] + 1)。

1143. 最长公共子序列

class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)dp = [[0 for _ in range(n+1)] for _ in range(m+1)]ans = 0for i in range(1, m+1):for j in range(1, n+1):if text1[i-1] == text2[j-1]:dp[i][j] = dp[i-1][j-1] + 1else:dp[i][j] = max(dp[i-1][j], dp[i][j-1])return dp[-1][-1]

dp[i][j]:长度为 [0, i - 1] 的字符串 text1 与长度为 [0, j - 1] 的字符串 text2 的最长公共子序列

如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以 dp[i][j] = dp[i - 1][j - 1] + 1; 如果 text1[i - 1] 与 text2[j - 1] 不相同,那就看看 text1[0, i - 2] 与 text2[0, j - 1] 的最长公共子序列 和 text1[0, i - 1] 与 text2[0, j - 2] 的最长公共子序列,取最大的。

如果 dp[i][j] 表示的是长度为 [0, i] 的字符串 text1 与长度为 [0, j] 的字符串 text2 的最长公共子序列,在初始化上就麻烦不少,代码如下:

class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n = len(text1), len(text2)dp = [[0 for _ in range(n)] for _ in range(m)]ans = 0if text1[0] == text2[0]:dp[0][0] = 1for i in range(1, m):if text1[i] == text2[0] or dp[i-1][0] == 1:dp[i][0] = 1for j in range(1, n):if text1[0] == text2[j] or dp[0][j-1] == 1:dp[0][j] = 1for i in range(1, m):for j in range(1, n):if text1[i] == text2[j]:dp[i][j] = dp[i-1][j-1] + 1else:dp[i][j] = max(dp[i-1][j], dp[i][j-1])return dp[-1][-1]

673. 最长递增子序列的个数

class Solution:def findNumberOfLIS(self, nums: List[int]) -> int:n = len(nums)max_len = ans = 0dp = [1] * ncnt = [1] * nfor i in range(n):for j in range(i):# 如果当前元素可以加入递增序列,使得dp[j]可以+1if nums[i] > nums[j]:# 遇到更长的递增子序列,则更新if dp[j] + 1 > dp[i]:dp[i] = dp[j] + 1cnt[i] = cnt[j]  # 重置计数# 相同长度的递增子序列elif dp[j] + 1 == dp[i]:cnt[i] += cnt[j]if dp[i] > max_len:max_len = dp[i]ans = cnt[i]  # 重置计数elif dp[i] == max_len:ans += cnt[i]return ans

在上一题的基础上,要对最长递增子序列的个数进行计数,我们定义一个 cnt 数组,cnt[i] 表示以 nums[i] 结尾的最长递增子序列的个数。设 nums 的最长递增子序列的长度为 maxLen,那么答案 ans 为所有满足 dp[i] = maxLen 的 i 所对应的 cnt[i] 之和。关键是对当前元素可以加入递增序列(nums[i] > nums[j])后的情况进行分类讨论,以找出当前最长的递增子序列以及对它进行计数。

354. 俄罗斯套娃信封问题

class Solution:def maxEnvelopes(self, envelopes: List[List[int]]) -> int:envelopes.sort(key=lambda k: (k[0], -k[1]))n = len(envelopes)dp = [1] * nfor i in range(n):for j in range(i):if envelopes[i][1] > envelopes[j][1]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)

关键思路是对宽度进行升序排序而对高度进行降序排序,降序的目的是为了保证宽度相同时只有一个信封会被选入到最长递增子序列当中(如果高度也是升序,则会进入多个,但是它们的宽度相同,不符合题意)。最后求关于高度的最长递增子序列的长度即为答案。

198. 打家劫舍

class Solution:def rob(self, nums: List[int]) -> int:n = len(nums)if n <= 2:return max(nums)dp = [0] * ndp[0] = nums[0]dp[1] = max(nums[0], nums[1])for i in range(2, n):dp[i] = max(dp[i-1], dp[i-2] + nums[i])return dp[-1]

这题可以看作是不相邻子序列的最大和,dp[i] 表示到第 i 号房为止能偷到的最多钱,状态转移方程为偷了第 i - 2 家后再偷第 i 家或者偷了第 i - 1 家(不能偷第 i 家了)两者的最大值。

740. 删除并获得点数

class Solution:def deleteAndEarn(self, nums: List[int]) -> int:# 以数字作为下标,对应点数作为值maxVal = max(nums)total = [0] * (maxVal + 1)for val in nums:total[val] += val# 打家劫舍问题n = len(total)if n == 1:return total[0]dp = [0] * ndp[0] = total[0]dp[1] = max(total[0], total[1])for i in range(2, n):dp[i] = max(dp[i-2] + total[i], dp[i-1])return dp[n-1]

将出现的数字作为下标,数字出现次数 * 数字本身 = 数字对应的点数,点数作为值,构建一个 total 数组,即变成了对于 total 数组的打家劫舍问题。

213. 打家劫舍 II

class Solution:def rob(self, nums: List[int]) -> int:if not nums:return 0n = len(nums)if n == 1:return nums[0]if n == 2:return max(nums)dp_1 = [0] * ndp_2 = [0] * ndp_1[0] = nums[0]dp_1[1] = max(nums[0], nums[1])dp_2[1] = nums[1]dp_2[2] = max(nums[1], nums[2])for i in range(2, n-1):dp_1[i] = max(dp_1[i-2] + nums[i], dp_1[i-1])for i in range(3, n):dp_2[i] = max(dp_2[i-2] + nums[i], dp_2[i-1])return max(dp_1[-2], dp_2[-1])

房屋首尾相连了,意味着偷了第一家就不能偷最后一家,反之亦然。因此,我们可以分类讨论,把偷第一家和偷最后一家分别考虑,用两个 dp 分别算出两个方案的结果,取较大值即可。对于第一家和最后一家都不偷的情况,其实已经被包含在上面两种情况里面了,因为上面也只是考虑偷第一家或最后一家,但不一定偷。

337. 打家劫舍 III

class Solution:def rob(self, root: TreeNode) -> int:def postTravel(root):if not root: return 0, 0  # 偷,不偷left = postTravel(root.left)right = postTravel(root.right)# 偷当前节点, 则左右子树都不能偷val_1 = root.val + left[1] + right[1]# 不偷当前节点, 则取左右子树中最大的值val_2 = max(left) + max(right)return val_1, val_2return max(postTravel(root))

这题实际上不算子序列问题,而是树形 dp 问题,但还是打家劫舍系列的所以放一起了。使用的是后序遍历,因为要先获得左右子树的结果,然后对于当前节点,有偷或者不偷两种方案,都需要返回,取其中的较大值即可,对于左右子树也是一样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建

前言中间件(Middleware)对于Asp.NetCore项目来说&#xff0c;不能说重要&#xff0c;而是不能缺少&#xff0c;因为Asp.NetCore的请求管道就是通过一系列的中间件组成的&#xff1b;在服务器接收到请求之后&#xff0c;请求会经过请求管道进行相关的过滤或处理&#xff1b;正文…

leetcode647. 回文子串

一&#xff1a;题目 二&#xff1a;上码 class Solution { public:/**思路:动态规划五步走1>:确定dp数组以及下标的含义dp[i][j] 表示的是在[i,j]范围内的字串 是否是 回文子串&#xff0c;如果是的话那么dp[i][j] true2>确定dp数组的状态转移方程那么就有两种情况 s[i…

Leetcode周赛复盘——第 276 场力扣周赛

第一次参加周赛&#xff0c;AC了三道题&#xff0c;也算不错的成绩了&#xff0c;从现在开始每周的周赛我都会参加并且复盘&#xff0c;有兴趣的小伙伴可以一起讨论。 5980. 将字符串拆分为若干长度为 k 的组 class Solution:def divideString(self, s: str, k: int, fill: s…

leetcode516. 最长回文子序列

一:题目 二:上码 class Solution { public:/**思路:1.分析题意 这个是让我们求最值,那么首先想到动态规划2.动态规划1>:确定dp数组以及下标的含义dp[i][j] 表示字符串在[i,j]范围内的最长回文子序列2>:确定dp数组的状态递推公式那么就是s[i] 与 s[j] 相等 不相等两种情况…

Leetcode周赛复盘——第 278 场力扣周赛

5993. 将找到的值乘以 2 我的做法是将数组从小到大排序之后&#xff0c;再将找到的值乘以2&#xff1a; class Solution:def findFinalValue(self, nums: List[int], original: int) -> int:nums.sort()for num in nums:if original num:original * 2return original然而…

C#刷剑指Offer | 二叉搜索树的后序遍历序列

【C#刷题】| 作者 / Edison Zhou这是EdisonTalk的第289篇原创内容我们来用之前学到的数据结构知识来刷《剑指Offer》的一些核心题目&#xff08;精选了其中30道题目&#xff09;&#xff0c;希望对你有帮助&#xff01;本文题目为&#xff1a;二叉搜索树的后序遍历序列。1题目介…

leetcode739. 每日温度

一:题目 二:上码 // class Solution { // public: // vector<int> dailyTemperatures(vector<int>& temperatures) { // vector<int> ans(temperatures.size(),0);// for (int i 0; i < temperatures.size(); i) {// …

Leetcode周赛复盘——第 71 场力扣双周赛与第 279 场力扣周赛

双周赛&#xff1a; 5984. 拆分数位后四位数字的最小和 class Solution:def minimumSum(self, num: int) -> int:a, b, c, d sorted(list(map(int, str(num))))return 10 * (a b) c dstr(num)得到字符串序列&#xff0c;然后用map函数对序列的每个字符转换为数字&…

使用SWAGGER和ASP.NET CORE设置可选路由参数

使用SWAGGER和ASP.NET CORE设置可选路由参数根据OpenAPI 3.0&#xff0c;这是不可能的。但是&#xff0c;如果您真的希望成为现实呢&#xff1f;您是否必须解决并允许您的Swagger文档出错&#xff1f;我在这里向您展示如何使用Swagger和ASP.NET Core设置可选的路由参数。等等&a…

在数组中找重复数、只出现一次的数或丢失数的题目(Leetcode题解-Python语言)

在一维数组中的考察中&#xff0c;最常见的就是找出数组中的重复数、只出现一次的数或者丢失&#xff08;消失&#xff09;数等等。 一般来说&#xff0c;首先想到的就是用哈希表&#xff08;集合&#xff09;来记录出现过的数&#xff0c;基本所有的题都可以用集合来做&#…

Confluent官博:Kafka最牛队列,性能15倍于RabbitMQ!

“容器、Kubernetes、DevOps、微服务、云原生&#xff0c;这些技术名词的频繁出现&#xff0c;预兆着新的互联网技术时代的到来&#xff0c;大数据高并发将不再遥远&#xff0c;而是大部分项目都必须面对的&#xff0c;消息队列则是核心利器&#xff01;成熟的消息队列产品很多…

leetcode503. 下一个更大元素 II

一:题目 二:上码 class Solution { public:/**思路: 1.将两个nums拼接到一块这里拼接到一块,当我们最后的元素找不到比其大的时候 就会开始从头开始这样的话就可以继续进行 入栈 或者出栈的操作入栈就是比我栈顶小的元素&#xff0c;出栈的话 那就是 找到了比其大的元素了…

队列的基础概念与经典题目(Leetcode题解-Python语言)

队列是先入先出&#xff08;后入后出&#xff09;的数据结构&#xff0c;常用操作就 push 和 popleft&#xff0c;Python中用列表中的 pop(0) 或者 collection.deque的 popleft() 都可以。 普通队列 225. 用队列实现栈 class MyStack:def __init__(self):self.queue1 []sel…

跟我一起学.NetCore之中间件(Middleware)应用和自定义

前言Asp.NetCore中的请求管道是通过一系列的中间件组成的&#xff0c;使得请求会根据需求进行对应的过滤和加工处理。在平时开发中会时常引用别人定义好的中间件&#xff0c;只需简单进行app.Usexxx就能完成中间件的注册&#xff0c;但是对于一些定制化需求还得自己进行处理和封…

leetcode42. 接雨水

一:题目 二:上码 // class Solution { // public: // /**超时 // 思路: // 1.我们按列来计算 这就是表明的是 我们求取接雨水 向上的高度就是雨水量 // 但是这里的话我们的需要对雨水的高度 来进行判定 // 2.那么如何判定…

贪心的问题合集(Leetcode题解-Python语言)

贪心算法&#xff08;Greedy Algorithm&#xff09;&#xff1a;是一种在每次决策时采用当前状态下最优或最好的策略&#xff0c;从而希望导致结果是最好或最优的算法。 455. 分发饼干 class Solution:def findContentChildren(self, g: List[int], s: List[int]) -> int:…

Magicodes.IE之导入导出筛选器

总体设计Magicodes.IE是一个导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。在本篇教程&#xff0c;笔者将讲述如何使用Magicodes.IE的导入导出筛选器。在开始之前&#xff0c;我们需要先了解Magicodes.IE目前支持的筛选…

字符串经典题目(Leetcode题解-Python语言)

344. 反转字符串 class Solution:def reverseString(self, s: List[str]) -> None:"""Do not return anything, modify s in-place instead."""left 0right len(s) - 1while left < right:s[left], s[right] s[right], s[left]left 1…

谈了千百遍的缓存数据的一致性问题

“灵魂拷问保证缓存和数据库的一致性很简单吗&#xff1f;有哪些方式能保证缓存和数据库的一致性呢&#xff1f;如果发生了缓存和数据库数据不一致的情况怎么办呢&#xff1f;在上篇文章我们介绍了缓存的定义分类以及优缺点等&#xff0c;如果还没看的同学可以移步这里听说你会…

BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统

一:项目背景 项目描述 一个基本功能较为完整的后台管理项目。项目主要功能有&#xff1a;登录验证&#xff0c;登录功能还加入了随机验证码的验证&#xff1b; 用户注册&#xff0c;注册中密码基于srping 安全框架提供的加密(自动加盐)的密码储存方式&#xff0c;对注册重名进…