Loading problems...
TL;DR: Sort the friendship logs by timestamp and use the Union-Find (Disjoint Set Union) data structure to merge social groups, returning the timestamp when the number of connected components drops to one.
The problem asks us to determine the precise moment in time when a group of people becomes fully connected. We are provided with a list of logs, where each log contains a timestamp and two individuals who became friends at that time. Friendship is transitive; if Person A is friends with Person B, and Person B is friends with Person C, then Person A is friends with Person C. We need to process these relationships to find the earliest timestamp where every person is part of the same friend circle.
This is a classic dynamic connectivity problem, often appearing in technical interviews as "LeetCode 1101". The core challenge of The Earliest Moment When Everyone Become Friends is efficiently managing merging groups and checking global connectivity as events occur.
A naive approach to solving this problem involves simulating the timeline and checking the entire graph's connectivity after every single friendship event.
logs array by timestamp to process events chronologically.[timestamp, x, y]:
x and person y in an adjacency list (representing the graph).timestamp.-1.textfunction earliestAcq(logs, n): sort logs by timestamp graph = new AdjacencyList(n) for each (t, x, y) in logs: graph.addEdge(x, y) visited_count = performBFS(graph, start_node=0) if visited_count == n: return t return -1
The key intuition lies in viewing each person as an isolated island initially. As we process friendship logs, we build bridges between these islands.
Instead of running a full traversal to check if all islands are connected, we can maintain a count of connected components.
The moment the number of components drops to 1, we know everyone is connected. Since we process logs in increasing order of timestamps, the first time this counter hits 1 corresponds to the earliest possible moment.
Visual Description:
Imagine a forest of trees, where each node is a root of its own tree. The parent array tracks these relationships.
parent[i] = i for all .A and node B, we find the root of A and the root of B. We make one root the child of the other.
To implement the optimal solution for LeetCode 1101, we utilize the DSU data structure with Path Compression and Union by Rank (or Size) for near-constant time operations.
logs array based on timestamps. This is critical because the problem asks for the earliest moment.parent array of size , initialized such that parent[i] = i.rank array (optional but recommended) to optimize tree height.component_count variable initialized to .x and y at time t:
x (rootX) and y (rootY).rootX != rootY, perform a Union operation:
rootY the parent of rootX).component_count by 1.component_count == 1.t.component_count is still greater than 1, return -1 (meaning the group never fully connected).Let's trace the algorithm with and logs: [[20190101, 0, 1], [20190104, 3, 0], [20190107, 2, 3]].
parent = [0, 1, 2, 3]components = 4[20190101, 0, 1]
parent becomes [1, 1, 2, 3] (0 points to 1).components becomes 3.[20190104, 3, 0]
parent becomes [1, 1, 2, 1] (3 points to 1).components becomes 2.[20190107, 2, 3]
parent becomes [1, 1, 1, 1] (2 points to 1).components becomes 1.The correctness relies on two properties:
component_count equals the number of disjoint sets in the graph. Since we start with sets and only decrement when two previously disjoint sets merge, component_count == 1 is the necessary and sufficient condition for full connectivity.cpp#include <vector> #include <algorithm> #include <numeric> using namespace std; class Solution { struct DSU { vector<int> parent; vector<int> rank; int components; DSU(int n) { parent.resize(n); // Initialize parent pointers to self iota(parent.begin(), parent.end(), 0); rank.assign(n, 0); components = n; } int find(int x) { if (parent[x] != x) { // Path compression parent[x] = find(parent[x]); } return parent[x]; } bool unite(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { // Union by rank if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else { parent[rootY] = rootX; rank[rootX]++; } components--; return true; } return false; } }; public: int earliestAcq(vector<vector<int>>& logs, int n) { // Sort logs by timestamp sort(logs.begin(), logs.end(), [](const vector<int>& a, const vector<int>& b) { return a[0] < b[0]; }); DSU dsu(n); for (const auto& log : logs) { int timestamp = log[0]; int person1 = log[1]; int person2 = log[2]; if (dsu.unite(person1, person2)) { if (dsu.components == 1) { return timestamp; } } } return -1; } };
javaimport java.util.Arrays; import java.util.Comparator; class Solution { class DSU { int[] parent; int[] rank; int components; public DSU(int n) { parent = new int[n]; rank = new int[n]; components = n; for (int i = 0; i < n; i++) { parent[i] = i; } } public int find(int x) { if (parent[x] != x) { // Path compression parent[x] = find(parent[x]); } return parent[x]; } public boolean union(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX != rootY) { // Union by rank if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else { parent[rootY] = rootX; rank[rootX]++; } components--; return true; } return false; } } public int earliestAcq(int[][] logs, int n) { // Sort logs by timestamp (index 0) Arrays.sort(logs, Comparator.comparingInt(a -> a[0])); DSU dsu = new DSU(n); for (int[] log : logs) { int timestamp = log[0]; int x = log[1]; int y = log[2]; // Attempt to union the two people if (dsu.union(x, y)) { // If components drop to 1, everyone is connected if (dsu.components == 1) { return timestamp; } } } return -1; } }
pythonclass Solution: def earliestAcq(self, logs: list[list[int]], n: int) -> int: # Sort logs by timestamp logs.sort(key=lambda x: x[0]) parent = list(range(n)) rank = [0] * n self.components = n def find(x): if parent[x] != x: # Path compression parent[x] = find(parent[x]) return parent[x] def union(x, y): rootX = find(x) rootY = find(y) if rootX != rootY: # Union by rank if rank[rootX] < rank[rootY]: parent[rootX] = rootY elif rank[rootX] > rank[rootY]: parent[rootY] = rootX else: parent[rootY] = rootX rank[rootX] += 1 self.components -= 1 return True return False for timestamp, x, y in logs: if union(x, y): if self.components == 1: return timestamp return -1
Let be the number of people and be the number of logs.
The Graph - Union-Find (Disjoint Set Union - DSU) pattern is versatile. Here is how it applies to similar problems:
1s. The final number of sets is the answer.find(u) == find(v) before union) and verifying a single component.Why does my solution return the wrong timestamp?
logs array.Why does my component count drop too low?
components without checking if rootX != rootY.Why is my solution Time Limit Exceeded (TLE) on large inputs?
parent and rank arrays in the DSU structure.component_count.find operation can degrade to in the worst case (a skewed tree), making the total complexity .