leetcode动态规划问题总结 Python

目录

一、基础理论

二、例题

1. 青蛙跳台阶

2. 解密数字

3. 最长不含重复字符的子字符串

4. 连续子数组的最大和

5. 最长递增子序列

6. 最长回文字符串

7. 机器人路径条数

8. 礼物的最大价值


一、基础理论

动态规划其实是一种空间换时间的基于历史数据的递推算法,甚至有时连空间也可以节省。动态规划算法,需要 3 个步骤。第一步决定用于记录历史计算结果的数据结构,例如 dp[];第二步构建递推公式,例如 dp[n]=dp[n-1]+dp[n-2];第三步设定初始值和递推顺序,例如 dp[0]=0, dp[1]=1。

二、例题

1. 青蛙跳台阶

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

示例:

输入:3

输出:3

本题首先使用 dp[i] 数据结构存储中间结算结果,表示 i 级台阶的跳法数量,由于要想跳到第 i 级台阶只能从第 i-1 级或者 i-2级台阶跳过来,所以递推公式为 dp[i] = dp[i-1] + dp[i-2],由于想要跳到第 1 级台阶只有一种跳法,要跳到第 2 级台阶有两种跳法,所以初始值 dp[1]=1, dp[2]=2。

def jumpFloor(n):if (n <= 2):return ndp = [0]*(n+1)           # 创建一个数组来保存历史数据,多补了一个用不到的0位dp[1] = 1                # 给出初始值dp[2] = 2for i in range(3, n+1):  # 通过关系式来计算dp[n]dp[i] = dp[i-1] + dp[i-2]return dp[n]

该问题还可以进一步扩展为:一只青蛙一次可跳上 1 级台阶,也可以跳上 2 级 … 它也可以跳上 n 级。求该青蛙跳上一个n级的台阶(n 为正整数)总共有多少种跳法。

扩展问题无法用动态规划来解,需要通过分析得到公式,具体为:

f(n)    = f(n-1) + f(n-2) + f(n-3) +… f(1)
f(n-1) =              f(n-2) + f(n-3) +… f(1)
f(n)    = 2*f(n-1)   n>1

2. 解密数字

现有一串神秘的密文 ciphertext,经调查,密文的特点和规则如下:

密文由非负整数组成,数字 0-25 分别对应字母 a-z

请根据上述规则将密文 ciphertext 解密为字母,并返回共有多少种解密结果。

示例:

输入: ciphertext = 216612

输出: 6

解释: 216612 解密后有 6 种不同的形式,分别是 "cbggbc","vggbc","vggm","cbggm","cqggbc" 和 "cqggm"

本题首先使用 dp[i] 表示长度为 i 的字符串的解法,然后分析递推公式。如果第 i-1 数字为 0,那 dp[i] == dp[i-1];如果第 i-1 数字为 1,那 dp[i] == dp[i-1] + dp[i-2];如果第 i-1 数字为 2,且第 i 数字小于 6,那 dp[i] == dp[i-1] + dp[i-2];如果第 i-1 数字大于 2,那 dp[i] == dp[i-1]。所以只需要 i-1 和 i 数字组成的数字大于等于 10 小于等于 25,则 dp[i] == dp[i-1] + dp[i-2],否则 dp[i] == dp[i-1]。需要注意,在使用 dp 时,我们在前面补一个用不到的 0,这样做的目的是让索引 i 与实际长度匹配,但是当需要访问 s 时需要注意 dp[i] 与 s[i-1] 对应。一定要理清楚 dp 与 s 的索引之间的关系,否则很容易搞混了。

def translateNum(num):s = str(num)dp = [1] * (len(s)+1)         # 定义状态列表,在0位置添加了一个1,以便于递推公式的进行,以及索引与实际长度匹配dp[1] = 1                     # 长度1时的解法for i in range(2, len(s)+1):  # 长度2到长度n的解法if int(s[i-2:i]) >9 and int(s[i-2:i]) < 26:  # dp[i]对应s[i-1]索引dp[i] = dp[i-1] + dp[i-2]else:dp[i] = dp[i-1]return dp[-1]

3. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 :

输入: "abcabcbb"

输出: 3      解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

该问题首先使用 dp[i] 表示以 s[i] 为结点的最长不包含重复字符的字符串长度,最终结果就是 dp 中的最大值。那递推公式很容易想到,如果 s[i] 不存在于以 s[i-1] 为结点的最长不包含重复字符的字符串中,那么 dp[i] = dp[i-1] + 1;如果s[i] 存在于以 s[i-1] 为结点的最长不包含重复字符的字符串中,且重复字符所在索引是 j,那么 dp[i] = i - j。此时引出一个问题,如何获取这个字符串,如何判断一个字符是否存在于这个字符串中,以及如何获取重复字符的索引,这个还是非常困难的。所以这里采用另外一种办法,可以使用字典数据结构把所有的字符和索引均存储起来,键是字符,值是索引,当字符存在重复时,会被覆盖,所以通过这个字典可以获取 s[j] 左侧是否存在重复字符,如果存在可以获得最近的索引,如果不存在可以使用默认值 -1 表示,那么此时就可以通过 i-j 与 dp[i-1] + 1 的大小比较以判断是否存在重复字符了,但这种办法会增加内存使用量。这里初始值 dp[0] = 1。

def lengthOfLongestSubstring(s):  # 本函数有很多优化点,但为了便于理解,这里没有优化if not s: return 0            # 如果为空,则返回 0  || 可优化掉dic = {}                      # 定义哈希表,用于获取最近相同字符的索引dic[s[0]] = 0                 # 初始化第一个字符对应的哈希表  || 可优化掉,直接放进循环里dp = [1] * len(s)             # 初始化第一个字符对应的 dp  || 可优化掉,直接使用一个变量值记录res = 1                       # 初始化第一个字符的 res 结果for i in range(1, len(s)):    # 从第二个字符开始处理j = dic.get(s[i], -1)     # 获取 s[i] 左侧最近相同元素的索引 jif dp[i-1] + 1 < i - j:   # 如果 i-j 大于 dp[i-1]+1 说明在 s[j] 并不存在于以 s[j-1] 为结点的最长无重复字符串中  || 可优化为min()函数dp[i] = dp[i-1] + 1else:                     # 反之dp[i] = i - jdic[s[i]] = i             # 哈希表记录每个字符,这会造成内存使用量偏高res = max(res, dp[i])return res

针对本问题,这里还有一个思路,即实时维护一个集合,当向右遍历字符串时,使得集合中的元素使用对应着当前的最长无重复字符串。那此时就需要一个左指针 left 和一个右指针 i,以及需要实时维护的集合 lookup。i 需要向右遍历,所以 i 对应一个 for 循环。每遍历一个 i 值,需要首先删除集合 lookup 中与 s[i] 重复的字符左侧的所有字符,这里使用了一个很巧妙的办法,即从 left 开始一直删除,直至不存在重复。最后需要将当前字符 s[i] 放入集合中,以完成针对 i 的遍历。此方案在空间消耗上要优于方案1。

def lengthOfLongestSubstring(s):if not s:return 0left = 0                      # 左指针lookup = set()                # 字典max_len = 0cur_len = 0for i in range(len(s)):       # i是右指针,每遍历一个i,lookup都是由left到i的元素组成的字典cur_len += 1while s[i] in lookup:     # 每遍历一个i,删除lookup中的重复元素,并同步修改leftlookup.remove(s[left])left += 1cur_len -= 1          # 附带着同步修改当前i对应字符串的长度if cur_len > max_len:max_len = cur_lenlookup.add(s[i])return max_len

4. 连续子数组的最大和

输入一个长度为 n 的整型数组 array,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

示例:

输入:[1,-2,3,10,-4,7,2,-5]

输出:18

该问题取 dp[i] 作为以 s[i] 为结尾的连续子数组的最大和,那 max(dp) 为最终结果。递推公式为 if dp[i-1]>0: dp[i]=dp[i-1]+s[i] else: dp[i]=s[i]。

def maxSubArray(nums):dp = [nums[0]] * len(nums)for i in range(1, len(nums)):if dp[i-1] > 0:dp[i] = dp[i-1] + nums[i]else:dp[i] = nums[i]return max(dp)

如果不仅想要获取最大和,还需要获取最大和所对应的字符串,那就需要增加几个变量实时记录当前 dp[i] 所对应的字符串,这里其实属于滑动窗口问题了,滑动窗口的右边界很容易界定,就是遍历的参数,左边界不太好想清楚,其实左边界只有在 dp[i-1] 小于 0 的时候才会更新,且更新为 i,想明白了这一点,那就好编程了。

def maxSubArray(nums):dp = [nums[0]] * len(nums)max_value = nums[0]beign = 0end = 0max_beign = 0max_end = 0for i in range(1, len(nums)):if dp[i-1] > 0:dp[i] = dp[i-1] + nums[i]else:dp[i] = nums[i]beign = i        # 这个是最关键的点,需要想清楚end = iif dp[i] > max_value:max_value = dp[i]max_beign = beignmax_end = endreturn (max_value, nums[max_beign:max_end+1])

5. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例:

输入:nums = [10,9,2,5,3,7,101,18]

输出:4    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

该问题选用 dp 作为中间数据存储结构,以 dp[i] 表示以 s[i] 为结尾的最大递增子序列长度。接下来是递推公式,首先想到的是与 dp[i-1] 之间的关系,但是这次的子序列是允许跳跃的,所以不一定只是与 dp[i-1] 之间有关系,所以仔细想清楚后,发现 dp[i] 其实与 dp[0]...dp[i-1] 都有关系,需要找出来的是在 s[i]>s[index] 前提下找到 max(dp[index]...)。

def lengthOfLIS(nums):dp = [1]*len(nums)for i in range(len(nums)):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j]+1)return max(dp)

如果想要同步获取这个子序列,那需要定义一个变量 result 保存所有的最长递增子序列,reslut[i] 存储的是以 s[i] 为结尾的最长递增子序列。然后在更新 dp 的时候同步更新 result。这里需要特别注意的是 [[]]*len(nums) 的方式属于浅拷贝,更新任何一个 [] 都会同步影响其它 []。在做 List 的整体赋值时一定注意好是需要赋值、浅拷贝还是深拷贝。深拷贝可以使用 copy 包或者 [:] 运算符。

import copydef lengthOfLIS(nums):dp = [1]*len(nums)max_dp = 1max_i = 0# results = [[]]*len(nums)                # 不能这样写,这是浅拷贝results = [[] for _ in range(len(nums))]  # 记录以s[i]结尾的最长递增子序列for i in range(len(nums)):for j in range(i,-1,-1):              # 检查i之前所有序列if nums[i] > nums[j]:             # 前提是s[i]大于s[j]if dp[i] < dp[j] + 1:         # 次之是序列长度要更长,因为存在相等的情况,所以存在答案不唯一dp[i] = dp[j] + 1results[i] = copy.deepcopy(results[j])  # 注意这里一定要用深拷贝# results[i] = results[j][:]            # 注意这里一定要用深拷贝results[i].append(nums[i])if max_dp < dp[i]:max_dp = dp[i]max_i = ireturn (dp[max_i], results[max_i])

6. 最长回文字符串

给你一个字符串 s,找到 s 中最长的回文字符串。

示例 :

输入:s = "babad"

输出:"bab"        解释:"aba" 同样是符合题意的答案。

本题采用二维 dp 作为中间存储数据结构,dp[i,j] 表示 s[i] 到 s[j] 是否为回文字符串,主对角线表示每个单独的字符均为长度 1 的回文字符,如果首尾两个元素相等,中间部分也是回文字符串,那当前字符串也为回文字符串,所以递推公式为 dp[i,j] = dp[i+1,j-1] and s[i]==s[j],从递推公式可以得到遍历输出是 i 从大到小 j 从小到大,至此可以获取任意字符串是否为回文字符串。在遍历的同时记录下最长长度和对应的起止点。

def longest_palindrome(s):dp = [[True]*len(s) for _ in range(len(s))]     # 注意深浅拷贝问题,直接相乘属于浅拷贝max_len = 1max_beign = 0max_end = 0for i in range(len(s)-2, -1, -1):for j in range(i+1, len(s)):dp[i][j] = dp[i+1][j-1] and s[i]==s[j]if dp[i][j] and max_len < j-i+1:        # 记录最长长度和对应起始终止位置max_len = j-i+1max_beign = imax_end = jreturn (max_len, s[max_beign:max_end+1])

本题还可以采用中心扩展法,即定义一个函数可以实现通过两边扩展遍历的方式寻找最长回文字符串,返回回文字符串的长度和起始点,然后遍历所有字符,检查每个字符对应的最长回文字符串,即可获取最长回文字符串。暴力遍历法的时间复杂度是O3,空间复杂度是O1,而动态规划的时间复杂度是O2,空间复杂度是O1,最后中心扩展法的时间复杂度是O2,空间复杂度是O1。

def longest_palindrome(s):def check(s, i, j):                             # 检查从i和j向两边扩散的字符串是否是回文字符串while i>=0 and j<len(s) and s[i]==s[j]:     # 实际仅仅用于 i=j 或者 i=j-1 两种情况i -= 1j += 1return j-i-1, i+1, j-1                      # 返回回文字符串的长度和起始以及终止点max_len = 1max_beign = 0max_end = 0for i in range(len(s)):len1, left1, right1 = check(s, i, i)len2, left2, right2 = check(s, i, i+1)if max_len < max(len1, len2):max_len = max(len1, len2)if len1 >= len2:max_beign = left1max_end = right1else:max_beign = left2max_end = right2    return s[max_beign:max_end+1]            

7. 机器人路径条数

一个机器人位于一个 mxn 网格的左上角(起始点在上图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。问总共有多少条不同的路径?

示例:

输入:m=3, n=7

输出:28

本问题需要准备一个二维的 dp 数据结构记录中间过程,dp[i,j] 表示从起点到 (i,j) 位置的路径条数,dp[-1,-1]即为最终结果。那递推公式显然就是 dp[i,j]=dp[i-1,j]+dp[i,j-1]。初始条件,第一行和第一列全部为1,递推顺序是第二行开始从左到右,从上到下。

def uniquePaths(m, n):dp = [[1 for _ in range(n)] for _ in range(m)]for i in range(1, m):for j in range(1, n):dp[i][j] = dp[i - 1][j] + dp[i][j - 1]return dp[-1][-1]

8. 礼物的最大价值

在一个 mxn 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例:

输入:[ [1,3,1], [1,5,1], [4,2,1] ]

输出:12        解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

本问题需要准备一个二维的 dp 数据结构记录中间过程,dp[i,j] 表示从起点到 (i,j) 位置的最多礼物,dp[-1,-1] 即为最终结果。那递推公式显然就是 dp[i,j]=max(dp[i-1,j]+dp[i,j-1])+grid[i,j]。初始条件,第一行和第一列全部为累计求和结果,递推顺序是第二行开始从左到右,从上到下。

def maxValue(grid):m, n = len(grid), len(grid[0])dp = [[grid[0][0] for _ in range(n)] for _ in range(m)]for i in range(1, m):dp[i][0] = dp[i-1][0] + grid[i][0]for j in range(1, n):dp[0][j] = dp[0][j-1] + grid[0][j]for i in range(1, m):for j in range(1, n):dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]return dp[-1][-1]

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

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

相关文章

【airsim】两分钟,用python脚本获取虚拟数据集

现有的教程在配置基于python的airsim环境过程中&#xff0c;都要求执行完整的编译步骤。然而&#xff0c;在linux和windows上的编译过程中会遇到诸多问题&#xff0c;大概需要半天左右时间。 如果你只是想采集一些数据&#xff0c;测试一下虚拟数据的效果&#xff0c;那么解决…

【分布式】分布式链路跟踪技术

为什么需要分布式链路追踪 提到分布式链路追踪&#xff0c;我们要先提到微服务。相信很多人都接触过微服务。微服务是一种开发软件的架构和组织方法&#xff0c;它侧重将服务解耦&#xff0c;服务之间通过API通信。使应用程序更易于扩展和更快地开发&#xff0c;从而加速新功能…

【Python从入门到进阶】46、58同城Scrapy项目案例介绍

接上篇《45、Scrapy框架核心组件介绍》 上一篇我们学习了Scrapy框架的核心组件的使用。本篇我们进入实战第一篇&#xff0c;以58同城的Scrapy项目案例&#xff0c;结合实际再次巩固一下项目结构以及代码逻辑的用法。 一、案例网站介绍 58同城是一个生活服务类平台&#xff0c…

qt-C++笔记之QProcess

qt-C笔记之QProcess code review! 文章目录 qt-C笔记之QProcess一.示例&#xff1a;QProcess来执行系统命令ls -l命令并打印出结果说明 二.示例&#xff1a;QProcess来执行系统命令ls -l命令并打印出结果&#xff0c;代码进一步丰富三.示例&#xff1a;使用 QProcess 在 Qt 中…

el-date-picker日期时间选择器限制可选的日期范围

业务场景&#xff1a;需要限制日期时间选择器可选择的日期&#xff0c;有两种模式&#xff0c; 一种是已知范围&#xff0c;只能选已知范围内的日期&#xff0c; 另一种是知道最近天数&#xff0c;只能选今天往前的天数内的日期&#xff0c;超出不能选。 <el-date-picker v-…

高性能、可扩展、分布式对象存储系统MinIO的介绍、部署步骤以及代码示例

详细介绍 MinIO 是一款流行的开源对象存储系统&#xff0c;设计上兼容 Amazon S3 API&#xff0c;主要用于私有云和边缘计算场景。它提供了高性能、高可用性以及易于管理的对象存储服务。以下是 MinIO 的详细介绍及优缺点&#xff1a; 架构与特性&#xff1a; 开源与跨平台&am…

加密流量:成功 NDR 策略的关键因素

IT 决策者之间一直在就基于网络的可视性日益增长的需求进行对话。然而&#xff0c;由于超过 95% 的基于互联网的流量都被加密&#xff0c;因此需要实时解密作为成功的网络检测和响应(NDR) 策略的要求&#xff0c;这在某种程度上是一个棘手的问题。 此外&#xff0c;决策者还面…

【C++核心编程(四)】

一、继承 继承是面向对象三大特性之一。 有些类与类之间存在特殊的关系&#xff0c;例如下图中: 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承的技术&#xff0c;减少重复代…

python统计分析——箱线图(plt.boxplot)

参考资料&#xff1a;用python动手学统计学 使用matplotlib.pyplot.boxplot()函数绘制箱线图 import numpy as np import pandas as pd from matplotlib import pyplot as pltdata_set1np.array([2,3,3,4,4,4,4,5,5,6]) data_set2np.array([[2,3,3,4,4,4,4,5,5,6],[5,6,6,7,7…

十八:爬虫-JS逆向(下)

一&#xff1a;AES与DES DES对称加密,是一种比较传统的加密方式,其加密运算、解密运算使用的是同样的密钥&#xff0c;信息的发送者。和信息的接收者在进行信息的传输与处理时&#xff0c;必须共同持有该密钥(称为对称密码),是一种对称加密算法。一般来说加密用的是encrypt()函…

[redis] redis的安装,配置与简单操作

一、缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且…

给您的应用添加弹窗

概述 在我们日常使用应用的时候&#xff0c;可能会进行一些敏感的操作&#xff0c;比如删除联系人&#xff0c;这时候我们给应用添加弹窗来提示用户是否需要执行该操作&#xff0c;如下图所示&#xff1a; 弹窗是一种模态窗口&#xff0c;通常用来展示用户当前需要的或用户必须…