「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。
记录 1 道算法题
账户合并
721. 账户合并 - 力扣(LeetCode) (leetcode-cn.com)
要求:将拥有相同邮箱的用号合并,有相同邮箱的账号一定有相同的名字,输出要求邮箱按照 ASCII 码排序,账号的顺序没有要求。
['John', 'johnsmith@mail.com', 'john00@mail.com'],
['John', 'johnsmith@mail.com']
输出:
['John', 'johnsmith@mail.com', 'john00@mail.com']
考虑到只有名字出现多次的账号才有可能需要合并,所以一开始先按照名字收集邮箱,目标是:{ 'John':[[], [], ...] }
。如果相同的名字的账号有多个就把邮箱收集成数组,push 到名字下面。
然后处理这个对象时,如果只有一组邮箱,那就直接排序输出,如果有多组邮箱就用并查集进行合并。
有多组邮箱的情况是 [[], [], [], ...]
。是一个二维数组,而且数组里面不止一个邮箱。所以要把邮箱都遍历一遍,用 Map 构建一个表,每个邮箱都对应着他所在的组的下标,当遇到重复的邮箱时,就能知道这个邮箱第一次赋值时的组是哪一个,然后进行连接。然后并查集管理的对象不是邮箱,而是邮箱组,所以组的下标是一一对应的。读里面的邮箱时就知道隶属于哪个组。
并查集进行关系的连接后,当我们再遍历这个 Map 的时候,我们就可以通过 find 得到邮箱最终隶属的组。
这道题并查集是其次,因为并查集的使用方法很固定,考究的是怎么建立并查集管理的表。
完整代码如下:
function accountsMerge(accounts) {
const map = new Map()
// 按名字进行收集
for (let [name, ...emails] of accounts) {
if (!map.has(name)) {
map.set(name, [emails])
} else {
map.get(name).push(emails)
}
}
// 返回的结果数组
const res = []
for (let [name, emailsGroup] of map) {
// 如果只有一个账号就直接去重排序
if (emailsGroup.length === 1) {
res.push([name, [...new Set(emailsGroup[0])].sort()])
} else {
let m = new Map()
// 并查集管理多个邮箱组
const parent = Array.from(new Array(emailsGroup.length), (_, i) => i)
emailsGroup.forEach((emails, i) => {
// 当前邮箱组内的邮箱进行登记
emails.forEach(email => {
if (!m.has(email)) {
// 每个邮箱对应自己组所在的下标
m.set(email, i)
} else {
// 如果已经记录过了,就并入那个邮箱的组的下标
parent[i] = find(parent, m.get(email))
}
})
})
const _res = {}
// 根据连接的关系生成合并后的账号的邮箱
for(let [email, index] of m) {
const i = find(parent, index)
if (!_res[i]) {
// 在同一循环内都是同一个名字
_res[i] = [name]
}
_res[i].push(email)
}
// 排序,map 记录时已经去重
res.push(...Object.values(_res).map(emails => emails.sort()))
}
}
return res
}
function find(parent, i) {
if (parent[i] !== i) {
parent[i] = find(parent, parent[i])
}
return parent[i]
}
结束