All Ancestors of a Node in a Directed Acyclic Graph — Medium Problem & Solution
A directed acyclic graph has n nodes numbered 0 to n - 1; each edges[i] = [fromi, toi] is a one-way edge from fromi to toi.
- Difficulty: Medium
- Topics: Breadth-First Search, Graph, Depth-First Search, Topological Sort
- Asked at: Amazon, Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
A directed acyclic graph has n nodes numbered 0 to n - 1; each edges[i] = [fromi, toi] is a one-way edge from fromi to toi.
Node u is an ancestor of node v if v can be reached from u by following one or more edges.
Return a list answer where answer[i] holds every ancestor of node i, sorted in ascending order.
Example 1
Input: n = 5, edges = [[0,2],[1,2],[2,3],[3,4],[1,4]]
Output: [[],[],[0,1],[0,1,2],[0,1,2,3]]
Example 2
Input: n = 4, edges = [[3,0],[2,1]]
Output: [[3],[2],[],[]]
Example 3
Input: n = 1, edges = []
Output: [[]]
Constraints
1 <= n <= 10000 <= edges.length <= min(2000, n * (n - 1) / 2)edges[i].length == 20 <= fromi, toi <= n - 1fromi != toithere are no duplicate edgesthe graph is directed and acyclic
How to solve All Ancestors of a Node in a Directed Acyclic Graph
Instead of searching backwards from each node, search forwards from each potential ancestor: every node a search from s reaches gets s appended to its list.
Approach
- Build forward adjacency lists.
- For
s = 0, 1, …, n − 1: run an iterative DFS fromswith a fresh visited array. - Every newly visited node
v(other thans) getssappended toanswer[v]. - Return
answer.
Why it works
s is appended to answer[v] exactly when v is reachable from s, which is the definition of an ancestor; the visited array ensures it is appended once. Because sources are processed in increasing order, each list receives its entries in increasing order.
Complexity
- Time —
O(n · (n + m)) - Space —
O(n + m) plus the output
Pitfalls
- Reset the visited marks for each new source, or later sources miss nodes an earlier search already saw.
- Nodes with no ancestors still need an (empty) entry in the answer.
- Collecting ancestors by merging parents' sets also works but needs deduplication and sorting.
Reference solution
Python
from typing import List
def getAncestors(n: int, edges: List[List[int]]) -> List[List[int]]:
adj = [[] for _ in range(n)]
for a, b in edges:
adj[a].append(b)
ans = [[] for _ in range(n)]
for s in range(n):
seen = [False] * n
seen[s] = True
stack = [s]
while stack:
u = stack.pop()
for v in adj[u]:
if not seen[v]:
seen[v] = True
ans[v].append(s)
stack.append(v)
return ansJavaScript
var getAncestors = function(n, edges) {
var adj = [], ans = [];
for (var i = 0; i < n; i++) { adj.push([]); ans.push([]); }
for (var e = 0; e < edges.length; e++) adj[edges[e][0]].push(edges[e][1]);
for (var s = 0; s < n; s++) {
var seen = [];
for (var k = 0; k < n; k++) seen.push(false);
seen[s] = true;
var stack = [s];
while (stack.length > 0) {
var u = stack.pop();
for (var j = 0; j < adj[u].length; j++) {
var v = adj[u][j];
if (!seen[v]) { seen[v] = true; ans[v].push(s); stack.push(v); }
}
}
}
return ans;
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 85 breadth-first search problems · the whole catalogue
Learn the technique: Breadth-First Search (BFS) · Graph Data Structure