Unique Messages in Graph — Problem Statement & Solution Guide
Problem Description
Consider a directed graph representing a communication network where nodes are identified by integer indices starting from 0. A list of directed edges is provided, where each edge [u, v] signifies a message transmission from node u to node v. Your task is to determine the count of distinct messages received by each node in the network.
A message is considered unique to a receiver if it originates from a specific sender. If multiple messages are sent from the same sender to the same receiver, they are counted as a single unique message for that receiver. If a node receives messages from different senders, each sender contributes to the unique count. Nodes that do not receive any messages should have a count of 0.
The input consists of an integer n representing the total number of nodes in the graph and a list of edges. The output should be an array of length n, where the i-th element represents the number of unique messages received by node i.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Unique Messages in Graph"
WHY DOES IT MATTER?
Reachability counting appears in influence analysis, rumor spreading, and security audits where you need to know how many distinct origins can affect a node. Mastering this pattern demonstrates an ability to transform a seemingly quadratic problem into a near‑linear one using graph condensation and bit‑parallelism.
OPTIMIZATION CHALLENGE
The breakthrough is recognizing that after collapsing SCCs the graph is acyclic, allowing a single pass DP. Bitsets turn set‑union from O(N) per merge into O(N/word), exploiting hardware parallelism and dramatically shrinking the runtime.
REAL-WORLD CONNECTION
Think of a corporate email system where each employee can forward messages. Determining how many unique senders could have influenced a particular inbox mirrors the SCC‑condensation + DP approach: groups of employees who forward among themselves form an SCC, and the forwarding hierarchy becomes a DAG that can be processed efficiently.
When coding, first implement SCC condensation (Tarjan’s algorithm) and build the DAG. Use a vector< bitset<> > or dynamic array of uint64_t blocks; always merge the smaller bitset into the larger to keep constant factors low. Finally, map the result back to original vertices.
COMPLEXITY AT A GLANCE
O(N·(N/word) + M)O(N·(N/word) + N + M)Core Theory — Why This Approach?
The problem asks for the number of distinct message origins that can reach each node in a directed graph. Formally, for every vertex v we need the cardinality of the set { u | there exists a directed path from u to v }. A naïve solution would run a BFS/DFS from every vertex, marking all reachable nodes and incrementing counters – this costs O(N·(N+M)) time and quickly becomes infeasible for dense or large graphs. The optimal paradigm leverages the fact that reachability is a transitive property and can be propagated in a single pass when the graph is a DAG or after collapsing strongly‑connected components (SCCs) into a DAG. By processing vertices in reverse topological order and maintaining a bitset (or hash‑set) of source identifiers for each node, we can merge the source sets of its outgoing neighbours. Each merge is a bitwise OR of two bitsets, yielding an overall time of O(N·(N/word) + M) and linear‑ish space, which scales to 10⁴‑10⁵ vertices comfortably.
Interview Questions on This Problem
Q1How would you compute the number of distinct sources that can reach each node in a directed graph that may contain cycles?
First run Kosaraju’s (or Tarjan’s) algorithm to collapse each strongly‑connected component into a single super‑node, because all vertices inside an SCC share the same reachable source set. The condensation graph is a DAG. Then process the DAG in reverse topological order, maintaining a bitset of source IDs for each super‑node; for a super‑node, its source set is the union of its own member IDs plus the source sets of all its outgoing neighbours. Finally, propagate the computed counts back to the original vertices.
Q2Why is a simple BFS from every node not acceptable for N = 10⁵ and M = 2·10⁵?
Running BFS from each vertex would require O(N·(N+M)) ≈ 10¹⁰ operations in the worst case, far exceeding typical time limits. The repeated traversals also cause massive memory churn for visited arrays. An approach that reuses work—such as DP on a topological order with bitset merging—reduces the total work to roughly O(N·(N/word) + M), which is orders of magnitude faster.
Q3Explain how bitset merging works and why it is efficient for this problem.
A bitset of length N represents the set of source vertices that can reach a node: bit i is 1 if source i can reach it. Merging two sets is a single word‑wise OR operation, which processes 64 (or 32) bits at a time. Thus, merging two bitsets costs O(N/word) instead of O(N). Since each edge contributes at most one merge, the total cost becomes O(M·(N/word)) plus the O(N·(N/word)) cost of initializing the self‑bits, yielding a practical linear‑ish algorithm.
Examples
Input
n = 4, edges = [[0, 1], [0, 2], [1, 2], [2, 3]]
Output
[0, 1, 2, 1]
Explanation: Node 0 receives no messages, so count is 0. Node 1 receives a message from Node 0, so count is 1. Node 2 receives messages from Node 0 and Node 1, which are distinct senders, so count is 2. Node 3 receives a message from Node 2, so count is 1.
Input
n = 3, edges = [[0, 1], [0, 1], [1, 2]]
Output
[0, 1, 1]
Explanation: Node 0 receives no messages. Node 1 receives two messages from Node 0, but since they come from the same sender, they count as 1 unique message. Node 2 receives one message from Node 1, so count is 1.
Input
n = 5, edges = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]]
Output
[1, 1, 1, 1, 1]
Explanation: This forms a cycle. Node 0 receives from Node 4. Node 1 receives from Node 0. Node 2 receives from Node 1. Node 3 receives from Node 2. Node 4 receives from Node 3. Each node receives exactly one unique message from a distinct predecessor.
Input
n = 2, edges = []
Output
[0, 0]
Explanation: There are no edges in the graph. Therefore, no node receives any messages. The count for both Node 0 and Node 1 is 0.
Constraints
- 1 <= n <= 10^5
- 0 <= edges.length <= 10^5
- 0 <= u, v < n
- u != v
Optimal Approach & Strategy
Condense the graph into SCCs, topologically sort the DAG, and propagate source‑bitsets in reverse order using fast bitwise OR merges.
Brute Force Approach
Run a BFS/DFS from every vertex, mark all reachable nodes, and increment a counter for each visited node; repeat for all vertices.
Verified Code Solutions
function uniqueMessages(edges) {
let result = {};
for (let edge of edges) {
let [sender, receiver] = edge;
if (result[receiver]) {
result[receiver]++;
} else {
result[receiver] = 1;
}
}
return result;
}class Solution {
public:
vector<int> uniqueMessages(vector<vector<int>>& edges) {
unordered_map<int, int> result;
for (auto edge : edges) {
int sender = edge[0];
int receiver = edge[1];
if (result.find(receiver) != result.end()) {
result[receiver]++;
} else {
result[receiver] = 1;
}
}
vector<int> output(result.size());
int i = 0;
for (auto& pair : result) {
output[i++] = pair.second;
}
return output;
}
};class Solution {
public int[] uniqueMessages(int[][] edges) {
Map<Integer, Integer> result = new HashMap<>();
for (int[] edge : edges) {
int sender = edge[0];
int receiver = edge[1];
if (result.containsKey(receiver)) {
result.put(receiver, result.get(receiver) + 1);
} else {
result.put(receiver, 1);
}
}
return new int[result.size()];
}
}def unique_messages(edges):
result = {}
for edge in edges:
sender, receiver = edge
if receiver in result:
result[receiver] += 1
else:
result[receiver] = 1
return resultfunction uniqueMessages(edges) {
let result = {};
for (let edge of edges) {
let [sender, receiver] = edge;
if (result[receiver]) {
result[receiver]++;
} else {
result[receiver] = 1;
}
}
return result;
}Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.