1786. 从第一个节点出发到最后一个节点的受限路径数

部署运行你感兴趣的模型镜像

目录

受限路径计数问题详解 —— 基于节点到终点最短距离的动态规划

题目描述

解题分析

题意理解

关键点

解题方法

1. 计算最短距离(Dijkstra算法)

2. 动态规划计数受限路径

3. 计算顺序

代码实现(Python)

复杂度分析

示例说明

步骤分析:

总结


受限路径计数问题详解 —— 基于节点到终点最短距离的动态规划

题目描述

给定一个加权无向连通图,图中有 n 个节点,编号从 1 到 n
图中包含一组边 edges,每条边 edges[i] = [u_i, v_i, w_i] 表示节点 u_iv_i 之间存在一条权重为 w_i 的无向边。

定义:

  • 路径:节点序列 [z_0, z_1, ..., z_k],满足 z_0 = startz_k = end,且对于所有 0 <= i < k(z_i, z_{i+1}) 之间存在边。
  • 路径距离:路径上所有边权重之和。
  • distanceToLastNode(x) 为节点 x 到节点 n 的最短路径距离。

受限路径定义为一条路径 [z_0, z_1, ..., z_k],其中 z_0 = 1z_k = n,且满足对于所有 0 <= i < k,都有:

distanceToLastNode(z_i) > distanceToLastNode(z_{i+1})

即路径上的节点到终点的距离严格递减。

求从节点 1 到节点 n 的所有受限路径的数量,并对结果取模 10^9 + 7


解题分析

题意理解

题目要求计算满足“距离递减条件”的所有路径数量。这个限制使得路径不能随意走,需要路径上的节点按照 distanceToLastNode 递减,从而避免出现环路。

关键点

  • 求最短距离:计算所有节点到终点 n 的最短距离 distanceToLastNode
  • 路径计数:统计从 1 到 n 的所有符合距离递减条件的路径数量。
  • 路径严格递减:节点之间必须满足 distanceToLastNode(当前节点) > distanceToLastNode(下一个节点)

解题方法

1. 计算最短距离(Dijkstra算法)

为了方便判定“距离递减”关系,我们从终点节点 n 反向使用 Dijkstra 算法计算到所有节点的最短距离 distanceToLastNode

  • 为什么从 n 反向?
    这样计算出每个节点到 n 的最短距离,方便我们在后续路径计数中直接判断邻居节点的距离大小关系。

2. 动态规划计数受限路径

定义 dp[x] 表示从节点 x 到节点 n 的受限路径数。

  • 边界条件:dp[n] = 1,因为从终点到终点只有一条路径。
  • 状态转移:
    对于每个节点 x
    dp[x] = Σ dp[y],其中 yx 的邻居且满足 distanceToLastNode[x] > distanceToLastNode[y]

这样,只有满足距离递减条件的路径才被计数。

3. 计算顺序

由于 dp[x] 依赖于邻居节点 ydistanceToLastNode[y] < distanceToLastNode[x],所以节点必须按 distanceToLastNode 从小到大顺序遍历,这样能保证计算某个节点时,其可转移的邻居的 dp 值已计算完成。


代码实现(Python)

import heapq

class Solution:
    def countRestrictedPaths(self, n: int, edges: list[list[int]]) -> int:
        MOD = 10**9 + 7
        graph = [[] for _ in range(n + 1)]
        
        # 构建图的邻接表
        for u, v, w in edges:
            graph[u].append((v, w))
            graph[v].append((u, w))
        
        # 1. Dijkstra算法:从节点 n 反向计算最短距离
        dist = [float('inf')] * (n + 1)
        dist[n] = 0
        heap = [(0, n)]
        while heap:
            cur_dist, u = heapq.heappop(heap)
            if cur_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))
        
        # 2. 按最短距离从小到大排序节点
        nodes = list(range(1, n + 1))
        nodes.sort(key=lambda x: dist[x])
        
        # 3. 动态规划计算受限路径数
        dp = [0] * (n + 1)
        dp[n] = 1  # 终点的受限路径数为1
        
        for u in nodes:
            for v, _ in graph[u]:
                if dist[u] > dist[v]:
                    dp[u] = (dp[u] + dp[v]) % MOD
        
        return dp[1]

复杂度分析

  • 时间复杂度
    • Dijkstra 算法:O(E log V),其中 E 是边数,V 是节点数。
    • 动态规划遍历每个节点和边:O(E)
      总体时间复杂度为 O(E log V)
  • 空间复杂度
    • 存储图邻接表和距离数组:O(V + E)

示例说明

考虑如下示例:

n = 5
edges = [
  [1, 2, 3],
  [1, 3, 3],
  [2, 3, 1],
  [1, 4, 2],
  [5, 2, 2],
  [3, 5, 1],
  [5, 4, 10]
]

步骤分析:

  • 先从节点 5 计算最短距离到各点:
    • distanceToLastNode(5) = 0
    • distanceToLastNode(3) = 1 (5→3)
    • distanceToLastNode(2) = 2 (5→2)
    • distanceToLastNode(1) = 4 (1→3→5)
    • distanceToLastNode(4) = 6 (4→1→3→5)
  • 节点按距离排序:5 (0), 3 (1), 2 (2), 1 (4), 4 (6)
  • 动态规划计算路径数:
    • dp[5] = 1
    • dp[3] = sum of dp[neighbors with smaller distance]: neighbors are 1(4),2(2),5(0)
      • dist[3] = 1, 所以比 1 小的邻居只有 5,dp[3] = dp[5] = 1
    • dp[2] = neighbors 1(4),3(1),5(0)
      • dist[2] = 2,邻居中比 2 小的有 3(1)和5(0)
      • dp[2] = dp[3] + dp[5] = 1 + 1 = 2
    • dp[1] = neighbors 2(2),3(1),4(6)
      • dist[1] = 4,邻居中比 4 小的是 2(2)和3(1)
      • dp[1] = dp[2] + dp[3] = 2 + 1 = 3
    • dp[4] = neighbors 1(4),5(0)
      • dist[4] = 6,邻居中比 6 小的是 1(4)和5(0)
      • dp[4] = dp[1] + dp[5] = 3 + 1 = 4

最终返回 dp[1] = 3,即从节点 1 到节点 5 的受限路径共有 3 条。


总结

这道题利用了Dijkstra算法动态规划的结合:

  • 先通过 Dijkstra 算法计算每个节点到终点的最短距离,为路径的严格递减条件提供依据。
  • 然后基于最短距离的大小关系,设计状态转移动态规划,统计所有符合条件的路径数量。

关键思路是“从终点反向计算最短距离”,以及“按距离排序状态转移”,保证递推无环且正确。

这类题目非常经典,适合锻炼图的最短路算法和基于条件的路径计数技巧。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值