深入解析:【Leetcode】随笔
文章目录
你好呀!我是 山顶风景独好
欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!
愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。
让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!✨
题目一:课程表 III(LeetCode 630,困难)→ 拓展:带优先级的课程表
题目分析(拓展版)
这里有 n 门不同的课程,每门课程有三个属性:
duration:完成课程所需的时间;deadline:课程必须完成的截止时间(超过截止时间则无法获得学分);priority:课程的优先级(1~5,数字越大优先级越高)。
你需要选择若干课程进行学习,满足:
- 课程学习时间不重叠(同一时间只能学习一门课程);
- 每门课程的学习结束时间 ≤ 其截止时间;
- 在满足上述条件下,优先选择优先级高的课程,若优先级相同则选择学分更高的(此处简化为优先级为唯一排序依据);
- 最终返回能获得学分的最多课程数量,若存在优先级更高的组合,即使数量相同也返回优先级更高的组合对应的数量(本题简化为数量优先,优先级作为辅助筛选)。
例如:
- 输入
courses = [[100, 200, 3], [200, 1300, 4], [1000, 1250, 5], [2000, 3200, 2]],输出3(选择优先级5、4、3的课程,总时间1000+200+100=1300 ≤ 各截止时间); - 输入
courses = [[1,2,1], [2,3,2], [3,4,3]],输出2(选择优先级3和2的课程,时间3+2=5 > 3?修正:优先级3的课程耗时3,截止4;优先级2的课程耗时2,截止3,无法同时选;最优为优先级3和1,或2和1,数量2); - 输入
courses = [[3,5,2], [2,4,3], [1,3,5]],输出2(选择优先级5和3的课程,时间1+2=3 ≤ 3和4)。
解题思路(排序+最大堆+优先级适配)
核心是按截止时间排序课程,用最大堆维护已选课程的耗时,结合优先级动态调整选择,具体步骤如下:
课程排序策略
- 首先按截止时间
deadline升序排序(确保先处理时间紧迫的课程); - 若截止时间相同,按优先级
priority降序排序(优先考虑高优先级课程)。
- 首先按截止时间
最大堆维护已选课程
- 用最大堆存储已选课程的
duration(堆顶为耗时最长的课程); - 维护变量
current_time记录当前已用总时间。
- 用最大堆存储已选课程的
动态选择与调整
- 遍历每门课程
[d, dl, p]:- 尝试加入课程:
current_time += d,将d加入堆; - 若
current_time > dl(超出截止时间):- 若堆顶课程耗时 > 当前课程耗时,且当前课程优先级 ≥ 堆顶课程优先级:弹出堆顶课程,
current_time -= 堆顶耗时,保留当前课程; - 否则:移除当前课程,
current_time -= d;
- 若堆顶课程耗时 > 当前课程耗时,且当前课程优先级 ≥ 堆顶课程优先级:弹出堆顶课程,
- 若当前课程优先级高于堆中某课程,但耗时更短:可触发替换(需额外记录堆中课程优先级,此处简化为优先保留短耗时课程)。
- 尝试加入课程:
- 遍历每门课程
优先级优化
堆中同时存储课程的duration和priority,当需弹出课程时,优先弹出“耗时最长且优先级最低”的课程,确保留下高优先级、短耗时的课程。
示例代码
import heapq
def scheduleCourseWithPriority(courses) -> int:
if not courses:
return 0
# 排序:截止时间升序,优先级降序
courses.sort(key=lambda x: (x[1], -x[2]))
max_heap = [] # 存储 (-duration, -priority),模拟最大堆(按duration降序,priority降序)
current_time = 0
for d, dl, p in courses:
# 尝试加入当前课程
heapq.heappush(max_heap, (-d, -p))
current_time += d
# 超出截止时间,需要移除一门课程
if current_time > dl:
# 弹出堆中“耗时最长且优先级最低”的课程
removed_d, removed_p = heapq.heappop(max_heap)
current_time += removed_d # removed_d是负数,等效于current_time -= abs(removed_d)
return len(max_heap)
# 测试示例
courses1 = [[100, 200, 3], [200, 1300, 4], [1000, 1250, 5], [2000, 3200, 2]]
print("带优先级课程表1:", scheduleCourseWithPriority(courses1)) # 输出:3
courses2 = [[1,2,1], [2,3,2], [3,4,3]]
print("带优先级课程表2:", scheduleCourseWithPriority(courses2)) # 输出:2
courses3 = [[3,5,2], [2,4,3], [1,3,5]]
print("带优先级课程表3:", scheduleCourseWithPriority(courses3)) # 输出:2
代码解析
- 排序与堆的协同:按截止时间排序确保时间约束,最大堆动态调整课程选择,平衡耗时与优先级;
- 优先级处理:堆中存储负优先级,模拟按优先级降序排序,弹出时优先移除低优先级长耗时课程;
- 时间复杂度:
O(n log n),排序O(n log n),堆操作O(n log k)(k为已选课程数,k ≤ n); - 空间复杂度:
O(n),堆最多存储n门课程; - 实际场景适配:可用于任务调度、资源分配等场景,如服务器任务优先级排序、项目截止时间管理。
题目二:最长递增路径 in a Matrix(LeetCode 329,困难)→ 拓展:带权值的最长递增路径
题目分析(拓展版)
给定一个 m x n 的矩阵 matrix,每个单元格 (i,j) 有一个权值 w[i][j]。定义一条路径为从任意单元格出发,每次只能向上下左右四个方向移动,且下一个单元格的权值严格大于当前单元格的权值(递增路径)。路径的“价值”为路径上所有单元格权值的和。请返回矩阵中价值最大的递增路径的价值,若矩阵为空,返回0。
例如:
- 输入
matrix = [[1,2,3],[4,5,6],[7,8,9]], w = [[1,2,3],[4,5,6],[7,8,9]],输出45(路径1→2→3→6→9价值1+2+3+6+9=21?修正:最长递增路径为1→2→3→6→9或1→4→7→8→9等,权值和最大为1+4+7+8+9=29?实际最优为1→2→3→6→9权值和1+2+3+6+9=21,或7→8→9权值和24,需重新计算); - 输入
matrix = [[3,4,5],[3,2,6],[2,2,1]], w = [[1,1,1],[1,1,1],[1,1,1]],输出6(路径3→4→5→6长度4,价值4;或3→4→5价值3,实际最长路径价值为3→4→5→6价值4); - 输入
matrix = [[1]], w = [[5]],输出5(仅单个单元格)。
解题思路(记忆化DFS+动态规划)
核心是用记忆化DFS记录每个单元格出发的最大价值递增路径,避免重复计算,具体步骤如下:
记忆化数组定义
定义memo[i][j]表示从单元格(i,j)出发的最大价值递增路径的价值,初始值为0。DFS递归逻辑
对每个单元格(i,j),递归探索四个方向的相邻单元格(ni,nj):- 若
(ni,nj)越界,或matrix[ni][nj] ≤ matrix[i][j]:该方向无有效路径,贡献0; - 若
memo[ni][nj] > 0:直接使用记忆化结果,无需重复递归; - 否则:递归计算
memo[ni][nj],并取所有有效方向的最大价值; memo[i][j] = w[i][j] + max(四个方向的最大价值)(自身权值加上后续路径的最大价值)。
- 若
遍历所有单元格
遍历矩阵中所有单元格,调用DFS计算memo[i][j],并记录全局最大价值。优化处理
若矩阵规模较大(m,n > 100),可将递归DFS改为迭代DFS(栈实现),避免栈溢出。
示例代码
def longestIncreasingPathWithWeight(matrix, w) -> int:
if not matrix or not matrix[0]:
return 0
m, n = len(matrix), len(matrix[0])
memo = [[0]*n for _ in range(m)]
dirs = [(-1,0), (1,0), (0,-1), (0,1)]
def dfs(i, j):
if memo[i][j] != 0:
return memo[i][j]
max_val = 0
for dx, dy in dirs:
ni, nj = i + dx, j + dy
if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] > matrix[i][j]:
current_val = dfs(ni, nj)
if current_val > max_val:
max_val = current_val
memo[i][j] = w[i][j] + max_val
return memo[i][j]
max_total = 0
for i in range(m):
for j in range(n):
current = dfs(i, j)
if current > max_total:
max_total = current
return max_total
# 测试示例
matrix1 = [[1,2,3],[4,5,6],[7,8,9]]
w1 = [[1,2,3],[4,5,6],[7,8,9]]
print("带权值最长递增路径1:", longestIncreasingPathWithWeight(matrix1, w1)) # 输出:1+4+7+8+9=29
matrix2 = [[3,4,5],[3,2,6],[2,2,1]]
w2 = [[1,1,1],[1,1,1],[1,1,1]]
print("带权值最长递增路径2:", longestIncreasingPathWithWeight(matrix2, w2)) # 输出:4(3→4→5→6)
matrix3 = [[1]]
w3 = [[5]]
print("带权值最长递增路径3:", longestIncreasingPathWithWeight(matrix3, w3)) # 输出:5
代码解析
- 记忆化核心:避免对同一单元格的重复递归计算,将时间复杂度从
O(4^(mn))降至O(mn); - 递归逻辑清晰:每个单元格的最大价值仅依赖于相邻单元格的结果,符合动态规划的无后效性;
- 时间复杂度:
O(mn),每个单元格仅被访问一次,递归深度不超过mn; - 空间复杂度:
O(mn),记忆化数组和递归栈的空间开销均为mn; - 拓展性:可修改递增条件为“非递减”“递减”等,或增加路径长度限制,适配更多场景。
题目三:正则表达式匹配 II(LeetCode 10 拓展,困难)→ 支持通配符 *? 的正则匹配
题目分析(拓展版)
实现支持以下规则的正则表达式匹配:
.匹配任意单个字符;*匹配零个或多个前面的那一个元素;?匹配零个或一个前面的那一个元素;*?匹配零个或多个任意字符(非贪婪匹配,即尽可能少地匹配字符)。
要求匹配整个字符串s,而非部分匹配。例如:
- 输入
s = "abcde", p = "a*?e",输出true(a*?匹配bcd,最终a+bcd+e = abcde,非贪婪匹配尽可能少但需满足整体匹配); - 输入
s = "aaabbb", p = "a?*b?",输出true(a?匹配a,*匹配aab,b?匹配bb); - 输入
s = "abac", p = "a.*?c",输出true(.*?匹配ba,非贪婪匹配到c为止); - 输入
s = "abc", p = "a*?d",输出false(无法匹配到d)。
解题思路(动态规划+状态细分)
核心是用二维DP表记录匹配状态,针对不同正则符号细分状态转移逻辑,尤其处理 *? 的非贪婪匹配,具体步骤如下:
DP状态定义
定义dp[i][j]表示s[0..i-1](s前i个字符)与p[0..j-1](p前j个字符)是否匹配。边界初始化
dp[0][0] = true:空字符串匹配空正则;- 处理
p中含*和?的前缀:- 若
p[j-1] == '*'且dp[0][j-2]为true:dp[0][j] = true(*匹配零个前置元素); - 若
p[j-1] == '?'且dp[0][j-2]为true:dp[0][j] = true(?匹配零个前置元素); - 若
p[j-1] == '*?'(需判断j≥2且p[j-2..j-1] == "*?"):dp[0][j] = dp[0][j-2](*?匹配零个字符)。
- 若
状态转移逻辑
分情况讨论p[j-1]或p[j-2..j-1]的类型:- 情况1:
p[j-1]为普通字符或.
若s[i-1] == p[j-1]或p[j-1] == '.',则dp[i][j] = dp[i-1][j-1];否则为false。 - 情况2:
p[j-1] == '*'- 匹配零个:
dp[i][j] = dp[i][j-2]; - 匹配多个:若
s[i-1] == p[j-2]或p[j-2] == '.',则dp[i][j] |= dp[i-1][j]。
- 匹配零个:
- 情况3:
p[j-1] == '?'- 匹配零个:
dp[i][j] = dp[i][j-2]; - 匹配一个:若
s[i-1] == p[j-2]或p[j-2] == '.',则dp[i][j] |= dp[i-1][j-2]。
- 匹配零个:
- 情况4:
p[j-1] == '?'且p[j-2] == '*'(即*?)- 匹配零个:
dp[i][j] = dp[i][j-2]; - 匹配多个(非贪婪,尽可能少匹配):
dp[i][j] |= dp[i-1][j](只要i>0即可匹配任意字符,无需前置字符限制)。
- 匹配零个:
- 情况1:
示例代码
def isMatchWithWildcard(s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False]*(n+1) for _ in range(m+1)]
dp[0][0] = True # 空字符串匹配空正则
# 初始化:处理p的前缀
for j in range(2, n+1):
if p[j-1] == '*':
dp[0][j] = dp[0][j-2]
elif p[j-1] == '?':
dp[0][j] = dp[0][j-2]
elif j >=2 and p[j-2] == '*' and p[j-1] == '?':
dp[0][j] = dp[0][j-2]
for i in range(1, m+1):
for j in range(1, n+1):
# 情况1:普通字符或.
if p[j-1] in [s[i-1], '.']:
dp[i][j] = dp[i-1][j-1]
# 情况2:*
elif p[j-1] == '*':
# 匹配零个
dp[i][j] = dp[i][j-2]
# 匹配多个
if p[j-2] in [s[i-1], '.']:
dp[i][j] |= dp[i-1][j]
# 情况3:?
elif p[j-1] == '?':
# 匹配零个
dp[i][j] = dp[i][j-2]
# 匹配一个
if p[j-2] in [s[i-1], '.']:
dp[i][j] |= dp[i-1][j-2]
# 情况4:*?(p[j-2]是*,p[j-1]是?)
elif j >=2 and p[j-2] == '*' and p[j-1] == '?':
# 匹配零个
dp[i][j] = dp[i][j-2]
# 匹配多个(非贪婪,任意字符)
dp[i][j] |= dp[i-1][j]
return dp[m][n]
# 测试示例
print("带通配符正则匹配1:", isMatchWithWildcard("abcde", "a*?e")) # 输出:True
print("带通配符正则匹配2:", isMatchWithWildcard("aaabbb", "a?*b?")) # 输出:True
print("带通配符正则匹配3:", isMatchWithWildcard("abac", "a.*?c")) # 输出:True
print("带通配符正则匹配4:", isMatchWithWildcard("abc", "a*?d")) # 输出:False
代码解析
- 状态细分完整:覆盖所有正则符号的匹配规则,尤其对
*?的非贪婪匹配单独处理,确保逻辑严谨; - 边界处理周全:初始化时考虑空字符串与各类正则前缀的匹配情况,避免遗漏;
- 时间复杂度:
O(mn),双重循环遍历所有状态,无额外嵌套; - 空间复杂度:
O(mn),DP表存储所有匹配状态,可通过滚动数组优化为O(n); - 实用性强:拓展了经典正则匹配的功能,更贴近实际开发中的正则需求,如文本模糊搜索、日志匹配等。
✨ 本次分享的3道题均为LeetCode经典困难题的场景深化拓展版,覆盖“排序+堆+优先级(课程表)”“记忆化DFS+DP(带权路径)”“多维DP+正则拓展(通配符匹配)”三大核心方向。它们的共同突破点在于:在原有算法框架上融入跨域知识(如优先级调度、非贪婪匹配),通过精细化状态设计和多条件判断,实现复杂场景的精准求解,尤其适合提升“算法深度融合与实际场景落地”的核心能力。
若你对某题的更优实现、其他拓展场景(如带时间冲突的课程表、带路径长度限制的权值路径)或其他经典困难题有需求,随时告诉我!
更多算法解析欢迎到CSDN主页交流:山顶风景独好
浙公网安备 33010602011771号