721. Accounts Merge
MediumDescription
給定帳戶列表
accounts,每個帳戶第一個元素是名字,其餘是該帳戶的電子信箱。若兩個帳戶有相同的電子信箱,則屬於同一個人。合併所有屬於同一個人的帳戶,信箱按字典序排列。
Example:
Input: accounts = [[“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”],[“Mary”,“mary@mail.com”],[“John”,“johnnybravo@mail.com”]]
Intuition#
將每個信箱視為節點,同一帳戶的信箱互相連接,用 Union-Find 或 DFS 找連通分量。
- 同一帳戶的信箱屬於同一個人,形成連通分量
- 不同帳戶若有相同信箱,也應合併
- 找出所有連通分量後,按要求格式輸出
Approaches#
1: DFS#
- 概念: 建立信箱的鄰接表,DFS 找出所有連通分量
- 時間複雜度:
O(N * K * log(N * K)),N 為帳戶數,K 為平均信箱數 - 空間複雜度:
O(N * K)
class Solution {
fun accountsMerge(accounts: List<List<String>>): List<List<String>> {
val graph = HashMap<String, MutableSet<String>>()
val emailToName = HashMap<String, String>()
for (account in accounts) {
val name = account[0]
for (i in 1 until account.size) {
emailToName[account[i]] = name
graph.getOrPut(account[i]) { mutableSetOf() }
if (i > 1) {
graph[account[1]]!!.add(account[i])
graph[account[i]]!!.add(account[1])
}
}
}
val visited = HashSet<String>()
val result = mutableListOf<List<String>>()
for (email in graph.keys) {
if (email in visited) continue
val component = mutableListOf<String>()
val stack = ArrayDeque<String>()
stack.add(email)
while (stack.isNotEmpty()) {
val cur = stack.removeLast()
if (cur in visited) continue
visited.add(cur)
component.add(cur)
for (next in graph[cur]!!) {
if (next !in visited) stack.add(next)
}
}
component.sort()
result.add(listOf(emailToName[email]!!) + component)
}
return result
}
}⭐2: Union-Find#
- 概念: 用 Union-Find 將同帳戶的信箱合併,最後按根節點分組
- 時間複雜度:
O(N * K * α(N * K)) - 空間複雜度:
O(N * K)
class Solution {
private val parent = HashMap<String, String>()
fun accountsMerge(accounts: List<List<String>>): List<List<String>> {
val emailToName = HashMap<String, String>()
for (account in accounts) {
val name = account[0]
for (i in 1 until account.size) {
emailToName[account[i]] = name
parent.putIfAbsent(account[i], account[i])
if (i > 1) union(account[1], account[i])
}
}
val groups = HashMap<String, MutableList<String>>()
for (email in parent.keys) {
val root = find(email)
groups.getOrPut(root) { mutableListOf() }.add(email)
}
return groups.values.map { emails ->
emails.sort()
listOf(emailToName[emails[0]]!!) + emails
}
}
private fun find(x: String): String {
if (parent[x] != x) parent[x] = find(parent[x]!!)
return parent[x]!!
}
private fun union(x: String, y: String) {
val px = find(x)
val py = find(y)
if (px != py) parent[px] = py
}
}🔑 Takeaways#
- Pattern: 連通分量合併 – Union-Find 處理等價類
- 關鍵技巧: 只需將每個帳戶的信箱與第一個信箱做 union,不需兩兩 union;注意最後要按字典序排列信箱