Leetcode 动态规划 3
题号直达: 300 最长递增子序列 | 152 乘积最大子数组 | 416 分割等和子集 | 32 最长有效括号
LeetCode 300. 最长递增子序列 ↑
题目
给定整数数组 nums,返回其中严格递增子序列的最长长度。
子序列:由数组删除若干元素(可删 0 个)得到、且不改变剩余元素相对顺序的序列。
严格递增:序列中后项必须大于前项。
测试样例
- 示例 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,7]
输出:1
提示
1 <= nums.length <= 2500-10^4 <= nums[i] <= 10^4
进阶:能否将时间复杂度降到 O(n log n)?(可用二分 /「耐心排序」思路,本题代码为 O(n²)。)
代码(Python)
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = [0] * len(nums)
dp[0] = 1
for i in range(1, len(nums)):
dp[i] = 1
first = -float('inf')
flag = 0
for j in range(i):
if nums[j] < nums[i] and dp[j] > first:
first = dp[j]
flag = 1 # 表示存在更小的合法前驱,非「排头」单独成列
if flag:
dp[i] = first + 1
return max(dp)
思路解析
dp[i] 表示以 nums[i] 结尾的最长严格递增子序列长度。初始化 dp[i] = 1(仅含自身)。
对所有 j < i 且 nums[j] < nums[i] 的位置,可从 j 结尾的最优长度转移:
dp[i] = max(dp[j] + 1)。实现中用 first 滚动维护当前最大的 dp[j],flag 标记是否至少有一个合法 j(若没有则保持长度为 1)。
答案为 max(dp)。
- 时间复杂度:O(n²),
n = len(nums)。 - 空间复杂度:O(n)。
- 进阶:维护有序数组 + 二分可将更新降为 O(log n) 每次,总体 O(n log n)。
LeetCode 152. 乘积最大子数组 ↑
题目
给定整数数组 nums,找出数组中非空连续子数组的乘积最大值,返回该乘积。
- 只含一个元素的子数组,乘积即为该元素本身。
- 题目保证测试用例的答案在 32 位有符号整数范围内;任意子数组的乘积也可视为在 32 位整数范围内。
测试样例
- 示例 1:
nums = [2, 3, -2, 4]
输出:6
解释:子数组[2, 3]的乘积最大,为6。 - 示例 2:
nums = [-2, 0, -1]
输出:0
解释:[-2, -1]不是连续子数组,不能一起乘出2。
提示
1 <= nums.length <= 2 * 10^4-10 <= nums[i] <= 10- 任意子数组乘积保证在 32 位整数范围内
代码(Python)
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
max_dp = [0] * len(nums)
min_dp = [0] * len(nums)
max_dp[0] = nums[0]
min_dp[0] = nums[0]
for i in range(1, len(nums)):
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_dp)
思路解析(如图示递推)
乘法与加法不同:负负得正,区间最大乘积未必来自「上一步最大 × 当前数」,也可能来自上一步最小(很负)× 当前负数。因此要同时维护两个量:
max_dp[i]:以nums[i]结尾的连续子数组的最大乘积。min_dp[i]:以nums[i]结尾的连续子数组的最小乘积(最负)。
每一步在三种候选里取值:接上 i-1 的最大链、接上 i-1 的最小链、从 i 重新开始(仅 nums[i])。f_max、f_min 与代码中 max_dp、min_dp 的对应关系见下图。
全局答案为 max(max_dp)。
- 时间复杂度:O(n)。
- 空间复杂度:O(n);可滚动为 O(1)。
LeetCode 416. 分割等和子集 ↑
题目
给定一个只包含正整数的非空数组 nums,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
测试样例
- 示例 1:
nums = [1, 5, 11, 5]
输出:true
解释:可分为[1, 5, 5]与[11],和均为11。 - 示例 2:
nums = [1, 2, 3, 5]
输出:false
提示
1 <= nums.length <= 2001 <= nums[i] <= 100
代码(Python)
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
if len(nums) < 2:
return False
summary = sum(nums)
if summary % 2 == 1:
return False
else:
target = summary // 2
maxNum = max(nums)
if maxNum > target:
return False
dp = [[False for _ in range(target + 1)] for _ in range(len(nums))]
for i in range(len(dp)):
dp[i][0] = True
dp[0][nums[0]] = True
for i in range(1, len(nums)):
for j in range(1, target + 1):
if j >= nums[i]:
dp[i][j] = (dp[i - 1][j]) or dp[i - 1][j - nums[i]]
else:
dp[i][j] = dp[i - 1][j]
return dp[len(nums) - 1][target]
思路解析
LeetCode 32. 最长有效括号 ↑
题目
给你一个只包含 '(' 与 ')' 的字符串 s,找出最长的、合法且连续的括号子串的长度。
合法指括号能按顺序配对、形如平衡括号串(例如 "(())" 合法)。
测试样例
- 示例 1:
s = "(()"
输出:2
解释:最长合法子串为"()"。 - 示例 2:
s = ")()())"
输出:4
解释:最长合法子串为"()()"。 - 示例 3:
s = ""
输出:0
提示
0 <= s.length <= 3 * 10^4s[i]为'('或')'
代码(Python)
class Solution(object):
def longestValidParentheses(self, s):
"""
:type s: str
:rtype: int
"""
dp = [0] * len(s)
for i in range(len(s)):
if s[i] == '(':
dp[i] = 0
# 发现右括号
else:
# 处理前期数组越界情况
if i == 0:
dp[i] = 0
elif i == 1:
if s[0] == '(':
dp[i] = 2
else:
dp[i] = 0
else:
if s[i - 1] == '(':
dp[i] = dp[i - 2] + 2
else:
index = i - dp[i - 1] - 1
if index < 0:
dp[i] = 0
else:
if s[index] == '(':
if index - 1 >= 0:
dp[i] = dp[i - 1] + dp[index - 1] + 2
else:
dp[i] = dp[i - 1] + 2
else:
dp[i] = 0
if dp:
return max(dp)
else:
return 0
思路解析

浙公网安备 33010602011771号