Merge Details of Students

Last Updated : 24 Sep, 2026

Given a 2D list details[][], where each row contains a student's name followed by their email addresses.

  • Two details belong to the same student if they have at least one email address in common. 
  • If the same name appears with different emails without any common email, treat them as different students.

Merge all such details belonging to same student.

  • If an email appears with different names, use the name from the first detail where that email appears.
  • For each merged student, keep the name and all unique email addresses in lexicographical order.

Finally, return all merged details sorted lexicographically by name. If two details have the same name, sort them by their email lists.

Examples:

Input: details[][] = [
["John", "johnsmith@mail.com", "john_newyork@mail.com"],
["John", "johnsmith@mail.com", "john00@mail.com"],
["Mary", "mary@mail.com"],
["John", "johnnybravo@mail.com"]
]
Output: [
["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"],
["John", "johnnybravo@mail.com"],
["Mary", "mary@mail.com"]
]
Explanation: The first two John details belong to the same student because they have the common email johnsmith@mail.com. Their unique email addresses are merged and sorted lexicographically.
The third John has no email address in common with the other details, so it represents a different student. Similarly, Mary has no common email with any other detail.
Finally, the merged details are sorted lexicographically by name. Since there are two John details, their sorted email lists are used to determine their order.

Input: details[][] = [
["Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co"],
["Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co"],
["Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co"],
["Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co"],
["Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co"]
]
Output: [
["Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co"],
["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"],
["Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co"],
["Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co"],
["Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co"]
]
Explanation: None of the details have a common email address, so no details need to be merged. We simply sort the email addresses of each student and then sort all the resulting details lexicographically by student name.

Try It Yourself
redirect icon

Using Graph + DFS/BFS

We can compare details and merge them whenever they share an email, but repeatedly checking all details can be inefficient.

Instead, we represent shared email connections as a graph. Then, every connected component of this graph represents one student, which we find using DFS or BFS.

  • Create a graph where each detail is represented by a node.
  • For every email address, store the indices of all details containing that email.
  • For each email, connect all details that contain that email.
  • Run DFS/BFS from every unvisited detail to find its connected component.
  • For each component, collect all unique email addresses from its details and sort them lexicographically.
  • Use the name from the first detail of the component and add the sorted emails.
  • Finally, sort all merged details lexicographically by name and, if needed, by their email lists.

Below is the implementation of the above approach:

C++
#include <bits/stdc++.h>
using namespace std;

// Merge details using Graph + BFS.
vector<vector<string>> mergeDetails(vector<vector<string>> &details)
{
    int n = details.size();

    // Each detail is represented as a node in the graph.
    vector<vector<int>> adj(n);

    // Map each email to all the details that contain it.
    unordered_map<string, vector<int>> emailDetails;

    // Store the detail index for every email.
    for (int i = 0; i < n; i++)
    {
        for (int j = 1; j < (int)details[i].size(); j++)
        {
            emailDetails[details[i][j]].push_back(i);
        }
    }

    // Connect all details that share the same email.
    for (auto &it : emailDetails)
    {
        vector<int> &nodes = it.second;

        // Connect the first detail with all other details
        // containing the same email.
        for (int i = 1; i < (int)nodes.size(); i++)
        {
            int u = nodes[0];
            int v = nodes[i];

            adj[u].push_back(v);
            adj[v].push_back(u);
        }
    }

    vector<bool> visited(n, false);
    vector<vector<string>> result;

    // Find all connected components using BFS.
    for (int i = 0; i < n; i++)
    {
        if (visited[i])
            continue;

        vector<int> component;
        queue<int> q;

        q.push(i);
        visited[i] = true;

        // Traverse the complete connected component.
        while (!q.empty())
        {
            int u = q.front();
            q.pop();

            component.push_back(u);

            for (int v : adj[u])
            {
                if (!visited[v])
                {
                    visited[v] = true;
                    q.push(v);
                }
            }
        }

        // Store all unique emails of this component.
        // set keeps the emails unique and sorted.
        set<string> emails;

        for (int idx : component)
        {
            for (int j = 1; j < (int)details[idx].size(); j++)
            {
                emails.insert(details[idx][j]);
            }
        }

        // Create the merged detail.
        vector<string> merged;

        // The first detail gives the student's name.
        merged.push_back(details[component[0]][0]);

        // Add all unique emails in sorted order.
        for (string email : emails)
        {
            merged.push_back(email);
        }

        result.push_back(merged);
    }

    // Sort the final result by name.
    // If names are equal, their email lists are compared
    // lexicographically automatically.
    sort(result.begin(), result.end());

    return result;
}

int main()
{
    vector<vector<string>> details = {{"John", "johnsmith@mail.com", "john_newyork@mail.com"},
                                      {"John", "johnsmith@mail.com", "john00@mail.com"},
                                      {"Mary", "mary@mail.com"},
                                      {"John", "johnnybravo@mail.com"}};

    vector<vector<string>> result = mergeDetails(details);

    cout << "[";

    for (int i = 0; i < (int)result.size(); i++)
    {
        cout << "[";

        for (int j = 0; j < (int)result[i].size(); j++)
        {
            cout << "\"" << result[i][j] << "\"";

            if (j + 1 < (int)result[i].size())
                cout << ",";
        }

        cout << "]";

        if (i + 1 < (int)result.size())
            cout << "," << endl;
    }

    cout << "]\n";

    return 0;
}
Java
import java.util.*;

class GFG {
    // Merge details using Graph + BFS.
    static ArrayList<ArrayList<String> >
        mergeDetails(String[][] details)
    {
        int n = details.length;

        // Each detail is represented as a node in the
        // graph.
        ArrayList<ArrayList<Integer> > adj
            = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }

        // Map each email to all the details that contain
        // it.
        HashMap<String, ArrayList<Integer> > emailDetails
            = new HashMap<>();

        // Store the detail index for every email.
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < details[i].length; j++) {
                String email = details[i][j];

                if (!emailDetails.containsKey(email)) {
                    emailDetails.put(email,
                                     new ArrayList<>());
                }

                emailDetails.get(email).add(i);
            }
        }

        // Connect all details that share the same email.
        for (ArrayList<Integer> nodes :
             emailDetails.values()) {

            // Connect the first detail with all other
            // details containing the same email.
            for (int i = 1; i < nodes.size(); i++) {
                int u = nodes.get(0);
                int v = nodes.get(i);

                adj.get(u).add(v);
                adj.get(v).add(u);
            }
        }

        boolean[] visited = new boolean[n];
        ArrayList<ArrayList<String> > result
            = new ArrayList<>();

        // Find all connected components using BFS.
        for (int i = 0; i < n; i++) {
            if (visited[i])
                continue;

            ArrayList<Integer> component
                = new ArrayList<>();
            Queue<Integer> q = new LinkedList<>();

            q.add(i);
            visited[i] = true;

            // Traverse the complete connected component.
            while (!q.isEmpty()) {
                int u = q.poll();

                component.add(u);

                for (int v : adj.get(u)) {
                    if (!visited[v]) {
                        visited[v] = true;
                        q.add(v);
                    }
                }
            }

            // Store all unique emails of this component.
            // TreeSet keeps the emails unique and sorted.
            TreeSet<String> emails = new TreeSet<>();

            for (int idx : component) {
                for (int j = 1; j < details[idx].length;
                     j++) {
                    emails.add(details[idx][j]);
                }
            }

            // Create the merged detail.
            ArrayList<String> merged = new ArrayList<>();

            // The first detail gives the student's name.
            merged.add(details[component.get(0)][0]);

            // Add all unique emails in sorted order.
            for (String email : emails) {
                merged.add(email);
            }

            result.add(merged);
        }

        // Sort the final result by name.
        // If names are equal, their email lists are
        // compared lexicographically automatically.
        Collections.sort(result, (a, b) -> {
            int size = Math.min(a.size(), b.size());

            for (int i = 0; i < size; i++) {
                int cmp = a.get(i).compareTo(b.get(i));

                if (cmp != 0)
                    return cmp;
            }

            return Integer.compare(a.size(), b.size());
        });

        return result;
    }

    public static void main(String[] args)
    {
        String[][] details
            = { { "John", "johnsmith@mail.com",
                  "john_newyork@mail.com" },
                { "John", "johnsmith@mail.com",
                  "john00@mail.com" },
                { "Mary", "mary@mail.com" },
                { "John", "johnnybravo@mail.com" } };

        ArrayList<ArrayList<String> > result
            = mergeDetails(details);

        System.out.print("[");

        for (int i = 0; i < result.size(); i++) {
            System.out.print("[");

            for (int j = 0; j < result.get(i).size(); j++) {
                System.out.print("\"" + result.get(i).get(j)
                                 + "\"");

                if (j + 1 < result.get(i).size())
                    System.out.print(",");
            }

            System.out.print("]");

            if (i + 1 < result.size())
                System.out.println(",");
        }

        System.out.println("]");
    }
}
Python
from collections import defaultdict, deque


# Merge details using Graph + BFS.
def mergeDetails(details):
    n = len(details)

    # Each detail is represented as a node in the graph.
    adj = [[] for _ in range(n)]

    # Map each email to all the details that contain it.
    emailDetails = defaultdict(list)

    # Store the detail index for every email.
    for i in range(n):
        for j in range(1, len(details[i])):
            emailDetails[details[i][j]].append(i)

    # Connect all details that share the same email.
    for nodes in emailDetails.values():

        # Connect the first detail with all other details
        # containing the same email.
        for i in range(1, len(nodes)):
            u = nodes[0]
            v = nodes[i]

            adj[u].append(v)
            adj[v].append(u)

    visited = [False] * n
    result = []

    # Find all connected components using BFS.
    for i in range(n):
        if visited[i]:
            continue

        component = []
        q = deque()

        q.append(i)
        visited[i] = True

        # Traverse the complete connected component.
        while q:
            u = q.popleft()

            component.append(u)

            for v in adj[u]:
                if not visited[v]:
                    visited[v] = True
                    q.append(v)

        # Store all unique emails of this component.
        # set keeps the emails unique.
        emails = set()

        for idx in component:
            for j in range(1, len(details[idx])):
                emails.add(details[idx][j])

        # Create the merged detail.
        merged = []

        # The first detail gives the student's name.
        merged.append(details[component[0]][0])

        # Add all unique emails in sorted order.
        for email in sorted(emails):
            merged.append(email)

        result.append(merged)

    # Sort the final result by name.
    # If names are equal, their email lists are compared
    # lexicographically automatically.
    result.sort()

    return result


# Driver Code
if __name__ == "__main__":

    details = [
        ["John", "johnsmith@mail.com", "john_newyork@mail.com"],
        ["John", "johnsmith@mail.com", "john00@mail.com"],
        ["Mary", "mary@mail.com"],
        ["John", "johnnybravo@mail.com"]
    ]

    result = mergeDetails(details)

    print("[", end="")

    for i in range(len(result)):
        print("[", end="")

        for j in range(len(result[i])):
            print("\"" + result[i][j] + "\"", end="")

            if j + 1 < len(result[i]):
                print(",", end="")

        print("]", end="")

        if i + 1 < len(result):
            print(",")

    print("]")
C#
using System;
using System.Collections.Generic;
using System.Linq;

class GFG {
    // Merge details using Graph + BFS.
    static List<List<string> > mergeDetails(string[][] details)
    {
        int n = details.Length;

        // Each detail is represented as a node in the
        // graph.
        List<List<int> > adj = new List<List<int> >();

        for (int i = 0; i < n; i++) {
            adj.Add(new List<int>());
        }

        // Map each email to all the details that contain
        // it.
        Dictionary<string, List<int> > emailDetails
            = new Dictionary<string, List<int> >();

        // Store the detail index for every email.
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < details[i].Length; j++) {
                string email = details[i][j];

                if (!emailDetails.ContainsKey(email)) {
                    emailDetails[email] = new List<int>();
                }

                emailDetails[email].Add(i);
            }
        }

        // Connect all details that share the same email.
        foreach(List<int> nodes in emailDetails.Values)
        {
            // Connect the first detail with all other
            // details containing the same email.
            for (int i = 1; i < nodes.Count; i++) {
                int u = nodes[0];
                int v = nodes[i];

                adj[u].Add(v);
                adj[v].Add(u);
            }
        }

        bool[] visited = new bool[n];
        List<List<string> > result
            = new List<List<string> >();

        // Find all connected components using BFS.
        for (int i = 0; i < n; i++) {
            if (visited[i])
                continue;

            List<int> component = new List<int>();
            Queue<int> q = new Queue<int>();

            q.Enqueue(i);
            visited[i] = true;

            // Traverse the complete connected component.
            while (q.Count > 0) {
                int u = q.Dequeue();

                component.Add(u);

                foreach(int v in adj[u])
                {
                    if (!visited[v]) {
                        visited[v] = true;
                        q.Enqueue(v);
                    }
                }
            }

            // Store all unique emails of this component.
            // SortedSet keeps the emails unique and sorted.
            SortedSet<string> emails
                = new SortedSet<string>();

            foreach(int idx in component)
            {
                for (int j = 1; j < details[idx].Length;
                     j++) {
                    emails.Add(details[idx][j]);
                }
            }

            // Create the merged detail.
            List<string> merged = new List<string>();

            // The first detail gives the student's name.
            merged.Add(details[component[0]][0]);

            // Add all unique emails in sorted order.
            foreach(string email in emails)
            {
                merged.Add(email);
            }

            result.Add(merged);
        }

        // Sort the final result by name.
        // If names are equal, their email lists are
        // compared lexicographically automatically.
        result.Sort((a, b) => {
            int size = Math.Min(a.Count, b.Count);

            for (int i = 0; i < size; i++) {
                int cmp = string.CompareOrdinal(a[i], b[i]);

                if (cmp != 0)
                    return cmp;
            }

            return a.Count.CompareTo(b.Count);
        });

        return result;
    }

    public static void Main()
    {
        string[][] details
            = { new string[] { "John", "johnsmith@mail.com",
                               "john_newyork@mail.com" },
                new string[] { "John", "johnsmith@mail.com",
                               "john00@mail.com" },
                new string[] { "Mary", "mary@mail.com" },
                new string[] { "John",
                               "johnnybravo@mail.com" } };

        List<List<string> > result = mergeDetails(details);

        Console.Write("[");

        for (int i = 0; i < result.Count; i++) {
            Console.Write("[");

            for (int j = 0; j < result[i].Count; j++) {
                Console.Write("\"" + result[i][j] + "\"");

                if (j + 1 < result[i].Count)
                    Console.Write(",");
            }

            Console.Write("]");

            if (i + 1 < result.Count)
                Console.WriteLine(",");
        }

        Console.WriteLine("]");
    }
}
JavaScript
// Merge details using Graph + BFS.
function mergeDetails(details)
{
    const n = details.length;

    // Each detail is represented as a node in the graph.
    const adj = Array.from({length : n}, () => []);

    // Map each email to all the details that contain it.
    const emailDetails = new Map();

    // Store the detail index for every email.
    for (let i = 0; i < n; i++) {
        for (let j = 1; j < details[i].length; j++) {
            const email = details[i][j];

            if (!emailDetails.has(email)) {
                emailDetails.set(email, []);
            }

            emailDetails.get(email).push(i);
        }
    }

    // Connect all details that share the same email.
    for (const nodes of emailDetails.values()) {

        // Connect the first detail with all other details
        // containing the same email.
        for (let i = 1; i < nodes.length; i++) {
            const u = nodes[0];
            const v = nodes[i];

            adj[u].push(v);
            adj[v].push(u);
        }
    }

    const visited = new Array(n).fill(false);
    const result = [];

    // Find all connected components using BFS.
    for (let i = 0; i < n; i++) {
        if (visited[i])
            continue;

        const component = [];
        const q = [ i ];
        let idx = 0;

        visited[i] = true;

        // Traverse the complete connected component.
        while (idx < q.length) {
            const u = q[idx++];

            component.push(u);

            for (const v of adj[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    q.push(v);
                }
            }
        }

        // Store all unique emails of this component.
        // Set keeps the emails unique.
        const emails = new Set();

        for (const detailIndex of component) {
            for (let j = 1; j < details[detailIndex].length;
                 j++) {
                emails.add(details[detailIndex][j]);
            }
        }

        // Create the merged detail.
        const merged = [];

        // The first detail gives the student's name.
        merged.push(details[component[0]][0]);

        // Add all unique emails in sorted order.
        const sortedEmails = Array.from(emails).sort();

        for (const email of sortedEmails) {
            merged.push(email);
        }

        result.push(merged);
    }

    // Sort the final result by name.
    // If names are equal, their email lists are compared
    // lexicographically automatically.
    result.sort((a, b) => {
        const size = Math.min(a.length, b.length);

        for (let i = 0; i < size; i++) {
            if (a[i] < b[i])
                return -1;

            if (a[i] > b[i])
                return 1;
        }

        return a.length - b.length;
    });

    return result;
}

// Driver Code
const details = [
    [
        "John", "johnsmith@mail.com",
        "john_newyork@mail.com"
    ],
    [ "John", "johnsmith@mail.com", "john00@mail.com" ],
    [ "Mary", "mary@mail.com" ],
    [ "John", "johnnybravo@mail.com" ]
];

const result = mergeDetails(details);

let output = "[";

for (let i = 0; i < result.length; i++) {
    output += "[";

    for (let j = 0; j < result[i].length; j++) {
        output += "\"" + result[i][j] + "\"";

        if (j + 1 < result[i].length)
            output += ",";
    }

    output += "]";

    if (i + 1 < result.length)
        output += ",\n";
}

output += "]";

console.log(output);

Output
[["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],
["John","johnnybravo@mail.com"],
["Mary","mary@mail.com"]]

Time Complexity: O(E * log U + n * log n), where n is the number of details, E is the total number of email occurrences and U is the number of unique emails.
Auxiliary Space: O(n + E)

Using Disjoint Set-Union

The idea is to treat each detail as a separate set initially. Whenever two details share an email, we merge their sets using DSU.

After processing all emails, details belonging to the same set form one student. We then collect all unique emails from each set, sort them, and finally sort the merged details lexicographically.

  • Create a DSU structure where every detail is initially its own group.
  • Create a map that stores the first detail index in which each email appears.
  • For every email of every detail, if the email has appeared before,
    union the current detail with the stored detail.
  • After all unions, use the DSU parent to group details belonging to the same student.
  • For every group, collect all unique emails and sort them lexicographically.
  • Use the first detail of the group as the student's name and sort the final result lexicographically.
C++
#include <bits/stdc++.h>
using namespace std;

// Find the representative of a set.
int findParent(vector<int> &parent, int x)
{
    if (parent[x] == x)
        return x;

    return parent[x] = findParent(parent, parent[x]);
}

// Merge two sets.
void unite(vector<int> &parent, int a, int b)
{
    a = findParent(parent, a);
    b = findParent(parent, b);

    if (a == b)
        return;

    // Keep the earlier detail as the root.
    if (a > b)
        swap(a, b);

    parent[b] = a;
}

// Merge details using DSU.
vector<vector<string>> mergeDetails(vector<vector<string>> &details)
{
    int n = details.size();

    vector<int> parent(n);

    // Initially, every detail is its own set.
    for (int i = 0; i < n; i++)
        parent[i] = i;

    // Map each email to the first detail containing it.
    unordered_map<string, int> emailDetails;

    // Merge details that share the same email.
    for (int i = 0; i < n; i++)
    {
        for (int j = 1; j < (int)details[i].size(); j++)
        {
            string email = details[i][j];

            if (emailDetails.find(email) == emailDetails.end())
            {
                emailDetails[email] = i;
            }
            else
            {
                unite(parent, i, emailDetails[email]);
            }
        }
    }

    // Store all unique emails for each group.
    unordered_map<int, set<string>> groups;

    for (int i = 0; i < n; i++)
    {
        int root = findParent(parent, i);

        for (int j = 1; j < (int)details[i].size(); j++)
        {
            groups[root].insert(details[i][j]);
        }
    }

    vector<vector<string>> result;

    // Create the merged details.
    for (auto &it : groups)
    {
        int root = it.first;

        vector<string> merged;

        // The earliest detail gives the student's name.
        merged.push_back(details[root][0]);

        // Add all unique emails in sorted order.
        for (string email : it.second)
        {
            merged.push_back(email);
        }

        result.push_back(merged);
    }

    // Sort the final result.
    sort(result.begin(), result.end());

    return result;
}

int main()
{
    vector<vector<string>> details = {{"John", "johnsmith@mail.com", "john_newyork@mail.com"},
                                      {"John", "johnsmith@mail.com", "john00@mail.com"},
                                      {"Mary", "mary@mail.com"},
                                      {"John", "johnnybravo@mail.com"}};

    vector<vector<string>> result = mergeDetails(details);

    cout << "[";

    for (int i = 0; i < (int)result.size(); i++)
    {
        cout << "[";

        for (int j = 0; j < (int)result[i].size(); j++)
        {
            cout << "\"" << result[i][j] << "\"";

            if (j + 1 < (int)result[i].size())
                cout << ",";
        }

        cout << "]";

        if (i + 1 < (int)result.size())
            cout << "," << endl;
    }

    cout << "]\n";

    return 0;
}
Java
import java.util.*;

class GFG {
    // Find the representative of a set.
    static int findParent(int[] parent, int x)
    {
        if (parent[x] == x)
            return x;

        parent[x] = findParent(parent, parent[x]);
        return parent[x];
    }

    // Merge two sets.
    static void unite(int[] parent, int a, int b)
    {
        a = findParent(parent, a);
        b = findParent(parent, b);

        if (a == b)
            return;

        // Keep the earlier detail as the root.
        if (a > b) {
            int temp = a;
            a = b;
            b = temp;
        }

        parent[b] = a;
    }

    // Merge details using DSU.
    static ArrayList<ArrayList<String> > mergeDetails(String[][] details)
    {
        int n = details.length;

        int[] parent = new int[n];

        // Initially, every detail is its own set.
        for (int i = 0; i < n; i++)
            parent[i] = i;

        // Map each email to the first detail containing it.
        HashMap<String, Integer> emailDetails
            = new HashMap<>();

        // Merge details that share the same email.
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < details[i].length; j++) {
                String email = details[i][j];

                if (!emailDetails.containsKey(email)) {
                    emailDetails.put(email, i);
                }
                else {
                    unite(parent, i,
                          emailDetails.get(email));
                }
            }
        }

        // Store all unique emails for each group.
        HashMap<Integer, TreeSet<String> > groups
            = new HashMap<>();

        for (int i = 0; i < n; i++) {
            int root = findParent(parent, i);

            if (!groups.containsKey(root))
                groups.put(root, new TreeSet<>());

            for (int j = 1; j < details[i].length; j++) {
                groups.get(root).add(details[i][j]);
            }
        }

        ArrayList<ArrayList<String> > result
            = new ArrayList<>();

        // Create the merged details.
        for (Map.Entry<Integer, TreeSet<String> > entry :
             groups.entrySet()) {
            int root = entry.getKey();

            ArrayList<String> merged = new ArrayList<>();

            // The earliest detail gives the student's name.
            merged.add(details[root][0]);

            // Add all unique emails in sorted order.
            for (String email : entry.getValue()) {
                merged.add(email);
            }

            result.add(merged);
        }

        // Sort the final result.
        result.sort((a, b) -> {
            int size = Math.min(a.size(), b.size());

            for (int i = 0; i < size; i++) {
                int cmp = a.get(i).compareTo(b.get(i));

                if (cmp != 0)
                    return cmp;
            }

            return Integer.compare(a.size(), b.size());
        });

        return result;
    }

    public static void main(String[] args)
    {
        String[][] details
            = { { "John", "johnsmith@mail.com",
                  "john_newyork@mail.com" },
                { "John", "johnsmith@mail.com",
                  "john00@mail.com" },
                { "Mary", "mary@mail.com" },
                { "John", "johnnybravo@mail.com" } };

        ArrayList<ArrayList<String> > result = mergeDetails(details);

        System.out.print("[");

        for (int i = 0; i < result.size(); i++) {
            System.out.print("[");

            for (int j = 0; j < result.get(i).size(); j++) {
                System.out.print("\"" + result.get(i).get(j)
                                 + "\"");

                if (j + 1 < result.get(i).size())
                    System.out.print(",");
            }

            System.out.print("]");

            if (i + 1 < result.size())
                System.out.println(",");
        }

        System.out.println("]");
    }
}
Python
# Merge details using DSU.
def mergeDetails(details):
    n = len(details)

    parent = list(range(n))

    # Find the representative of a set.
    def findParent(x):
        if parent[x] == x:
            return x

        parent[x] = findParent(parent[x])
        return parent[x]

    # Merge two sets.
    def unite(a, b):
        a = findParent(a)
        b = findParent(b)

        if a == b:
            return

        # Keep the earlier detail as the root.
        if a > b:
            a, b = b, a

        parent[b] = a

    # Map each email to the first detail containing it.
    emailDetails = {}

    # Merge details that share the same email.
    for i in range(n):
        for j in range(1, len(details[i])):
            email = details[i][j]

            if email not in emailDetails:
                emailDetails[email] = i
            else:
                unite(i, emailDetails[email])

    # Store all unique emails for each group.
    groups = {}

    for i in range(n):
        root = findParent(i)

        if root not in groups:
            groups[root] = set()

        for j in range(1, len(details[i])):
            groups[root].add(details[i][j])

    result = []

    # Create the merged details.
    for root, emails in groups.items():

        merged = []

        # The earliest detail gives the student's name.
        merged.append(details[root][0])

        # Add all unique emails in sorted order.
        for email in sorted(emails):
            merged.append(email)

        result.append(merged)

    # Sort the final result.
    result.sort()

    return result


# Driver Code
if __name__ == "__main__":

    details = [
        ["John", "johnsmith@mail.com", "john_newyork@mail.com"],
        ["John", "johnsmith@mail.com", "john00@mail.com"],
        ["Mary", "mary@mail.com"],
        ["John", "johnnybravo@mail.com"]
    ]

    result = mergeDetails(details)

    print("[", end="")

    for i in range(len(result)):
        print("[", end="")

        for j in range(len(result[i])):
            print("\"" + result[i][j] + "\"", end="")

            if j + 1 < len(result[i]):
                print(",", end="")

        print("]", end="")

        if i + 1 < len(result):
            print(",")

    print("]")
C#
using System;
using System.Collections.Generic;

class GFG {
    // Find the representative of a set.
    static int FindParent(int[] parent, int x)
    {
        if (parent[x] == x)
            return x;

        parent[x] = FindParent(parent, parent[x]);
        return parent[x];
    }

    // Merge two sets.
    static void Unite(int[] parent, int a, int b)
    {
        a = FindParent(parent, a);
        b = FindParent(parent, b);

        if (a == b)
            return;

        // Keep the earlier detail as the root.
        if (a > b) {
            int temp = a;
            a = b;
            b = temp;
        }

        parent[b] = a;
    }

    // Merge details using DSU.
    static List<List<string> > mergeDetails(string[][] details)
    {
        int n = details.Length;

        int[] parent = new int[n];

        // Initially, every detail is its own set.
        for (int i = 0; i < n; i++)
            parent[i] = i;

        // Map each email to the first detail containing it.
        Dictionary<string, int> emailDetails
            = new Dictionary<string, int>();

        // Merge details that share the same email.
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < details[i].Length; j++) {
                string email = details[i][j];

                if (!emailDetails.ContainsKey(email)) {
                    emailDetails[email] = i;
                }
                else {
                    Unite(parent, i, emailDetails[email]);
                }
            }
        }

        // Store all unique emails for each group.
        Dictionary<int, SortedSet<string> > groups
            = new Dictionary<int, SortedSet<string> >();

        for (int i = 0; i < n; i++) {
            int root = FindParent(parent, i);

            if (!groups.ContainsKey(root))
                groups[root] = new SortedSet<string>();

            for (int j = 1; j < details[i].Length; j++) {
                groups[root].Add(details[i][j]);
            }
        }

        List<List<string> > result
            = new List<List<string> >();

        // Create the merged details.
        foreach(var entry in groups)
        {
            int root = entry.Key;

            List<string> merged = new List<string>();

            // The earliest detail gives the student's name.
            merged.Add(details[root][0]);

            // Add all unique emails in sorted order.
            foreach(string email in entry.Value)
            {
                merged.Add(email);
            }

            result.Add(merged);
        }

        // Sort the final result.
        result.Sort((a, b) => {
            int size = Math.Min(a.Count, b.Count);

            for (int i = 0; i < size; i++) {
                int cmp = string.CompareOrdinal(a[i], b[i]);

                if (cmp != 0)
                    return cmp;
            }

            return a.Count.CompareTo(b.Count);
        });

        return result;
    }

    public static void Main()
    {
        string[][] details
            = { new string[] { "John", "johnsmith@mail.com",
                               "john_newyork@mail.com" },
                new string[] { "John", "johnsmith@mail.com",
                               "john00@mail.com" },
                new string[] { "Mary", "mary@mail.com" },
                new string[] { "John",
                               "johnnybravo@mail.com" } };

        List<List<string> > result = mergeDetails(details);

        Console.Write("[");

        for (int i = 0; i < result.Count; i++) {
            Console.Write("[");

            for (int j = 0; j < result[i].Count; j++) {
                Console.Write("\"" + result[i][j] + "\"");

                if (j + 1 < result[i].Count)
                    Console.Write(",");
            }

            Console.Write("]");

            if (i + 1 < result.Count)
                Console.WriteLine(",");
        }

        Console.WriteLine("]");
    }
}
JavaScript
// Find the representative of a set.
function findParent(parent, x)
{
    if (parent[x] === x)
        return x;

    parent[x] = findParent(parent, parent[x]);
    return parent[x];
}

// Merge two sets.
function unite(parent, a, b)
{
    a = findParent(parent, a);
    b = findParent(parent, b);

    if (a === b)
        return;

    // Keep the earlier detail as the root.
    if (a > b) {
        [a, b] = [ b, a ];
    }

    parent[b] = a;
}

// Merge details using DSU.
function mergeDetails(details)
{
    const n = details.length;

    const parent = Array.from({length : n}, (_, i) => i);

    // Map each email to the first detail containing it.
    const emailDetails = new Map();

    // Merge details that share the same email.
    for (let i = 0; i < n; i++) {
        for (let j = 1; j < details[i].length; j++) {
            const email = details[i][j];

            if (!emailDetails.has(email)) {
                emailDetails.set(email, i);
            }
            else {
                unite(parent, i, emailDetails.get(email));
            }
        }
    }

    // Store all unique emails for each group.
    const groups = new Map();

    for (let i = 0; i < n; i++) {
        const root = findParent(parent, i);

        if (!groups.has(root))
            groups.set(root, new Set());

        for (let j = 1; j < details[i].length; j++) {
            groups.get(root).add(details[i][j]);
        }
    }

    const result = [];

    // Create the merged details.
    for (const [root, emails] of groups) {
        const merged = [];

        // The earliest detail gives the student's name.
        merged.push(details[root][0]);

        // Add all unique emails in sorted order.
        const sortedEmails = Array.from(emails).sort();

        for (const email of sortedEmails) {
            merged.push(email);
        }

        result.push(merged);
    }

    // Sort the final result.
    result.sort((a, b) => {
        const size = Math.min(a.length, b.length);

        for (let i = 0; i < size; i++) {
            if (a[i] < b[i])
                return -1;

            if (a[i] > b[i])
                return 1;
        }

        return a.length - b.length;
    });

    return result;
}

// Driver Code
const details = [
    [
        "John", "johnsmith@mail.com",
        "john_newyork@mail.com"
    ],
    [ "John", "johnsmith@mail.com", "john00@mail.com" ],
    [ "Mary", "mary@mail.com" ],
    [ "John", "johnnybravo@mail.com" ]
];

const result = mergeDetails(details);

let output = "[";

for (let i = 0; i < result.length; i++) {
    output += "[";

    for (let j = 0; j < result[i].length; j++) {
        output += "\"" + result[i][j] + "\"";

        if (j + 1 < result[i].length)
            output += ",";
    }

    output += "]";

    if (i + 1 < result.length)
        output += ",\n";
}

output += "]";

console.log(output);

Output
[["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],
["John","johnnybravo@mail.com"],
["Mary","mary@mail.com"]]

Time Complexity: O(E * log U + n * log n), where n is the number of details, E is the total number of email occurrences and U is the number of unique emails.
Auxiliary Space: O(n + E)

Comment