LeetCode Solutions

721. Accounts Merge

Time: $O(nk\log nk + nk\alpha(n)) = O(nk\log nk)$, where $k = \max(|\texttt{accounts[i]}|)$

Space: $O(n + nk) = O(nk)$

			

class UnionFind {
 public:
  UnionFind(int n) : sz(n, 1), id(n) {
    iota(begin(id), end(id), 0);
  }

  void unionBySize(int u, int v) {
    const int i = find(u);
    const int j = find(v);
    if (i == j)
      return;
    if (sz[i] < sz[j]) {
      sz[j] += sz[i];
      id[i] = j;
    } else {
      sz[i] += sz[j];
      id[j] = i;
    }
  }

  int find(int u) {
    return id[u] == u ? u : id[u] = find(id[u]);
  }

 private:
  vector<int> sz;
  vector<int> id;
};

class Solution {
 public:
  vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
    vector<vector<string>> ans;
    unordered_map<string, int> emailToIndex;        // {email: index}
    unordered_map<int, set<string>> indexToEmails;  // {index: {emails}}
    UnionFind uf(accounts.size());

    for (int i = 0; i < accounts.size(); ++i) {
      const string name = accounts[i][0];
      for (int j = 1; j < accounts[i].size(); ++j) {
        const string email = accounts[i][j];
        const auto it = emailToIndex.find(email);
        if (it == emailToIndex.end()) {
          // Only record if it's the first time we see thie email
          emailToIndex[email] = i;
        } else {
          // Otherwise, union i w/ emailToIndex[index]
          uf.unionBySize(i, it->second);
        }
      }
    }

    for (const auto& [email, index] : emailToIndex)
      indexToEmails[uf.find(index)].insert(email);

    for (const auto& [index, emails] : indexToEmails) {
      const string name = accounts[index][0];
      vector<string> row{name};
      row.insert(end(row), begin(emails), end(emails));
      ans.push_back(row);
    }

    return ans;
  }
};
			

class UnionFind {
  public UnionFind(List<List<String>> accounts) {
    for (List<String> account : accounts)
      for (int i = 1; i < account.size(); ++i) {
        final String email = account.get(i);
        id.putIfAbsent(email, email);
      }
  }

  public void union(final String u, final String v) {
    id.put(find(u), find(v));
  }

  public String find(final String u) {
    if (u != id.get(u))
      id.put(u, find(id.get(u)));
    return id.get(u);
  }

  private Map<String, String> id = new HashMap<>();
}

class Solution {
  public List<List<String>> accountsMerge(List<List<String>> accounts) {
    List<List<String>> ans = new ArrayList<>();
    Map<String, String> emailToName = new HashMap<>();
    Map<String, TreeSet<String>> idEmailToEmails = new HashMap<>();
    UnionFind uf = new UnionFind(accounts);

    // Get {email: name} mapping
    for (final List<String> account : accounts)
      for (int i = 1; i < account.size(); ++i)
        emailToName.putIfAbsent(account.get(i), account.get(0));

    // Union emails
    for (final List<String> account : accounts)
      for (int i = 2; i < account.size(); ++i)
        uf.union(account.get(i), account.get(i - 1));

    for (final List<String> account : accounts)
      for (int i = 1; i < account.size(); ++i) {
        final String id = uf.find(account.get(i));
        idEmailToEmails.putIfAbsent(id, new TreeSet<>());
        idEmailToEmails.get(id).add(account.get(i));
      }

    for (final String idEmail : idEmailToEmails.keySet()) {
      List<String> emails = new ArrayList<>(idEmailToEmails.get(idEmail));
      final String name = emailToName.get(idEmail);
      emails.add(0, name);
      ans.add(emails);
    }

    return ans;
  }
}