深入解析:【Leetcode】随笔

你好呀!我是 山顶风景独好
欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!
愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。
让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!✨

题目一:课程表 III(LeetCode 630,困难)→ 拓展:带优先级的课程表

题目分析(拓展版)

这里有 n 门不同的课程,每门课程有三个属性:

  1. duration:完成课程所需的时间;
  2. deadline:课程必须完成的截止时间(超过截止时间则无法获得学分);
  3. 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)。

解题思路(排序+最大堆+优先级适配)

核心是按截止时间排序课程,用最大堆维护已选课程的耗时,结合优先级动态调整选择,具体步骤如下:

  1. 课程排序策略

    • 首先按截止时间 deadline 升序排序(确保先处理时间紧迫的课程);
    • 若截止时间相同,按优先级 priority 降序排序(优先考虑高优先级课程)。
  2. 最大堆维护已选课程

    • 用最大堆存储已选课程的 duration(堆顶为耗时最长的课程);
    • 维护变量 current_time 记录当前已用总时间。
  3. 动态选择与调整

    • 遍历每门课程 [d, dl, p]
      1. 尝试加入课程:current_time += d,将 d 加入堆;
      2. current_time > dl(超出截止时间):
        • 若堆顶课程耗时 > 当前课程耗时,且当前课程优先级 ≥ 堆顶课程优先级:弹出堆顶课程,current_time -= 堆顶耗时,保留当前课程;
        • 否则:移除当前课程,current_time -= d
      3. 若当前课程优先级高于堆中某课程,但耗时更短:可触发替换(需额外记录堆中课程优先级,此处简化为优先保留短耗时课程)。
  4. 优先级优化
    堆中同时存储课程的 durationpriority,当需弹出课程时,优先弹出“耗时最长且优先级最低”的课程,确保留下高优先级、短耗时的课程。

示例代码

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→91→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记录每个单元格出发的最大价值递增路径,避免重复计算,具体步骤如下:

  1. 记忆化数组定义
    定义 memo[i][j] 表示从单元格 (i,j) 出发的最大价值递增路径的价值,初始值为0。

  2. 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(四个方向的最大价值)(自身权值加上后续路径的最大价值)。
  3. 遍历所有单元格
    遍历矩阵中所有单元格,调用DFS计算 memo[i][j],并记录全局最大价值。

  4. 优化处理
    若矩阵规模较大(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 拓展,困难)→ 支持通配符 *? 的正则匹配

题目分析(拓展版)

实现支持以下规则的正则表达式匹配:

  1. . 匹配任意单个字符;
  2. * 匹配零个或多个前面的那一个元素;
  3. ? 匹配零个或一个前面的那一个元素;
  4. *? 匹配零个或多个任意字符(非贪婪匹配,即尽可能少地匹配字符)。

要求匹配整个字符串s,而非部分匹配。例如:

  • 输入 s = "abcde", p = "a*?e",输出 truea*? 匹配 bcd,最终 a+bcd+e = abcde,非贪婪匹配尽可能少但需满足整体匹配);
  • 输入 s = "aaabbb", p = "a?*b?",输出 truea? 匹配 a* 匹配 aabb? 匹配 bb);
  • 输入 s = "abac", p = "a.*?c",输出 true.*? 匹配 ba,非贪婪匹配到 c 为止);
  • 输入 s = "abc", p = "a*?d",输出 false(无法匹配到 d)。

解题思路(动态规划+状态细分)

核心是用二维DP表记录匹配状态,针对不同正则符号细分状态转移逻辑,尤其处理 *? 的非贪婪匹配,具体步骤如下:

  1. DP状态定义
    定义 dp[i][j] 表示 s[0..i-1]si 个字符)与 p[0..j-1]pj 个字符)是否匹配。

  2. 边界初始化

    • dp[0][0] = true:空字符串匹配空正则;
    • 处理 p 中含 *? 的前缀:
      • p[j-1] == '*'dp[0][j-2]truedp[0][j] = true* 匹配零个前置元素);
      • p[j-1] == '?'dp[0][j-2]truedp[0][j] = true? 匹配零个前置元素);
      • p[j-1] == '*?'(需判断 j≥2p[j-2..j-1] == "*?"):dp[0][j] = dp[0][j-2]*? 匹配零个字符)。
  3. 状态转移逻辑
    分情况讨论 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 即可匹配任意字符,无需前置字符限制)。

示例代码

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主页交流:山顶风景独好

posted @ 2025-12-25 22:27  clnchanpin  阅读(18)  评论(0)    收藏  举报