1217 words
6 minutes
日拱两卒(二)
2026-07-09

90#

给一个括号序列,问最长的一串合法括号。

合法的一串括号一定是连续的,而且每个右括号一定是和最近的左括号配对。

拿栈存左括号,先扫一遍把所有配对的左右括号标记,再扫一遍找最长的一串标记。

class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = []
n = len(s)
v = [False] * n
for i in range(n):
if s[i] == '(':
stack.append(i)
elif s[i] == ')' and len(stack):
left = stack.pop()
v[left] = v[i] = True
ans, cnt, i = 0, 0, 0
while i < n:
if v[i]:
cnt += 1
ans = max(ans, cnt)
else:
cnt = 0
i += 1
return ans

89#

能否把一个数组里的数字分成两个集合,每个集合的和相等。

先判奇偶。

问题变成能否从n个数里选某些数使得和为m,是背包问题。

class Solution:
def canPartition(self, nums: List[int]) -> bool:
s = sum(nums)
if s & 1:
return False
n = len(nums)
@cache
def dfs(i, j) -> bool:
if i == 0:
return j == 0 or j == nums[0]
if j < 0:
return False
return dfs(i - 1, j) or dfs(i - 1, j - nums[i])
return dfs(n - 1, s / 2)

88#

乘积最大子数组。

因为这个数组里有负数,所以要同时维护只考虑前i个数且取第i个数的最大值和最小值,可能负负得正。

class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
f = [float('-inf')] * n
g = [float('inf')] * n
f[0] = nums[0]
g[0] = nums[0]
for i in range(1, n):
f[i] = max(f[i - 1] * nums[i], g[i - 1] * nums[i], nums[i], f[i])
g[i] = min(g[i - 1] * nums[i], f[i - 1] * nums[i], nums[i], g[i])
return max(f)

87#

最长上升子序列。

最重要的是理解O(nlogn)做法的思想。

先看代码实际意味着做的事情。

想象数组是一副扑克牌,现在按以下规则分成若干堆牌堆:

为当前牌找到第一个牌堆顶部牌大于等于它的堆,放上去。

如果没找到,就以这个牌新开一个堆。

最后牌堆的数量就是LIS长度。

为什么两者等价呢?

每个牌堆,都精确对应了一个“某个长度的上升子序列”的最好结尾。

k个牌堆顶部的牌,是目前为止,能构成"长度为k的LIS"的最小可能结尾数字。

具体例子,考虑[3, 1, 5]

  • 新建第一个堆,堆顶是3,意味着想构成长度为1的子序列,末尾最小可以是3

  • 1 < 3,想构成长度为1的子序列,末尾最小可以是113更有潜力。

  • 5比前两个数都大,无法去优化长度为1的子序列的末尾,但它带来了长度为2的子序列的想象空间,新开一个堆。

核心比喻抗遗忘:转化分牌堆,维护一个有“潜力”的结构。

class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
d = []
n = len(nums)
for num in nums:
l, r = 0, len(d)
while l < r:
mid = (l + r) >> 1;
if num <= d[mid]:
r = mid
else:
l = mid + 1
if l < len(d):
d[l] = num
else:
d.append(num)
return len(d)

86#

单词拆分。给定字符串和单词列表,问字符串能不能由列表内若干单词组成。

如果这里没用set,用了dict的话,不要写mp[s[j:i]],要写s[j:i] in mp

Python对于没有的Key会直接抛一个异常。

class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
n = len(s)
dp = [False] * (n + 1)
word_set = set(wordDict)
dp[0] = True
for i in range(1, n + 1):
for j in range(0, i):
dp[i] = dp[i] or (dp[j] and s[j:i] in word_set)
return dp[n]

85#

零钱兑换。问某一个总额能不能由若干固定面额的零钱凑出来,每种零钱有无数个。

记忆化搜索就完了。

class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
@cache
def dfs(remains) -> int:
if remains < 0:
return float("inf")
if remains == 0:
return 0
res = float("inf")
for coin in coins:
res = min(res, dfs(remains - coin) + 1)
return res
res = dfs(amount)
return res if res != float("inf") else -1

84#

一个数最少能由几个完全平方数相加得到。

简单DP。

class Solution:
def numSquares(self, n: int) -> int:
squares = set()
i = 1
while i * i <= n:
squares.add(i * i)
i += 1
@cache
def dfs(x) -> int:
if x <= 0:
return float("inf")
if x in squares:
return 1
res = float("inf")
for square in squares:
res = min(res, dfs(x - square) + 1)
return res
return dfs(n)

83#

打家劫舍。

不能连着抢两家相邻的。简单DP。

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

82#

打印杨辉三角形。

简单DP。

class Solution:
def generate(self, numRows: int) -> List[List[int]]:
f = [[1] for _ in range(numRows)]
for i in range(1, numRows):
for j in range(1, i + 1):
f[i].append(f[i - 1][j - 1])
if j < i:
f[i][j] += f[i - 1][j]
return f

81#

爬楼梯。

简单DP。

class Solution:
def climbStairs(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0] = dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]