Talentd
FleetCode
Make and follow sheets.Sign in

Number of Restricted Paths From First to Last Node solution and explanation

MediumDynamic ProgrammingGraphHeap (Priority Queue)Shortest Path7 min read41.6% acceptanceUpdated 9 Apr 2026
5699. Number of Restricted Paths From First to Last NodeFraz · 9:26 · 4K viewsWatch on YouTube
9 more videos on this problem
  1. 5699. Number of Restricted Paths From First to Last NodeFraz · 9:26 · 4K views
  2. 花花酱 LeetCode 1786. Number of Restricted Paths From First to Last Node - 刷题找工作 EP388Hua Hua · 23:02 · 3K views
  3. LeetCode 1786. Number of Restricted Paths From First to Last NodeHappy Coding · 14:41 · 1K views
  4. LeetCode 1786. Number of Restricted Paths From First to Last Node | Visualization | PythonAH Tech · 9:09 · 1K views
  5. 1786. Number of Restricted Paths From First to Last NodeGaurav Codes · 19:42 · 619 views
  6. 1786. Number of Restricted Paths From First to Last Node (Leetcode Medium)Programming Live with Larry · 17:08 · 603 views
  7. 【每日一题】1786. Number of Restricted Paths From First to Last Node, 3/10/2021Huifeng Guan · 23:36 · 591 views
  8. Leetcode 1786. Number of Restricted Paths From First to Last Node(Dijkstra + dp)LetsCode · 13:45 · 230 views
  9. LeetCode 1786. Number of Restricted Paths From First to Last Node 中文宰相小甘罗 · 5:58 · 143 views
  10. LeetCode 1786. Number of Restricted Paths From First to Last Node宰相小甘罗 · 6:02 · 129 views

The problem

There is an undirected weighted connected graph. You are given a positive integer n which denotes that the graph has n nodes labeled from 1 to n, and an array edges where each edges[i] = [ui, vi, weighti] denotes that there is an edge between nodes ui and vi with weight equal to weighti.

A path from node start to node end is a sequence of nodes [z0, z1, z2, ..., zk] such that z0 = start and zk = end and there is an edge between zi and zi+1 where 0 <= i <= k-1.

The distance of a path is the sum of the weights on the edges of the path. Let distanceToLastNode(x) denote the shortest distance of a path between node n and node x. A restricted path is a path that also satisfies that distanceToLastNode(zi) > distanceToLastNode(zi+1) where 0 <= i <= k-1.

Return the number of restricted paths from node 1 to node n. Since that number may be too large, return it modulo 109 + 7.

Example 1:

Input: n = 5, edges = [[1,2,3],[1,3,3],[2,3,1],[1,4,2],[5,2,2],[3,5,1],[5,4,10]]
Output: 3
Explanation: Each circle contains the node number in black and its distanceToLastNode value in blue. The three restricted paths are:
1) 1 --> 2 --> 5
2) 1 --> 2 --> 3 --> 5
3) 1 --> 3 --> 5

Example 2:

Input: n = 7, edges = [[1,3,1],[4,1,2],[7,3,4],[2,5,3],[5,6,1],[6,7,2],[7,5,3],[2,6,4]]
Output: 1
Explanation: Each circle contains the node number in black and its distanceToLastNode value in blue. The only restricted path is 1 --> 3 --> 7.

Constraints:

  • 1 <= n <= 2 * 104
  • n - 1 <= edges.length <= 4 * 104
  • edges[i].length == 3
  • 1 <= ui, vi <= n
  • ui != vi
  • 1 <= weighti <= 105
  • There is at most one edge between any two nodes.
  • There is at least one path between any two nodes.

Approach overview

Problem Overview: You are given an undirected weighted graph with n nodes. A path from node 1 to node n is restricted if the shortest distance to node n strictly decreases at every step along the path. The task is to count how many such restricted paths exist, modulo 1e9 + 7.

Approach 1: Modified Dijkstra's Algorithm with Memoization (O(E log V) time, O(V + E) space)

The key observation: whether a step is valid depends on the shortest distance from each node to node n. Compute these distances first using shortest path logic. Run Dijkstra's algorithm starting from node n so you get dist[x] = shortest distance from x to n. Once distances are known, a restricted path simply means moving from a node to a neighbor with a strictly smaller distance value.

After this preprocessing, the graph effectively becomes a directed acyclic structure if you only keep edges that go from larger dist to smaller dist. Use DFS from node 1 and count valid paths recursively. Memoization stores the number of ways to reach n from each node so each state is computed once. Every DFS step iterates through neighbors and only continues if dist[next] < dist[current]. This combines dynamic programming with graph traversal to avoid recomputation.

Approach 2: DAG Shortest Path with Topological Sorting (O(E log V + V log V) time, O(V + E) space)

After computing shortest distances to node n using Dijkstra, orient every edge from the node with larger distance to the node with smaller distance. Because distances strictly decrease, the resulting graph forms a DAG. Counting restricted paths becomes a classic DP-on-DAG problem.

Sort nodes by increasing dist value (which acts as a topological order). Initialize dp[n] = 1 because there is exactly one way to stay at node n. Traverse nodes in sorted order and propagate counts to neighbors with larger distance values in reverse direction. Each transition adds dp[next] into the current node’s count while applying modulo arithmetic. This approach removes recursion and replaces it with iterative dynamic programming over the DAG structure.

Recommended for interviews: The Dijkstra + memoized DFS approach is the one most candidates implement. It shows understanding of graph traversal, shortest paths, and DP with caching. The DAG + topological DP approach demonstrates deeper insight into how the distance constraint implicitly creates a DAG and is often discussed as an optimization or alternative explanation.

Approach 1: Approach 1: Modified Dijkstra's Algorithm with Memoization

To solve this problem, we first use Dijkstra's Algorithm to find the shortest path from the last node (node n) to every other node in the graph. This allows us to compute distanceToLastNode for each node. Then, using dynamic programming, we count the number of restricted paths from node 1 to node n. A restricted path is one where for each consecutive pair of nodes [u, v] in the path, distanceToLastNode(u) > distanceToLastNode(v).

In the dynamic programming function, we'll use memoization to store the number of restricted paths from a given node to the last node, which prevents redundant calculations and optimizes the solution.

import heapq

MOD = 10**9 + 7

def countRestrictedPaths(n, edges):
    graph = [[] for _ in range(n + 1)]
    for u, v, w in edges:
        graph[u].append((v, w))
        graph[v].append((u, w))
    
    # Step 1: Find shortest paths from node n to every other node
    dist = [float('inf')] * (n + 1)
    dist[n] = 0
    min_heap = [(0, n)]
    
    while min_heap:
        d, current = heapq.heappop(min_heap)
        if d > dist[current]:
            continue
        for neighbor, weight in graph[current]:
            if dist[current] + weight < dist[neighbor]:
                dist[neighbor] = dist[current] + weight
                heapq.heappush(min_heap, (dist[neighbor], neighbor))
    
    # Step 2: Count restricted paths using DFS + Memoization
    memo = {}
    def dfs(node):
        if node == n:
            return 1
        if node in memo:
            return memo[node]
        
        total_paths = 0
        for neighbor, _ in graph[node]:
            if dist[neighbor] < dist[node]:
                total_paths += dfs(neighbor)
                total_paths %= MOD
        
        memo[node] = total_paths
        return total_paths
    
    return dfs(1)
const MOD = 10**9 + 7;

function countRestrictedPaths(n, edges) {
    const graph = Array.from({ length: n + 1 }, () => []);
    for (const [u, v, w] of edges) {
        graph[u].push([v, w]);
        graph[v].push([u, w]);
    }
    
    const dist = Array(n + 1).fill(Infinity);
    dist[n] = 0;
    const minHeap = [[0, n]];
    
    while (minHeap.length) {
        const [d, node] = minHeap.pop();
        if (d > dist[node]) continue;
        
        for (const [neighbor, weight] of graph[node]) {
            if (dist[node] + weight < dist[neighbor]) {
                dist[neighbor] = dist[node] + weight;
                minHeap.push([dist[neighbor], neighbor]);
                minHeap.sort(([a], [b]) => b - a); // simulate priority queue
            }
        }
    }

    const memo = {};

    function dfs(node) {
        if (node === n) return 1;
        if (memo[node] != null) return memo[node];

        let totalPaths = 0;
        for (const [neighbor] of graph[node]) {
            if (dist[neighbor] < dist[node]) {
                totalPaths = (totalPaths + dfs(neighbor)) % MOD;
            }
        }

        memo[node] = totalPaths;
        return totalPaths;
    }

    return dfs(1);
}

This solution starts by constructing the graph and then utilizes Dijkstra's algorithm to find the shortest path from the last node to all others, recorded in the dist array. In the second part, it uses a DFS approach to count paths while checking the restricted path condition, storing results in memo to avoid repeating work.

Complexity

Time Complexity: O((n + m) log n), where n is the number of nodes and m is the number of edges, due to Dijkstra's algorithm and DFS with memoization.
Space Complexity: O(n + m) for the graph representation and distance/memoization arrays.

Try this approach in the editor

Approach 2: Approach 2: DAG Shortest Path with Topological Sorting

Another approach involves using the graph's nature and transforming it into a Directed Acyclic Graph (DAG) by considering only edges where the distanceToLastNode property holds. With a DAG, we can perform a topological sort to determine a valid order of nodes, then use dynamic programming to calculate restricted paths from node 1 to node n.

import java.util.*;

public class Solution {
    private static final int MOD = 1_000_000_007;
    
    public int countRestrictedPaths(int n, int[][] edges) {
        List<int[]>[] graph = new ArrayList[n + 1];
        for (int i = 1; i <= n; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int[] edge : edges) {
            graph[edge[0]].add(new int[]{edge[1], edge[2]});
            graph[edge[1]].add(new int[]{edge[0], edge[2]});
        }

        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[n] = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        pq.offer(new int[]{0, n});

        while (!pq.isEmpty()) {
            int[] node = pq.poll();
            int d = node[0], u = node[1];
            if (d > dist[u]) continue;

            for (int[] neighbor : graph[u]) {
                int v = neighbor[0], w = neighbor[1];
                if (dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    pq.offer(new int[]{dist[v], v});
                }
            }
        }

        Integer[] order = new Integer[n];
        for (int i = 0; i < n; i++) {
            order[i] = i + 1;
        }
        Arrays.sort(order, Comparator.comparingInt(a -> dist[a]));

        int[] dp = new int[n + 1];
        dp[n] = 1;

        for (int u : order) {
            for (int[] neighbor : graph[u]) {
                int v = neighbor[0];
                if (dist[v] < dist[u]) {
                    dp[u] = (dp[u] + dp[v]) % MOD;
                }
            }
        }

        return dp[1];
    }
}
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

const int MOD = 1e9 + 7;

int countRestrictedPaths(int n, vector<vector<int>>& edges) {
    vector<vector<pair<int, int>>> graph(n + 1);
    for (auto& edge : edges) {
        graph[edge[0]].emplace_back(edge[1], edge[2]);
        graph[edge[1]].emplace_back(edge[0], edge[2]);
    }
    
    vector<long long> dist(n + 1, LLONG_MAX);
    dist[n] = 0;
    priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<>> pq;
    pq.emplace(0, n);
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;

        for (auto& [v, w] : graph[u]) {
            if (d + w < dist[v]) {
                dist[v] = d + w;
                pq.emplace(dist[v], v);
            }
        }
    }

    vector<int> dp(n + 1, -1);
    vector<int> sortedNodes;
    for (int i = 1; i <= n; ++i) {
        sortedNodes.push_back(i);
    }
    sort(sortedNodes.begin(), sortedNodes.end(), [&](int a, int b) {
        return dist[a] < dist[b];
    });

    function<int(int)> dfs = [&](int node) {
        if (node == n) return 1;
        if (dp[node] != -1) return dp[node];

        int totalPaths = 0;
        for (auto& [v, w] : graph[node]) {
            if (dist[v] < dist[node]) {
                totalPaths = (totalPaths + dfs(v)) % MOD;
            }
        }
        return dp[node] = totalPaths;
    };

    return dfs(1);
}

This Java solution follows a topological sorting approach similar to C++, after calculating shortest distances via Dijkstra. Nodes are processed based on increasing distances which allows the dynamic programming step to compute restricted paths counts correctly and efficiently.

Complexity

Time Complexity: O((n + m) log n), comprehensive of sorting operations and graph traversals.
Space Complexity: O(n + m), accommodating graph and dynamic programming arrays.

Try this approach in the editor

Approach 3: Default Approach

const inf = math.MaxInt32
const mod = 1e9 + 7

type pair struct {
	first  int
	second int
}

var _ heap.Interface = (*pairs)(nil)

type pairs []pair

func (a pairs) Len() int { return len(a) }
func (a pairs) Less(i int, j int) bool {
	return a[i].first < a[j].first || a[i].first == a[j].first && a[i].second < a[j].second
}
func (a pairs) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a *pairs) Push(x any)       { *a = append(*a, x.(pair)) }
func (a *pairs) Pop() any         { l := len(*a); t := (*a)[l-1]; *a = (*a)[:l-1]; return t }

func countRestrictedPaths(n int, edges [][]int) int {
	g := make([]pairs, n+1)
	for _, e := range edges {
		u, v, w := e[0], e[1], e[2]
		g[u] = append(g[u], pair{v, w})
		g[v] = append(g[v], pair{u, w})
	}
	dist := make([]int, n+1)
	f := make([]int, n+1)
	for i := range dist {
		dist[i] = inf
		f[i] = -1
	}
	dist[n] = 0
	h := make(pairs, 0)
	heap.Push(&h, pair{0, n})
	for len(h) > 0 {
		u := heap.Pop(&h).(pair).second
		for _, ne := range g[u] {
			v, w := ne.first, ne.second
			if dist[v] > dist[u]+w {
				dist[v] = dist[u] + w
				heap.Push(&h, pair{dist[v], v})
			}
		}
	}
	var dfs func(int) int
	dfs = func(i int) int {
		if f[i] != -1 {
			return f[i]
		}
		if i == n {
			return 1
		}
		ans := 0
		for _, ne := range g[i] {
			j := ne.first
			if dist[i] > dist[j] {
				ans = (ans + dfs(j)) % mod
			}
		}
		f[i] = ans
		return ans
	}
	return dfs(1)
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach 1: Modified Dijkstra's Algorithm with Memoization

Time Complexity: O((n + m) log n), where n is the number of nodes and m is the number of edges, due to Dijkstra's algorithm and DFS with memoization.
Space Complexity: O(n + m) for the graph representation and distance/memoization arrays.

Approach 2: DAG Shortest Path with Topological Sorting

Time Complexity: O((n + m) log n), comprehensive of sorting operations and graph traversals.
Space Complexity: O(n + m), accommodating graph and dynamic programming arrays.

Default Approach–

Complexity analysis

ApproachTimeSpaceWhen to Use
Modified Dijkstra + Memoized DFSO(E log V)O(V + E)General case; easiest to implement in interviews
DAG DP with Topological OrderingO(E log V + V log V)O(V + E)When you want an iterative DP solution without recursion

Frequently asked questions

Is Number of Restricted Paths From First to Last Node easy or hard?

The problem is rated Medium because it combines multiple concepts: shortest path computation, graph traversal, and dynamic programming. The tricky part is realizing that restricted paths depend on distances to node n, which must be computed first. Once that insight is clear, the remaining solution is a straightforward DP on decreasing distance edges.

Number of Restricted Paths From First to Last Node Python/Java solution

Python solutions typically combine heapq-based Dijkstra with a recursive DFS using functools.lru_cache for memoization. Java implementations use PriorityQueue for Dijkstra and either recursion with memo arrays or iterative DP on a DAG. Both approaches maintain O(E log V) time complexity.

How to solve Number of Restricted Paths From First to Last Node in O(E log V)?

First run Dijkstra starting from node n to compute the shortest distance from every node to n. Then perform DFS from node 1 and only move to neighbors whose distance value is smaller than the current node’s distance. Memoize the number of ways to reach n from each node to avoid recomputation. Combine counts modulo 1e9+7.

What is the best approach for Number of Restricted Paths From First to Last Node?

The standard solution computes shortest distances from every node to node n using Dijkstra’s algorithm, then counts valid paths using DFS with memoization. A step is allowed only if the next node has a strictly smaller distance to n. This converts the problem into dynamic programming on a directed acyclic structure. The overall complexity is O(E log V).

Is Number of Restricted Paths From First to Last Node asked at Google/Amazon/Meta?

Graph problems involving shortest paths and dynamic programming are common at companies like Google, Amazon, and Meta. This problem tests multiple concepts together: Dijkstra’s algorithm, DAG reasoning, and memoized DFS. Variants of this pattern appear frequently in senior-level interview rounds.

What data structure is used in Number of Restricted Paths From First to Last Node?

The solution uses an adjacency list to represent the graph and a min-heap priority queue for Dijkstra’s algorithm. Additional structures include a distance array storing shortest paths to node n and a memoization array for dynamic programming during DFS. These enable efficient traversal and caching of subproblems.

What is the time complexity of Number of Restricted Paths From First to Last Node?

The optimal solution runs in O(E log V) time due to the Dijkstra shortest path computation with a priority queue. The subsequent DFS with memoization visits each node and edge at most once, which is O(V + E). Space complexity is O(V + E) for the adjacency list, distance array, and memo table.

Comments

Now write it yourselfClose this page and solve Number of Restricted Paths From First to Last Node in the editor, without looking.
Practise Number of Restricted Paths From First to Last Node