You are given an array logs
where each element logs[i] = [timestamp_i, x_i, y_i]
indicates that at time timestamp_i
, person x_i
and person y_i
become friends. You are also given an integer n
representing the total number of people. Friendship is transitive. That is, if A is a friend of B, and B is a friend of C, then A is a friend of C.
Your task is to find the earliest time when all the n
people are connected (i.e., every person is a friend of every other person). Return the earliest timestamp at which this happens. If there is no time at which all people are connected, return -1
.
For example:
logs = [[20190101,0,1],[20190104,3,4],[20190107,2,3],[20190211,1,5],[20190224,2,4],[20190301,0,3],[20190312,1,2],[20190322,4,5]], n = 6
In this case, the people become connected at the following times:
logs = [[0,2,0],[1,0,1],[3,0,3],[4,1,2],[5,1,3]], n = 4
The output is 5, because that is the first time that all nodes are connected.
Can you implement an algorithm to efficiently solve this problem?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach means we will explore every possible friendship event order to see when everyone is connected. We'll go through each friendship event, one at a time, and check if, after adding that friendship, everyone is friends with each other.
Here's how the algorithm would work step-by-step:
def earliest_moment_everyone_friends(number_people, friendships):
for current_time, person_one, person_two in friendships:
# Add the current friendship to the network
friend_network = build_friend_network(friendships, current_time)
# Check if everyone is connected
if all_connected(number_people, friend_network):
return current_time
return -1
def build_friend_network(friendships, current_time):
friend_network = {}
for time_stamp, person_one, person_two in friendships:
if time_stamp <= current_time:
if person_one not in friend_network:
friend_network[person_one] = []
if person_two not in friend_network:
friend_network[person_two] = []
friend_network[person_one].append(person_two)
friend_network[person_two].append(person_one)
return friend_network
def all_connected(number_people, friend_network):
if not friend_network:
return number_people == 1
first_person = next(iter(friend_network))
visited = {first_person}
queue = [first_person]
# Perform BFS to find all connected people
while queue:
person = queue.pop(0)
if person in friend_network:
for friend in friend_network[person]:
if friend not in visited:
visited.add(friend)
queue.append(friend)
# Verify that everyone is connected
return len(visited) == number_people
# This is an example of usage for this function
# number_people = 4
# friendships = [[0, 1, 2], [1, 2, 3], [2, 1, 4]]
# result = earliest_moment_everyone_friends(number_people, friendships)
# print(result)
The goal is to find the earliest time when everyone is connected. To do this efficiently, we'll use a method to track connections and efficiently merge groups of people until everyone belongs to the same group, stopping at the first moment this happens.
Here's how the algorithm would work step-by-step:
def find_earliest_moment(number_of_people, connections):
parent = list(range(number_of_people))
number_of_groups = number_of_people
def find(person):
if parent[person] != person:
parent[person] = find(parent[person])
return parent[person]
def union(person1, person2):
nonlocal number_of_groups
root_person1 = find(person1)
root_person2 = find(person2)
if root_person1 != root_person2:
parent[root_person1] = root_person2
number_of_groups -= 1
for timestamp, person1, person2 in connections:
# Process connections in chronological order.
union(person1 - 1, person2 - 1)
# Check if all people are now in a single group.
if number_of_groups == 1:
return timestamp
# If no such moment exists.
return -1
Case | How to Handle |
---|---|
Empty log or no friendships established | Return -1 or appropriate error code if the log is empty, indicating no friendship ever occurred. |
Only one person exists | Return 0 if only one person exists since everyone is already 'friends' from the start. |
Log is not sorted by timestamp | Sort the log entries by timestamp as a preprocessing step to ensure correct order of events. |
Disjointed groups that never fully connect | Return -1 if the algorithm completes and there are still disjoint groups, indicating that everyone never becomes friends. |
Maximum number of people is very large, impacting Union-Find performance. | Use path compression and union by rank in the Union-Find data structure for optimal performance with a large number of people. |
Integer overflow when calculating timestamp or representing person IDs. | Use appropriate data types (e.g., long) to handle large timestamps and IDs to avoid integer overflow. |
Invalid person IDs (e.g., negative or out of range). | Validate person IDs at the beginning, raising an exception or returning an error if they are invalid. |
Log contains redundant friendship connections between the same two people at different timestamps. | The Union-Find data structure ensures that subsequent connections between already connected people are ignored, preventing incorrect earliest moment calculation. |