2092. Find All People With Secret
Problem Description
You have n
people numbered from 0
to n - 1
. Person 0
initially has a secret that they share with person firstPerson
at time 0
.
You're given a list of meetings where each meeting is represented as [xi, yi, timei]
, meaning person xi
and person yi
meet at time timei
.
The key rules for how the secret spreads are:
- During a meeting: If one person knows the secret when they meet, they immediately share it with the other person
- Simultaneous sharing: A person can attend multiple meetings at the same time
- Instant propagation: If someone learns the secret at a particular time, they can share it with others in their meetings at that same time
For example, if persons A, B, and C all have meetings at time 5:
- If A knows the secret and meets B at time 5, B learns it
- If B also meets C at time 5, B can immediately share it with C
- This all happens instantaneously at time 5
The goal is to determine which people know the secret after all meetings have concluded. Return a list of all person IDs who know the secret, in any order.
The solution approach groups meetings by time and uses BFS to propagate the secret among all people meeting at each time point, tracking who knows the secret through a visited array.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves people (nodes) and meetings between them (edges). Each meeting creates a connection between two people, and the secret spreads through these connections. We need to model this as a graph where people are vertices and meetings are edges.
Is it a tree?
- No: The graph is not a tree because:
- There can be cycles (person A can meet B, B can meet C, and C can meet A)
- Multiple disconnected components can exist (groups of people meeting separately)
- There's no hierarchical parent-child relationship
Is the problem related to directed acyclic graphs?
- No: While there's a time component, the graph itself isn't directed. When two people meet, the secret can flow bidirectionally between them if one knows it.
Is the problem related to shortest paths?
- No: We're not looking for the shortest path between nodes. We need to find all nodes (people) that can be reached from the initial secret holders through the meeting connections.
Does the problem involve connectivity?
- Yes: The core problem is about connectivity - determining which people are connected to those who know the secret through chains of meetings at specific times. We need to find all people who become "connected" to secret holders through meetings.
Does the problem have small constraints?
- Yes: While not explicitly small, the time-grouped approach makes each subproblem manageable. At each timestamp, we only process meetings occurring at that time, creating smaller connected components to explore.
DFS/backtracking?
- Yes: DFS is perfect for exploring all connected people from those who know the secret. Starting from people with the secret, we traverse through all their connections (meetings) to spread the secret to everyone reachable.
Conclusion: The flowchart suggests using DFS to solve this connectivity problem. The solution uses DFS (implemented via BFS with a queue in the given code) to explore connected components at each timestamp, spreading the secret from known holders to all reachable people through meetings.
Intuition
The key insight is that the secret spreads like a contagion through meetings, but with a crucial twist: it can only spread among people who meet at the same time. This time constraint is what shapes our approach.
Initially, we might think to process meetings one by one, but this would miss an important aspect: if persons A, B, and C all meet at time 5 (A meets B, B meets C), and A knows the secret, then B learns it from A and can immediately share it with C - all at time 5. This "instant propagation" means we need to consider all meetings at the same timestamp together.
Think of each timestamp as creating a temporary network. At time 5, all people meeting at that time form a graph where edges represent meetings. The secret flows through this entire connected component instantly. If anyone in this component knows the secret at the start of time 5, everyone connected to them will know it by the end of time 5.
This leads us to a natural approach:
- Group all meetings by their timestamp
- For each timestamp, build a graph of who meets whom
- Find everyone who already knows the secret in this group
- Use graph traversal (DFS/BFS) to spread the secret to all people connected to secret holders
Why DFS/BFS? Because we need to explore all paths from secret holders to mark everyone reachable. It's essentially a reachability problem within each time-based subgraph.
The temporal ordering is critical - we must process earlier times first because someone who learns the secret at time 3 can share it in their meetings at time 5, but not vice versa. This is why we sort meetings by time before processing.
By breaking the problem into time-sliced subproblems and using graph traversal within each slice, we efficiently track how the secret propagates through the entire network of meetings.
Learn more about Depth-First Search, Breadth-First Search, Union Find, Graph and Sorting patterns.
Solution Approach
The implementation follows the intuition of processing meetings in temporal groups and using BFS to propagate the secret within each time slice.
Initial Setup:
- Create a boolean array
vis
of sizen
to track who knows the secret - Mark
vis[0] = True
(person 0 has the secret initially) andvis[firstPerson] = True
(they learn it at time 0) - Sort all meetings by timestamp to process them chronologically
Time-Grouped Processing: The core algorithm uses a two-pointer technique to group meetings by time:
i = 0 while i < m: j = i # Find all meetings at the same timestamp while j + 1 < m and meetings[j + 1][2] == meetings[i][2]: j += 1
This groups meetings [i, j]
that occur at the same time.
Building the Graph for Each Timestamp: For each time group, we:
- Create an adjacency list
g
using a defaultdict - Track all people involved in meetings at this time in set
s
- Build bidirectional edges for each meeting:
for x, y, _ in meetings[i : j + 1]: g[x].append(y) g[y].append(x) s.update([x, y])
BFS Propagation: Starting from people who already know the secret, we spread it through the connected component:
q = deque([u for u in s if vis[u]]) # Start with secret holders while q: u = q.popleft() for v in g[u]: # Check all neighbors if not vis[v]: vis[v] = True # Mark as knowing the secret q.append(v) # Add to queue to spread further
The BFS ensures that if anyone in a connected component knows the secret, everyone in that component will learn it.
Why This Works:
- By processing times in order, we ensure people who learn the secret earlier can share it in later meetings
- The graph representation at each timestamp captures all possible paths for secret propagation
- BFS guarantees we find all reachable people from secret holders
- The
vis
array persists across time groups, maintaining the cumulative knowledge state
Time Complexity: O(M log M + M + N)
where M is the number of meetings and N is the number of people. The sorting takes O(M log M)
, and processing all meetings takes O(M)
.
Space Complexity: O(N + M)
for the visited array and the temporary graphs created at each timestamp.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input:
n = 6
(people numbered 0-5)firstPerson = 1
(person 1 learns the secret at time 0)meetings = [[1,2,5], [2,3,8], [1,5,10], [3,4,5]]
Step 1: Initial Setup
- Create
vis = [False, False, False, False, False, False]
- Mark person 0 and person 1 as knowing the secret:
vis = [True, True, False, False, False, False]
- Sort meetings by time:
[[3,4,5], [1,2,5], [2,3,8], [1,5,10]]
Step 2: Process Time 5 (meetings at index 0-1)
- Meetings:
[3,4,5]
and[1,2,5]
- Build graph:
g = {3: [4], 4: [3], 1: [2], 2: [1]} s = {1, 2, 3, 4}
- Find who knows the secret in this group: person 1
- BFS from person 1:
- Visit person 1 β spread to person 2
- Mark
vis[2] = True
- Persons 3 and 4 form a separate component (no one knows secret there)
- After time 5:
vis = [True, True, True, False, False, False]
Step 3: Process Time 8 (meeting at index 2)
- Meeting:
[2,3,8]
- Build graph:
g = {2: [3], 3: [2]} s = {2, 3}
- Find who knows the secret in this group: person 2
- BFS from person 2:
- Visit person 2 β spread to person 3
- Mark
vis[3] = True
- After time 8:
vis = [True, True, True, True, False, False]
Step 4: Process Time 10 (meeting at index 3)
- Meeting:
[1,5,10]
- Build graph:
g = {1: [5], 5: [1]} s = {1, 5}
- Find who knows the secret in this group: person 1
- BFS from person 1:
- Visit person 1 β spread to person 5
- Mark
vis[5] = True
- After time 10:
vis = [True, True, True, True, False, True]
Final Result: People who know the secret are [0, 1, 2, 3, 5]
Note how person 4 never learns the secret because they only met person 3 at time 5, before person 3 knew the secret. This demonstrates the importance of processing meetings chronologically.
Solution Implementation
1from collections import defaultdict, deque
2from typing import List
3
4class Solution:
5 def findAllPeople(
6 self, n: int, meetings: List[List[int]], firstPerson: int
7 ) -> List[int]:
8 # Track who knows the secret (initially person 0 and firstPerson)
9 knows_secret = [False] * n
10 knows_secret[0] = True
11 knows_secret[firstPerson] = True
12
13 # Sort meetings by time to process them chronologically
14 meetings.sort(key=lambda meeting: meeting[2])
15
16 meeting_idx = 0
17 total_meetings = len(meetings)
18
19 # Process all meetings grouped by time
20 while meeting_idx < total_meetings:
21 # Find all meetings happening at the same time
22 end_idx = meeting_idx
23 current_time = meetings[meeting_idx][2]
24
25 while end_idx + 1 < total_meetings and meetings[end_idx + 1][2] == current_time:
26 end_idx += 1
27
28 # Build graph for people meeting at this time
29 people_at_current_time = set()
30 adjacency_list = defaultdict(list)
31
32 for person_x, person_y, _ in meetings[meeting_idx : end_idx + 1]:
33 # Create bidirectional edges for people meeting
34 adjacency_list[person_x].append(person_y)
35 adjacency_list[person_y].append(person_x)
36 people_at_current_time.add(person_x)
37 people_at_current_time.add(person_y)
38
39 # BFS to spread the secret among connected people at this time
40 queue = deque()
41
42 # Start BFS from people who already know the secret
43 for person in people_at_current_time:
44 if knows_secret[person]:
45 queue.append(person)
46
47 # Spread the secret through the connected component
48 while queue:
49 current_person = queue.popleft()
50
51 for neighbor in adjacency_list[current_person]:
52 if not knows_secret[neighbor]:
53 knows_secret[neighbor] = True
54 queue.append(neighbor)
55
56 # Move to next time group
57 meeting_idx = end_idx + 1
58
59 # Return list of all people who know the secret
60 return [person_id for person_id in range(n) if knows_secret[person_id]]
61
1class Solution {
2 public List<Integer> findAllPeople(int n, int[][] meetings, int firstPerson) {
3 // Track which people know the secret
4 boolean[] knowsSecret = new boolean[n];
5 knowsSecret[0] = true; // Person 0 initially knows the secret
6 knowsSecret[firstPerson] = true; // firstPerson initially knows the secret
7
8 // Sort meetings by time
9 int totalMeetings = meetings.length;
10 Arrays.sort(meetings, Comparator.comparingInt(meeting -> meeting[2]));
11
12 // Process meetings grouped by time
13 for (int i = 0; i < totalMeetings;) {
14 // Find all meetings happening at the same time
15 int endIndex = i;
16 while (endIndex + 1 < totalMeetings &&
17 meetings[endIndex + 1][2] == meetings[i][2]) {
18 endIndex++;
19 }
20
21 // Build adjacency list for people meeting at this time
22 Map<Integer, List<Integer>> adjacencyList = new HashMap<>();
23 Set<Integer> peopleInCurrentTime = new HashSet<>();
24
25 for (int k = i; k <= endIndex; k++) {
26 int person1 = meetings[k][0];
27 int person2 = meetings[k][1];
28
29 // Create bidirectional edges
30 adjacencyList.computeIfAbsent(person1, key -> new ArrayList<>()).add(person2);
31 adjacencyList.computeIfAbsent(person2, key -> new ArrayList<>()).add(person1);
32
33 // Track all people involved in meetings at this time
34 peopleInCurrentTime.add(person1);
35 peopleInCurrentTime.add(person2);
36 }
37
38 // BFS to spread the secret among connected people at this time
39 Deque<Integer> queue = new ArrayDeque<>();
40
41 // Start BFS from people who already know the secret
42 for (int person : peopleInCurrentTime) {
43 if (knowsSecret[person]) {
44 queue.offer(person);
45 }
46 }
47
48 // Spread the secret through the connected graph
49 while (!queue.isEmpty()) {
50 int currentPerson = queue.poll();
51
52 // Share secret with all neighbors
53 for (int neighbor : adjacencyList.getOrDefault(currentPerson, Collections.emptyList())) {
54 if (!knowsSecret[neighbor]) {
55 knowsSecret[neighbor] = true;
56 queue.offer(neighbor);
57 }
58 }
59 }
60
61 // Move to next time group
62 i = endIndex + 1;
63 }
64
65 // Collect all people who know the secret
66 List<Integer> result = new ArrayList<>();
67 for (int person = 0; person < n; person++) {
68 if (knowsSecret[person]) {
69 result.add(person);
70 }
71 }
72
73 return result;
74 }
75}
76
1class Solution {
2public:
3 vector<int> findAllPeople(int n, vector<vector<int>>& meetings, int firstPerson) {
4 // Track which people know the secret
5 vector<bool> knowsSecret(n);
6 knowsSecret[0] = true; // Person 0 initially knows
7 knowsSecret[firstPerson] = true; // firstPerson initially knows
8
9 // Sort meetings by time
10 sort(meetings.begin(), meetings.end(), [](const auto& a, const auto& b) {
11 return a[2] < b[2];
12 });
13
14 int totalMeetings = meetings.size();
15 int currentIndex = 0;
16
17 // Process meetings grouped by same time
18 while (currentIndex < totalMeetings) {
19 // Find the range of meetings happening at the same time
20 int endIndex = currentIndex;
21 while (endIndex + 1 < totalMeetings &&
22 meetings[endIndex + 1][2] == meetings[currentIndex][2]) {
23 endIndex++;
24 }
25
26 // Build adjacency list for people meeting at this time
27 unordered_map<int, vector<int>> adjacencyList;
28 unordered_set<int> peopleInCurrentTime;
29
30 // Add all meetings at current time to the graph
31 for (int k = currentIndex; k <= endIndex; k++) {
32 int person1 = meetings[k][0];
33 int person2 = meetings[k][1];
34
35 adjacencyList[person1].push_back(person2);
36 adjacencyList[person2].push_back(person1);
37 peopleInCurrentTime.insert(person1);
38 peopleInCurrentTime.insert(person2);
39 }
40
41 // BFS to spread the secret among connected people at this time
42 queue<int> bfsQueue;
43
44 // Start BFS from people who already know the secret
45 for (int person : peopleInCurrentTime) {
46 if (knowsSecret[person]) {
47 bfsQueue.push(person);
48 }
49 }
50
51 // Spread the secret through connected components
52 while (!bfsQueue.empty()) {
53 int currentPerson = bfsQueue.front();
54 bfsQueue.pop();
55
56 // Share secret with all people meeting currentPerson
57 for (int neighbor : adjacencyList[currentPerson]) {
58 if (!knowsSecret[neighbor]) {
59 knowsSecret[neighbor] = true;
60 bfsQueue.push(neighbor);
61 }
62 }
63 }
64
65 // Move to next time group
66 currentIndex = endIndex + 1;
67 }
68
69 // Collect all people who know the secret
70 vector<int> result;
71 for (int i = 0; i < n; i++) {
72 if (knowsSecret[i]) {
73 result.push_back(i);
74 }
75 }
76
77 return result;
78 }
79};
80
1/**
2 * Finds all people who know the secret after all meetings
3 * @param n - Total number of people (0 to n-1)
4 * @param meetings - Array of meetings [person1, person2, time]
5 * @param firstPerson - The first person who knows the secret (besides person 0)
6 * @returns Array of all people who know the secret
7 */
8function findAllPeople(n: number, meetings: number[][], firstPerson: number): number[] {
9 // Initialize Union-Find parent array where each person is their own parent initially
10 const parent: number[] = Array.from({ length: n + 1 }, (_, index) => index);
11
12 // Person 0 knows the secret initially, firstPerson also knows it
13 parent[firstPerson] = 0;
14
15 /**
16 * Find the root parent of a person using path compression
17 * @param index - The person's index
18 * @returns The root parent of the person
19 */
20 function findParent(index: number): number {
21 if (parent[index] !== index) {
22 // Path compression: directly connect to root parent
23 parent[index] = findParent(parent[index]);
24 }
25 return parent[index];
26 }
27
28 // Group meetings by time
29 const meetingsByTime = new Map<number, number[][]>();
30
31 for (const meeting of meetings) {
32 const [person1, person2, time] = meeting;
33
34 if (!meetingsByTime.has(time)) {
35 meetingsByTime.set(time, []);
36 }
37 meetingsByTime.get(time)!.push([person1, person2]);
38 }
39
40 // Process meetings in chronological order
41 const sortedTimes = Array.from(meetingsByTime.keys()).sort((a, b) => a - b);
42
43 for (const time of sortedTimes) {
44 const currentMeetings = meetingsByTime.get(time)!;
45
46 // First pass: Union people who meet at this time
47 for (const [personA, personB] of currentMeetings) {
48 const rootA = findParent(personA);
49 const rootB = findParent(personB);
50
51 // If either person knows the secret (parent is 0), share it
52 if (parent[rootA] === 0 || parent[rootB] === 0) {
53 parent[rootA] = 0;
54 parent[rootB] = 0;
55 }
56
57 // Union the two groups
58 parent[rootA] = parent[rootB];
59 }
60
61 // Second pass: Reset people who don't know the secret after this time
62 for (const [personA, personB] of currentMeetings) {
63 const rootA = findParent(personA);
64 const rootB = findParent(personB);
65
66 // If they know the secret, keep the connection
67 if (parent[rootA] === 0 || parent[rootB] === 0) {
68 parent[rootA] = 0;
69 parent[rootB] = 0;
70 } else {
71 // Reset connections for people who don't know the secret
72 parent[personA] = personA;
73 parent[personB] = personB;
74 }
75 }
76 }
77
78 // Collect all people who know the secret
79 const result: number[] = [];
80
81 for (let i = 0; i <= n; i++) {
82 // A person knows the secret if their root parent is 0
83 if (parent[findParent(i)] === 0) {
84 result.push(i);
85 }
86 }
87
88 return result;
89}
90
Time and Space Complexity
Time Complexity: O(m log m + m + E)
where m
is the number of meetings and E
is the total number of edges across all time groups.
- Sorting the meetings takes
O(m log m)
- The outer while loop processes each meeting exactly once:
O(m)
- For each time group, we:
- Build the adjacency list:
O(k)
wherek
is the number of meetings in that time group - Perform BFS traversal:
O(V + E)
whereV
is the number of unique people in that time group andE
is the number of edges (connections) in that time group
- Build the adjacency list:
- Since each meeting is processed once and contributes to exactly one time group's graph, the total BFS work across all time groups is
O(m + E)
whereE
can be at most2m
(since each meeting creates at most 2 directed edges) - Overall:
O(m log m + m + E)
=O(m log m + m)
=O(m log m)
Space Complexity: O(n + m)
vis
array:O(n)
- For each time group, we create:
- Set
s
:O(V)
whereV
is the number of unique people in that time group - Graph
g
:O(V + E)
whereE
is the number of edges in that time group - Queue
q
:O(V)
in the worst case
- Set
- Since we process one time group at a time and the temporary structures are recreated for each group, the maximum space used at any point is
O(n)
for thevis
array plusO(V + E)
for the largest time group - In the worst case, all meetings could happen at the same time, making
V = O(n)
andE = O(m)
- Overall:
O(n + m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Simultaneous Secret Propagation Within Same Time
The Pitfall: A common mistake is to process meetings at the same time individually rather than as a group. If you process meetings one by one, you might miss the chain reaction of secret spreading that should happen instantaneously.
Example Scenario:
meetings = [[1, 2, 5], [2, 3, 5], [3, 4, 5]] # If person 1 knows the secret: # - Person 1 meets 2 at time 5 (2 learns secret) # - Person 2 meets 3 at time 5 (3 should learn secret immediately) # - Person 3 meets 4 at time 5 (4 should learn secret immediately)
Wrong Approach:
# Processing meetings individually for x, y, time in meetings: if knows_secret[x]: knows_secret[y] = True elif knows_secret[y]: knows_secret[x] = True # This fails because person 2 learns at time 5 but can't immediately share with person 3
Correct Solution: Group all meetings by time and use BFS to propagate the secret through the entire connected component at once, as shown in the provided solution.
2. Forgetting to Sort Meetings by Time
The Pitfall: Processing meetings in arbitrary order instead of chronological order means people who learn the secret later might not be able to share it in their subsequent meetings.
Example:
meetings = [[1, 2, 10], [0, 1, 5]] # Unsorted # If processed as-is: # - Meeting at time 10: persons 1 and 2 meet (neither knows secret yet) # - Meeting at time 5: person 0 shares with person 1 # Person 2 never learns the secret!
Solution: Always sort meetings by timestamp before processing:
meetings.sort(key=lambda meeting: meeting[2])
3. Creating a Global Graph Instead of Per-Timestamp Graphs
The Pitfall: Building one graph for all meetings ignores the temporal aspect - people can only share secrets during their actual meeting times.
Wrong Approach:
# Building a single graph for all meetings
graph = defaultdict(list)
for x, y, _ in meetings:
graph[x].append(y)
graph[y].append(x)
# Then doing one BFS - this incorrectly allows secret sharing across different times
Correct Solution: Create a fresh graph for each timestamp group and only propagate the secret among people meeting at that specific time.
4. Not Maintaining Persistent Knowledge State
The Pitfall: Resetting or not properly maintaining the knows_secret
array between different timestamps means people "forget" the secret they learned earlier.
Example of Wrong Implementation:
while meeting_idx < total_meetings: # Wrong: Creating new array for each time group temp_knows = knows_secret.copy() # ... BFS propagation ... knows_secret = temp_knows # This might lose information
Correct Approach: Use a single knows_secret
array throughout the entire algorithm that accumulates knowledge over time. Once someone learns the secret, they keep it forever.
5. Incorrect BFS Initialization
The Pitfall: Starting BFS from all people in meetings rather than only those who already know the secret.
Wrong:
queue = deque(people_at_current_time) # Start with everyone
Correct:
queue = deque([person for person in people_at_current_time if knows_secret[person]])
Only people who already know the secret can spread it to others in their meetings.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro Once we have a strong grasp of recursion and Depth First Search we can now introduce Disjoint Set Union DSU This data structure is motivated by the following problem Suppose we have sets of elements
Want a Structured Path to Master System Design Too? Donβt Miss This!