Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 21 – Topological sort and DAGs

Dynamic programming on DAGs

Find the longest path and count the routes in a directed acyclic graph by taking its nodes in topological order, with counts kept modulo 1,000,000,007.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 9 of 35 minutes free
Free: Two problems: the best trail and the number of trails · The brute force: walk every route · What to notice. With a pass: the other 12 sections and 3 interview answers, about 26 minutes.

What you will learn

  • Compute longest paths in a DAG by relaxing edges in topological order
  • Count paths modulo a prime without overflow in any language
  • Rebuild the best route from parent pointers
  • Explain why the same method fails on graphs with cycles, and why it allows negative weights
  • Find the earliest finish time of parallel tasks as a longest path

Before you start

What you’ll learn · Before you start

What you will learn

  • Compute longest paths in a DAG by relaxing edges in topological order
  • Count paths modulo a prime without overflow in any language
  • Rebuild the best route from parent pointers
  • Explain why the same method fails on graphs with cycles, and why it allows negative weights
  • Find the earliest finish time of parallel tasks as a longest path

Before you start

On this page

In a directed acyclic graph, the longest path from a start node can be found in linear time: take the nodes in a topological order, and give each one the best total over the edges that reach it. A topological order puts every edge’s start before its end, so when a node’s turn comes, every route into it has already been scored. The same pass counts the routes, adds up durations into finish times, or finds shortest paths with negative weights. In a graph with cycles none of this works, and finding a longest simple path is NP-hard.

Two problems: the best trail and the number of trails

A hill park has n junctions, numbered 0 to n − 1, and one-way trails (u, v, w): the trail runs downhill from u to v, and its view score is w, from 1 to 10,000. Because every trail goes downhill, no route can return to a junction. Limits: up to 100,000 junctions and 200,000 trails.

Problem A. Return the highest total view score of a route from junction s to junction t, and the route, or −1 if t cannot be reached.

Problem B. Return the number of different routes from s to t, modulo 1,000,000,007.

In the park of the diagram, from the Summit (0) to the Gate (5), the best route scores 16 through the Old Fort and the Waterfall, and 4 routes exist in all. From the Gate to the Summit nothing is reachable: −1, and 0 routes. A junction to itself scores 0, with 1 route.

Six junctions of a hill park joined by eight one-way trails that run downhill, each labelled with its view score.0 Summit1 Pine Ridge2 Old Fort3 Waterfall4 Tea Stall5 Gate42583761

The hill park, with the view score of every trail

Text description of the diagram

The diagram shows six junctions as boxes, from the Summit at the top to the Gate at the bottom, with an arrow for each trail. Every trail runs downhill, so the arrows only point down, and no route can come back to a junction it has passed. The Summit and the Gate have double borders: routes start at the Summit and end at the Gate.

  1. From 0 Summit, a trail of score 4 leads to 1 Pine Ridge and a trail of score 2 to 2 Old Fort.
  2. From Pine Ridge, trails lead to 3 Waterfall (score 5) and 4 Tea Stall (score 3).
  3. From Old Fort, trails lead to Waterfall (score 8) and Tea Stall (score 7).
  4. From Waterfall a trail of score 6 leads to 5 Gate, and from Tea Stall a trail of score 1.

Four routes run from the Summit to the Gate. Their totals are 15 through Pine Ridge and Waterfall, 16 through Old Fort and Waterfall, 8 through Pine Ridge and Tea Stall, and 10 through Old Fort and Tea Stall.

The brute force: walk every route

Walk every route from s with a stack, keep the best total that reaches t, and count the routes:

Every route walked: the park, a greedy walk, and ladders Python · brute/every_route.py
"""The brute force: walk every route from the summit to the gate, keeping the best score and counting the routes."""

JUNCTIONS = ["Summit", "Pine Ridge", "Old Fort", "Waterfall", "Tea Stall", "Gate"]
TRAILS = [(0, 1, 4), (0, 2, 2), (1, 3, 5), (2, 3, 8), (1, 4, 3), (2, 4, 7), (3, 5, 6), (4, 5, 1)]  # (from, to, view)


def every_route(n, trails, s, t):
    """The best total and its route, the number of routes, and the number of trail steps walked."""
    out = [[] for _ in range(n)]
    for u, v, w in trails:
        out[u].append((v, w))
    best, best_route, routes, steps = -1, [], 0, 0
    stack = [(s, 0, [s])]
    while stack:
        u, total, route = stack.pop()
        if u == t:
            routes += 1
            if total > best:
                best, best_route = total, route
            continue
        for v, w in out[u]:
            steps += 1
            stack.append((v, total + w, route + [v]))
    return best, best_route, routes, steps


best, route, routes, steps = every_route(6, TRAILS, 0, 5)
print(f"best view score {best}: {' -> '.join(JUNCTIONS[j] for j in route)}; {routes} routes in all")

out = {u: max((w, v) for a, v, w in TRAILS if a == u) for u in range(5)}  # the most scenic trail out of each junction
u, total, greedy = 0, 0, [0]
while u != 5:
    w, u = out[u]
    total += w
    greedy.append(u)
print(f"always the most scenic next trail: {total}, {' -> '.join(JUNCTIONS[j] for j in greedy)}")

print("\nA ladder of k levels, two junctions each, every junction joined to both of the next level:")
for k in (4, 8, 12, 16):
    trails = [(0, 1, 1), (0, 2, 1)]
    for level in range(k - 1):
        a, b = 1 + 2 * level, 2 + 2 * level
        trails += [(a, a + 2, 1), (a, b + 2, 1), (b, a + 2, 1), (b, b + 2, 1)]
    gate = 2 * k + 1
    trails += [(2 * k - 1, gate, 1), (2 * k, gate, 1)]
    best, route, routes, steps = every_route(gate + 1, trails, 0, gate)
    print(f"  {k:2} levels, {gate + 1:2} junctions: {routes:>6,} routes, {steps:>7,} trail steps walked")

Output

best view score 16: Summit -> Old Fort -> Waterfall -> Gate; 4 routes in all
always the most scenic next trail: 15, Summit -> Pine Ridge -> Waterfall -> Gate

A ladder of k levels, two junctions each, every junction joined to both of the next level:
   4 levels, 10 junctions:     16 routes,      46 trail steps walked
   8 levels, 18 junctions:    256 routes,     766 trail steps walked
  12 levels, 26 junctions:  4,096 routes,  12,286 trail steps walked
  16 levels, 34 junctions: 65,536 routes, 196,606 trail steps walked

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 every_route.py

Walking always along the most scenic next trail gives 15, not 16: a 4-point trail to Pine Ridge looks better than 2 to the Old Fort, which leads on to a trail of 8. On a ladder of junctions, two a level with every junction joined to both of the next level, the routes double each level: 65,536 for 16 levels, and 249,999 for the 100,000 junctions the limits allow.

What to notice

The best total of a route ending at v depends only on the best totals of routes ending at the junctions that have a trail to v. In a graph without cycles, a topological order finishes all of those first. Four techniques follow:

  • Relaxing each trail in topological order gives the longest, or the shortest, path from s.
  • A route count adds up the counts of a junction’s predecessors, modulo 1,000,000,007.
  • Parent pointers record the best predecessor of each junction, so the route can be rebuilt backwards.
  • Memoised recursion computes the same values top-down, working each junction out once.

Longest paths from s in topological order, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why topological order makes each value final: the proof
  • Counting routes, and why the count needs a modulus
  • A dry run on the hill park
  • The code in five languages
  • How much work it does
  • Edge cases and six bugs, found on the smallest inputs
  • Variants: finish times, negative trails, a grid as a hidden DAG, memoised recursion
  • Exercise: count the routes
    • Count the routes through a one-way network
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • A colleague finds the most scenic route through a downhill trail network with Dijkstra's algorithm, taking the largest score first. It passes the examples. Why is it wrong, what do you use instead, and what changes if the network may have cycles?
    • A service returns the number of routes between two stops of a one-way network, modulo 1,000,000,007. Users report wrong counts, but only for long networks and only from the JavaScript client. What is going on, and how do you fix it in every client?
    • A city's elevation grid has 4,000 × 4,000 cells. Cyclists want the longest route that only climbs, moving between neighbouring cells. How do you compute it, and how do you keep memory and stack depth under control?
  • Key takeaways

5 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 22 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.