All Ancestors of a Node in a DAG
Instead of asking about pairs, run one search from each node i. Every node that search reaches has i as an ancestor, so add i to each of their lists. Start the searches in order 0, 1, … , n − 1, so every list fills up in sorted order.
Node i is an ancestor of w exactly when the search from i reaches w. So each list gets one entry for each such i, and no other entries.
Problem
The nodes of a directed acyclic graph are numbered from 0 to n − 1. You are given edges, where edges[i] = [from, to] is an arrow from from to to. The graph has no cycles.
An ancestor of node v is any node u that can reach v by following arrows, through any number of steps. Direct parents count, and so do parents of parents, and so on.
For every node i, return the list of its ancestors, sorted in ascending order.
n, the number of nodes, and edges, a list of arrows [from, to].
answer[i], the sorted list of all ancestors of node i. The list is empty if no node can reach i.
Nodes 0, 1 and 2 have no arrows pointing at them. Node 3 has arrows from 0 and 1. Node 6 is reached from 3 and 4, and node 4 is reached from 0 and 2, so the ancestors of 6 are 0, 1, 2, 3 and 4.
Every smaller node points at every bigger node, so node i has the ancestors 0, 1, … up to i − 1.
- • 1 ≤ n ≤ 1000
- • 0 ≤ edges.length ≤ min(2000, n · (n − 1) / 2)
- • edges[i].length = 2
- • 0 ≤ from, to ≤ n − 1
- • from ≠ to
- • There are no duplicate edges.
- • The graph is directed and acyclic.
First, think yourself
Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.
- 1.
An ancestor is any node that can reach this one through a chain of arrows. Does a direct arrow count, and does a longer chain count?
- 2.
If I already know the ancestors of node 3, what can I say about the nodes that node 3 points to?
- 3.
Is it easier to ask who can reach each node, or what each node can reach? Could one answer give me the other?
- 4.
With n = 1000 there are about half a million pairs. How much work would one search per pair cost?
- 5.
If I process the start nodes 0, 1, 2, … in order, what do I know about each list as I add to it?
Brute force approach
Optimized approach
Why does this algorithm work?
Dry run
All approaches compared
| Approach | Idea | Time | Space |
|---|---|---|---|
| Brute Force | For each pair (i, j), search from i to see whether it reaches j | O(N² · (N + E)) | O(N²) |
| Optimal | From every node i, search forward once and add i to the list of every node it reaches | O(N · (N + E)) | O(N²) |
Why does this belong to this pattern?
- “ancestors”Every node that can reach this node through any number of arrows, not only the direct parents.
- “directed acyclic graph”Arrows have one direction and never form a loop. So 'u reaches v' is a one-way fact, and no node is its own ancestor.
- “sorted in ascending order”Process the start nodes in order, so each list is filled in sorted order and no sort is needed.
This is the first question of the DFS order / DAG DP group. The graph has no loops, so 'i reaches w' is a fixed, one-way fact. The trick is to flip the question: instead of asking who can reach w (backwards, from many places), let each node push itself forward onto everything it reaches. Loud and Rich and Longest Increasing Path go one step further and reuse answers with memoised DFS.
Don't make this mistake
This is the pattern-confusion trap: an ancestor is anyone who can reach the node, not only its direct parents. On the dry-run graph, node 6 has direct parents 3 and 4 only, but its ancestors are 0, 1, 2, 3 and 4. Node 0 reaches 6 through 3 (0 → 3 → 6) and through 4 (0 → 4 → 6). Node 5 gets [3] instead of [0, 1, 3].
Merging parents is a tempting shortcut. On the dry run, node 6 has parents 3 and 4. Merging gives [0, 1, 3] + [0, 2, 4] = [0, 1, 3, 0, 2, 4]. The 0 appears twice, and the list is not sorted. Removing duplicates and sorting would fix it, but the forward search never creates the problem.
The wrong line fills the list of i with the nodes that i reaches, which are its descendants. On the dry run, node 0 would get [3, 4, 5, 6, 7] instead of []. Node 0 has no ancestors, since nothing points at it.
Want to implement this?
Write it yourself on LeetCode, then come back and compare your code with the solutions above.
Open coding workspaceHow this concept connects to System Design & C++ Systems
Top engineering interviews test whether you understand how algorithms like Topological Sort translate into production architectures and low-level memory mechanics.