[路飞] 账户合并

114 阅读2分钟

「这是我参与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]
    }

结束