1. 题目背景与核心挑战LeetCode 2977题是一道结合了Dijkstra算法和动态规划(DP)的图论问题这类题型在周赛和面试中频繁出现。题目通常会给出一张带权有向图要求计算从起点到终点的某种最优路径而约束条件往往需要通过DP状态设计来解决。这道题的难点在于识别出何时该用Dijkstra处理路径权重何时该用DP处理状态转移。许多参赛者在初次遇到时会陷入两个误区要么试图纯用Dijkstra暴力求解导致超时要么DP状态设计不当遗漏关键约束。我在第一次尝试时也花了近40分钟才理清思路后来通过系统总结发现了这类问题的通用解法模式。2. Dijkstra算法的适用场景与优化2.1 为什么选择Dijkstra而非其他最短路径算法当题目中出现最短路径、最小代价等关键词且边权为非负数时Dijkstra就是首选方案。相比Floyd-Warshall的O(n³)复杂度Dijkstra的O(E VlogV)复杂度更适合处理稀疏图。以LeetCode 743题为例def networkDelayTime(times, n, k): graph defaultdict(list) for u, v, w in times: graph[u].append((v, w)) heap [(0, k)] dist {node: float(inf) for node in range(1, n1)} dist[k] 0 while heap: current_dist, u heapq.heappop(heap) if current_dist dist[u]: continue for v, w in graph[u]: if dist[v] dist[u] w: dist[v] dist[u] w heapq.heappush(heap, (dist[v], v)) max_dist max(dist.values()) return max_dist if max_dist float(inf) else -12.2 堆优化实现的关键细节使用最小堆优化时要注意三个易错点每次从堆中取出的距离可能已不是最新值需要通过if current_dist dist[u]: continue过滤过期数据邻接表的存储方式比邻接矩阵更节省空间适合处理稀疏图Python的heapq模块默认是最小堆若要最大堆需要存储负值提示在竞赛环境中建议预先生成完整的邻接表结构避免在松弛操作中频繁进行哈希查找影响性能。3. 动态规划的状态设计与转移3.1 识别DP适用条件当问题具有以下特征时需要考虑DP求最优解最大/最小值存在重叠子问题具有最优子结构性质在2977题中除了基本的最短路径要求外通常还会有额外约束如必须经过特定节点路径长度限制多维度代价计算3.2 经典状态设计模式对于图上的DP常用状态定义为dp[u][state]表示到达节点u且处于state状态时的最优解。例如# 伪代码示例 dp [[float(inf)] * (1 k) for _ in range(n)] dp[start][0] 0 for state in range(1 k): for u in nodes: if dp[u][state] float(inf): continue for v, weight in graph[u]: new_state state | (1 must_pass[v]) if dp[v][new_state] dp[u][state] weight: dp[v][new_state] dp[u][state] weight4. Dijkstra与DP的协同工作模式4.1 分层图思想将DP状态融入图的节点中构建分层图每个原始节点拆分为多个状态节点边转移同时考虑路径权重和状态变化使用Dijkstra在分层图上求最短路4.2 实现模板def solve(): # 初始化 heap [(0, start_state)] dist defaultdict(lambda: float(inf)) dist[start_state] 0 while heap: current_dist, state heapq.heappop(heap) if current_dist dist[state]: continue # 状态转移 for next_state, cost in get_neighbors(state): if dist[next_state] dist[state] cost: dist[next_state] dist[state] cost heapq.heappush(heap, (dist[next_state], next_state)) return dist[target_state]5. 实战案例分析5.1 LeetCode 1976. 到达目的地的方案数这道题完美展示了DijkstraDP的组合应用先用Dijkstra求出所有节点到终点的最短距离定义dp[u]为从u到终点的最短路径数目按照到终点的距离升序处理节点保证DP的无后效性def countPaths(n, roads): graph defaultdict(list) for u, v, time in roads: graph[u].append((v, time)) graph[v].append((u, time)) # Dijkstra求最短路 dist [float(inf)] * n dist[0] 0 heap [(0, 0)] while heap: d, u heapq.heappop(heap) if d dist[u]: continue for v, time in graph[u]: if dist[v] dist[u] time: dist[v] dist[u] time heapq.heappush(heap, (dist[v], v)) # DP过程 nodes sorted(range(n), keylambda x: dist[x]) dp [0] * n dp[0] 1 for u in nodes: for v, time in graph[u]: if dist[v] dist[u] time: dp[v] (dp[v] dp[u]) % (10**9 7) return dp[-1]5.2 调试技巧与边界处理在实现这类算法时我总结了几条实用技巧打印中间状态在Dijkstra和DP的关键步骤后输出dist和dp数组小规模测试先用手算能验证的小图测试特殊用例单节点图不连通图存在重边的图最大规模极限测试6. 性能优化与进阶技巧6.1 双向Dijkstra的应用当图规模较大时可以考虑从起点和终点同时进行Dijkstra搜索def bidirectional_dijkstra(graph, start, end): # 前向搜索 dist_start {start: 0} heap_start [(0, start)] # 后向搜索 dist_end {end: 0} heap_end [(0, end)] visited_start set() visited_end set() while heap_start and heap_end: # 前向步 d1, u heapq.heappop(heap_start) if u in visited_start: continue visited_start.add(u) # 相遇检查 if u in visited_end: return dist_start[u] dist_end[u] # 后向步 d2, v heapq.heappop(heap_end) if v in visited_end: continue visited_end.add(v) # 相遇检查 if v in visited_start: return dist_start[v] dist_end[v] # 松弛操作略6.2 A*搜索的启发式函数当图具有空间特性时A*算法可能更高效def heuristic(node, target): # 实现合适的启发式函数如曼哈顿距离 return abs(node.x - target.x) abs(node.y - target.y) def a_star(graph, start, end): heap [(0 heuristic(start, end), 0, start)] dist {start: 0} while heap: _, current_dist, u heapq.heappop(heap) if u end: return current_dist for v, weight in graph[u]: if v not in dist or current_dist weight dist[v]: dist[v] current_dist weight heapq.heappush(heap, (dist[v] heuristic(v, end), dist[v], v)) return -17. 常见错误与验证方法7.1 Dijkstra实现中的陷阱未处理重复节点同一个节点可能被多次加入堆中错误终止条件应在找到目标节点时立即返回但有时需要处理所有节点负权边处理Dijkstra不能处理含负权边的图验证方法手动计算小图的最短路径检查是否满足三角不等式dist[v] ≤ dist[u] weight(u,v)7.2 DP状态转移错误典型错误包括状态转移顺序不当未正确处理边界条件状态压缩时位运算错误调试建议打印每个状态的转移过程使用assert检查不变式对比暴力解法的结果8. 扩展应用与变种问题8.1 多目标优化问题当需要同时优化多个指标时如时间和费用可以将状态扩展为多维dp [[float(inf)] * (max_cost 1) for _ in range(n)] dp[start][0] 0 for cost in range(max_cost 1): for u in range(n): if dp[u][cost] float(inf): continue for v, (time, edge_cost) in graph[u]: new_cost cost edge_cost if new_cost max_cost: if dp[v][new_cost] dp[u][cost] time: dp[v][new_cost] dp[u][cost] time8.2 随机图上的期望最短路径当边权具有概率特性时可以结合期望计算# E[u] Σ (p_uv * (w_uv E[v])) def expected_shortest_path(graph, start, end): E {node: 0 if node end else float(inf) for node in graph} for _ in range(len(graph) - 1): for u in graph: if u end: continue total 0 for (v, w, p) in graph[u]: total p * (w E[v]) E[u] total return E[start]在实际刷题过程中我发现这类问题的解题能力会随着刻意练习快速提升。建议从LeetCode 743、787、1976等题开始逐步过渡到周赛的hard题目。每次AC后记录下自己的解题思路和优化点形成个人解题模板库。
阅读完成 · 觉得有帮助?