Compute trigger counts in a DAG
Company: Robinhood
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of directed acyclic graphs, graph traversal and accumulation of values across dependency edges, as well as algorithmic efficiency for handling large sparse graphs.
Constraints
- 0 <= len(edges) <= 10^5
- The graph is acyclic
- Node IDs are strings or integers
- Only nodes reachable from entry should be included in the result
- The solution should run in O(V + E) time, where V is the number of nodes and E is the number of edges
Examples
Input: ([('A','B'),('B','C'),('B','D'),('C','D'),('D','E'),('D','F'),('E','F')], 'A')
Expected Output: {'A': 1, 'B': 1, 'C': 1, 'D': 2, 'E': 2, 'F': 4}
Explanation: A starts with 1. B gets 1 from A. C gets 1 from B. D gets 1 from B and 1 from C, so 2. E gets 2 from D. F gets 2 from D and 2 from E, so 4.
Input: ([], 'X')
Expected Output: {'X': 1}
Explanation: With no edges, only the entry node is triggered once.
Input: ([(1,2),(1,3),(2,4),(3,4)], 1)
Expected Output: {1: 1, 2: 1, 3: 1, 4: 2}
Explanation: Node 4 is reached through two distinct parent triggers: one from 2 and one from 3.
Input: ([('A','B'),('X','B'),('B','C')], 'A')
Expected Output: {'A': 1, 'B': 1, 'C': 1}
Explanation: X is unreachable from A, so it never triggers B. Only the reachable path A -> B -> C contributes.
Input: ([('s','a'),('a','b'),('x','y')], 's')
Expected Output: {'s': 1, 'a': 1, 'b': 1}
Explanation: The component x -> y is unreachable from s, so it is ignored.
Input: ([(0,1),(0,2),(1,3),(2,3),(1,4),(3,4),(4,5)], 0)
Expected Output: {0: 1, 1: 1, 2: 1, 3: 2, 4: 3, 5: 3}
Explanation: Node 3 gets one trigger from 1 and one from 2. Node 4 gets one directly from 1 plus two from 3, for a total of 3. Node 5 then gets 3.
Solution
from collections import defaultdict, deque
def solution(edges, entry):
adj = defaultdict(list)
for u, v in edges:
adj[u].append(v)
reachable = {entry}
stack = [entry]
while stack:
u = stack.pop()
for v in adj.get(u, []):
if v not in reachable:
reachable.add(v)
stack.append(v)
indegree = {node: 0 for node in reachable}
for u, v in edges:
if u in reachable and v in reachable:
indegree[v] += 1
counts = defaultdict(int)
counts[entry] = 1
queue = deque(node for node in reachable if indegree[node] == 0)
topo_order = []
while queue:
u = queue.popleft()
topo_order.append(u)
for v in adj.get(u, []):
if v not in reachable:
continue
counts[v] += counts[u]
indegree[v] -= 1
if indegree[v] == 0:
queue.append(v)
if len(topo_order) != len(reachable):
raise ValueError('Input contains a cycle in the reachable subgraph.')
return {node: counts[node] for node in topo_order}Time complexity: O(V + E). Space complexity: O(V + E).
Hints
- The trigger count of a node is the sum of the trigger counts of its parents. In a DAG, this is equivalent to counting paths from the entry node.
- Process only the reachable part of the graph in topological order so every parent contributes before its child is finalized.