当前位置: 首页 > news >正文

力扣hot100 | 动态规划2 | 139. 单词拆分、300. 最长递增子序列、152. 乘积最大子数组、416. 分割等和子集、32. 最长有效括号

139. 单词拆分

力扣题目链接
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。

示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。

示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

递归

参考自灵茶山艾府。

class Solution:def wordBreak(self, s: str, wordDict: List[str]) -> bool:max_len = max(map(len, wordDict))  # 用于限制下面 j 的循环次数words = set(wordDict)  # 便于快速判断 s[j:i] in words@cache  # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)def dfs(i: int) -> bool:  # dfs(i) 的意义: s[:i]是否能被拆分if i == 0:  # 成功拆分!return Truefor j in range(i - 1, max(i - max_len - 1, -1), -1): # max(i-max_len-1,-1)直接写-1也能过if s[j:i] in words and dfs(j):return Truereturn Falsereturn dfs(len(s))
  • 时间复杂度 O(mL+nL2mL + nL^2mL+nL2),其中 mwordDict 的长度,LwordDict 中字符串的最长长度,ns 的长度。创建哈希集合需要 O(mL) 的时间。由于每个状态只会计算一次,动态规划的时间复杂度=状态个数×单个状态的计算时间动态规划的时间复杂度 = 状态个数 \times 单个状态的计算时间动态规划的时间复杂度=状态个数×单个状态的计算时间。本题状态个数等于 O(n)O(n)O(n),单个状态的计算时间为 O(L2)O(L^2)O(L2)(注意判断子串是否在哈希集合中需要 O(L)O(L)O(L) 的时间),所以记忆化搜索的时间复杂度为 O(nL2nL^2nL2)。
  • 空间复杂度 O(mL+nmL + nmL+n)。哈希集合需要 O(mL)O(mL)O(mL) 的空间。记忆化搜索需要 O(n)O(n)O(n) 的空间。

递推

class Solution:def wordBreak(self, s: str, wordDict: List[str]) -> bool:max_len = max(map(len, wordDict))  # 用于限制下面 j 的循环次数words = set(wordDict)  # 便于快速判断 s[j:i] in wordsn = len(s)f = [True] + [False] * nfor i in range(1, n + 1):for j in range(i - 1, max(i - max_len - 1, -1), -1):if f[j] and s[j:i] in words:f[i] = Truebreakreturn f[n]
  • 复杂度都不变。

300. 最长递增子序列

力扣题目链接
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

一、递推

参考自Krahets

class Solution:def lengthOfLIS(self, nums: List[int]) -> int:if not nums: return 0n = len(nums)dp = [1] * n # dp[i]定义:以nums[i]结尾的最长上升子序列长度for i in range(n):for j in range(i):if nums[i] > nums[j]: # 对每个符合的j都更新dp[i]的最大值dp[i] = max(dp[i], dp[j] + 1) return max(dp) # 全局最长不一定是以nums[-1]结尾的哦(不能写dp[n-1])
  • 时间复杂度 O(n^2)
  • 空间复杂度 O(n)

二、递推 + 二分查找优化

# Dynamic programming + Dichotomy.
class Solution:def lengthOfLIS(self, nums: [int]) -> int:tails, res = [0] * len(nums), 0for num in nums:i, j = 0, reswhile i < j:m = (i + j) // 2if tails[m] < num: i = m + 1 # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。else: j = mtails[i] = numif j == res: res += 1return res
  • 时间复杂度 O(n · log n)
  • 空间复杂度 O(n)

152. 乘积最大子数组

力扣题目链接
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

误区

本题是 53. 最大子数组和 的乘法版本,但不能套用代码,如下是错误的!

class Solution:def maxProduct(self, nums: List[int]) -> int:cur_pr = max_pr = nums[0]for num in nums[1:]:cur_pr = max(num, num * cur_pr)max_pr = max(cur_pr, max_pr)return max_pr

因为会遇到nums = [-2,3,-4]这种“负负得正”的情况,正确结果是24,但套用传统Kadane’s算法(如上)会导致-2被提前丢弃,返回错误答案3。—— 所以要调整思路【多维护一个变量】以应对“负负得正”的情况!

写法一:Modified Kadane’s

此方法参考自灵茶山艾府。

  • 【思路】需要在遍历 nums 的同时,维护两个信息:
    • fmax[i]f_{max}[i]fmax[i]:右端点下标为 i 的子数组的最大乘积。
    • fmin[i]f_{min}[i]fmin[i]:右端点下标为 i 的子数组的最小乘积。
  • 遍历时,对每个元素x = num[i],进行三种情况分类讨论但不用“人为分析“,直接取三种情况的max()min()就能同时维护最大最小值。

其实就是比 53. 最大子数组和 的情况多维护了一个fmin[i]f_{min}[i]fmin[i]fmax[i]f_{max}[i]fmax[i]就是以上错误写法中的cur_pr的数组形式。)

  • 【问】fmin[i]f_{min}[i]fmin[i]为什么要加到max(情况1, 情况2, 情况3)中?
  • 【答】代表了 “负负得正” 情况,f_min[i-1]为负数时说明它是 “绝对值最大的负数”,而f_max[i-1]表示 “绝对值最大的正数”
  • 所以面对nums = [-2,3,-4],遍历到-4时先前被错误抛弃的-2被保留在f_min中以待“转正”,转正后有可能比“最大正数”和“单独成段”的值更大。
class Solution:def maxProduct(self, nums: List[int]) -> int:n = len(nums)f_max = [0] * nf_min = [0] * nf_max[0] = f_min[0] = nums[0] # 初始化:以 nums[0] 为右端点的子数组乘积只能是 nums[0]for i in range(1, n):x = nums[i]# 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,# 或者单独组成一个子数组,只有 x 一个元素f_max[i] = max(f_max[i - 1] * x, f_min[i - 1] * x, x) # 分三类而不是两类,fmin考虑“负负得正”情况f_min[i] = min(f_max[i - 1] * x, f_min[i - 1] * x, x) # 同理,三类return max(f_max)
  • 时间复杂度 O(n)
  • 空间复杂度 O(n)

写法二:空间优化

此方法参考自灵茶山艾府
由于计算 f_max [i]f_min [i] 只会用到 f_max [i−1]f_min [i−1],不会用到更早的状态,所以可以用两个变量 f_maxf_min​滚动计算。

  • 【具体优化有两点】
      1. 状态转移方程简化为(不用存储数组了):
        fmax=max(fmax⋅x,fmin⋅x,x)fmin=min(fmax⋅x,fmin⋅x,x)f_{max} = max(f_{max}\cdot x,~f_{min}\cdot x,~ x)\\f_{min} = min(f_{max}\cdot x,~f_{min}\cdot x,~ x)fmax=max(fmaxx, fminx, x)fmin=min(fmaxx, fminx, x)
      1. 可以初始化f_max = f_min = 1因为 1 乘以 nums[0 ]等于 nums[0],这样就可以从下标 0 开始遍历

  • 【注意一点】f_maxf_min同时更新,不能写成两行! 若是写成如下两行,则更新f_min时用的就是下一轮的f_max了。
f_max = max(f_max * x, f_min * x, x)
f_min = min(f_max * x, f_min * x, x)

class Solution:def maxProduct(self, nums: List[int]) -> int:ans = -inf  # 注意答案可能是负数!f_max = f_min = 1 # 初始化为1,for x in nums:    # 则可以从i=0开始遍历f_max, f_min = max(f_max * x, f_min * x, x), \min(f_max * x, f_min * x, x)ans = max(ans, f_max)return ans
  • 时间复杂度 O(n)
  • 空间复杂度 O(1)

416. 分割等和子集

力扣题目链接
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

“恰好装满”0-1背包

参考自灵茶山艾府,后续优化【翻译成递推、空间优化、二进制方法】请见原出处。

from functools import cache
class Solution:def canPartition(self, nums: List[int]) -> bool:@cachedef dfs(i, c):# 含义:nums[:i+1]中任选某几件,能否 刚好组成和为c的子序列if i < 0: return c == 0 # 边界时看容量c是否已经被装满(减为0)# 考虑nums[i]选或不选if c < nums[i]: # 只能不选return dfs(i-1, c)# 选                  # 不选return dfs(i-1, c-nums[i]) or dfs(i-1, c)s = sum(nums) # 首先sum必须为偶数,不然整数数组凑不出小数的half值return s % 2 == 0 and dfs(len(nums) - 1, s // 2)
  • 时间复杂度 O(ns),其中 n 是 nums 的长度,s 是 nums 的元素和(的一半)。由于每个状态只会计算一次,动态规划的时间复杂度=状态个数×单个状态的计算时间动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间动态规划的时间复杂度=状态个数×单个状态的计算时间。本题状态个数等于 O(ns),单个状态的计算时间为 O(1),所以动态规划的时间复杂度为 O(ns)。
  • 空间复杂度 O(ns),保存多少状态,就需要多少空间。

32. 最长有效括号

力扣题目链接
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。

左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 “(()())”。

示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”

示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”

示例 3:
输入:s = “”
输出:0

以下题解参考自powcai

法一、栈方法【括号匹配 + 排序+最长连续子数组】

对于这种括号匹配问题,一般都是使用栈

我们先找到所有可以匹配的索引号,然后找出最长连续数列

  • 例如:s = )(()()),我们用栈可以找到,
    • 位置 2 和位置 3 匹配,
    • 位置 4 和位置 5 匹配,
    • 位置 1 和位置 6 匹配,

这个数组为:2,3,4,5,1,6 这是通过栈找到的,我们按递增排序:1,2,3,4,5,6

找出该数组的最长连续数列的长度就是最长有效括号长度!

所以时间复杂度来自排序:O(nlogn)。

class Solution:def longestValidParentheses(self, s: str) -> int:if not s: return 0# 将匹配成功的下标放入res(用一个栈st辅助,括号匹配思想)res, st = [], []for i in range(len(s)):if s[i] == '(':st.append(i)if st and s[i] == ')':res.append(st.pop())res.append(i)res.sort()# 双指针遍历res,维护最长连续子数组长度maxLmaxL = 0n = len(res)l = r = 0while l < n:r = l # 开始,固定l遍历r,找到最大连续区间while r < n - 1 and res[r + 1] == res[r] + 1:r += 1maxL = max(maxL, r - l + 1)l = r + 1 # 收缩左边界至 本轮考虑过的区间 的右边(新开始一轮)return maxL
  • 时间复杂度 O(n log n),复杂度主要来自排序的复杂度O(n log n)。
  • 空间复杂度 O(n)

时间优化的栈方法【最推荐】

省略排序的过程,直接在弹栈时候进行操作。

class Solution:def longestValidParentheses(self, s: str) -> int:if not s: return 0maxL = 0st = [-1] # 初始哨兵,作为第一个参照点for i in range(len(s)):if s[i] == '(':st.append(i)   # 压栈,等待匹配else:          # 若遇右括号st.pop()   # 先弹出(可能是匹配的'(',也可能是哨兵)if not st:       # 若栈空了,说明当前')'无法匹配st.append(i) # 让它成为新的参照点(原始方法中的“左指针 l-1”)else:            # 若匹配成功maxL = max(maxL, i - st[-1]) # 则更新最大长度return maxL
  • 时间复杂度 O(n),只遍历一次
  • 空间复杂度 O(n),栈最坏情况存储所有索引

法二、dp

我们用 dp[i] 表示以 i 结尾的最长有效括号;

s[i](,dp[i] 必然等于 0,因为不可能组成有效的括号;

那么 s[i])

2.1 当 s[i-1](,那么 dp[i] = dp[i-2] + 2

2.2 当 s[i-1]) 并且 s[i-dp[i-1] - 1](,那么 dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]

class Solution:def longestValidParentheses(self, s: str) -> int:n = len(s)if n == 0: return 0dp = [0] * nres = 0for i in range(n):if i>0 and s[i] == ")":if  s[i - 1] == "(":dp[i] = dp[i - 2] + 2elif s[i - 1] == ")" and i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(":dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]if dp[i] > res:res = dp[i]return res
  • 时间复杂度 O(n)
  • 空间复杂度 O(n)
http://www.dtcms.com/a/442363.html

相关文章:

  • 做外贸一般看什么网站卓越网站建设的优点
  • centos8安装docker【新】
  • LeetCode算法日记 - Day 61: 解数独、单词搜索(附带模版总结)
  • 李宏毅machine learning 2021学习笔记——transformer
  • hana C# 连接问题
  • 每日一个网络知识点:TCP/IP参考模型
  • 网站报价明细网络营销战略内容
  • springboot+vue会议室管理系统(源码+文档+调试+基础修改+答疑)
  • 不依赖WMI的硬件检测工具,数据更准、速度更快
  • k8s 部署 gitlab 公网无法访问排查
  • 昆明市住房和城乡建设局网站铜川网站建设哪家好
  • 国外医院网站设计微网站建设正规公司
  • 推广网站代码中国八大设计院排名
  • 【Nest】日志记录
  • 什么网站可以做汽车国际贸易php网站开发实例教程 课件
  • [Linux基础——Lesson11.Shell运行原理------王婆传媒]
  • 梦幻创意网站建设互动平台怎么注册
  • 第三十八章 ESP32S3 SPIFFS 实验
  • Seata 与 Redisson从底层到实战
  • 如何将wsl安装的Ubuntu系统从C盘移到D盘?
  • 怎么用阿里云做网站如何开发游戏
  • 网站服务器费用免费生成ppt的网站
  • 自动驾驶中的传感器技术62——USS(8)
  • AI时代数据存储和数据恢复 | 数据恢复损坏文件修复经验建议
  • 淄博网站制作定制中国寰球工程公司
  • MTK调试- 工程模式配置
  • 黑龙江生产建设兵团知识网站商城系统平台开发
  • 做麻将网站即墨网站设计
  • 网络请求完整指南:从零开始理解前端数据交互
  • Coze源码分析-资源库-编辑知识库-前端源码-核心逻辑/API