1. 程式人生 > 其它 >721. 賬戶合併+並查集

721. 賬戶合併+並查集

技術標籤:java

並查集

參考並查集

並查集被很多OIer認為是最簡潔而優雅的資料結構之一,主要用於解決一些元素分組的問題。它管理一系列不相交的集合,並支援兩種操作:

-合併(Union):把兩個不相交的集合合併為一個集合。
-查詢(Find):查詢兩個元素是否在同一個集合中。

參考親戚問題

  • 題目背景
    若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。
    題目描述
    規定:x和y是親戚,y和z是親戚,那麼x和z也是親戚。如果x,y是親戚,那麼x的親戚都是y的親戚,y的親戚也都是x的親戚。
    輸入格式
    第一行:三個整數n,m,p,(n<=5000,m<=5000,p<=5000),分別表示有n個人,m個親戚關係,詢問p對親戚關係。
    以下m行:每行兩個數Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有親戚關係。
    接下來p行:每行兩個數Pi,Pj,詢問Pi和Pj是否具有親戚關係。 輸出格式
    P行,每行一個’Yes’或’No’。表示第i個詢問的答案為“具有”或“不具有”親戚關係。

即我們可以將是親戚關係的劃入一個集合,然後通過比較兩人是否為一個集合中人即可判斷兩人是否為親戚。
具體詳細講解可以檢視演算法學習筆記(1):並查集,寫的炒雞棒棒,下面我直接貼最後改善完成的程式碼。

#include <cstdio>
#define MAXN 5005
int fa[MAXN], rank[MAXN];
inline void init(int n)//初始化 { for (int i = 1; i <= n; ++i) { fa[i] = i; rank[i] = 1; } } int find(int x)//查 { return x == fa[x] ? x : (fa[x] = find(fa[x])); } inline void merge(int i, int j)//合併 { int x = find(i), y = find(j); if (rank[x] <= rank[y]) fa[
x] = y; else fa[y] = x; if (rank[x] == rank[y] && x != y) rank[y]++; }

ok,下面分析本題,也是參考大佬地程式碼(大佬牛批),連結奉上Lemon題解評論處

大佬力扣頭像
okk,程式碼來嘍

class Solution {
    public List<List<String>> accountsMerge(List<List<String>> accounts) {
        // 作用:儲存每個郵箱屬於哪個賬戶 ,同時 在遍歷郵箱時,判斷郵箱是否出現過[去重]
        // 格式:<郵箱,賬戶id>
        Map<String, Integer> emailToId = new HashMap<>();
        int n = accounts.size();//id個數
        UnionFind myUnion = new UnionFind(n);
        for(int i = 0; i < n; i++){
            int num = accounts.get(i).size();
            for(int j = 1; j < num; j++){
                String curEmail = accounts.get(i).get(j);
                //當前郵箱沒有出現過
                if(!emailToId.containsKey(curEmail)){
                    emailToId.put(curEmail, i);
                }else{//當前郵箱已經出現過,那麼代表這兩個使用者是同一個
                    myUnion.union(i, emailToId.get(curEmail));
                }
            }
        }
        //進行完上面的步驟,同一個使用者的所有郵箱已經屬於同一個連通域了,但是就算在同一個連通域,不同的郵箱還是可能會對應不同的id
        // 作用: 儲存每個賬戶下的郵箱
        // 格式: <賬戶id, 郵箱列表> >
        // 注意:這裡的key必須是賬戶id,不能是賬戶名稱,名稱可能相同,會造成覆蓋
        Map<Integer, List<String>> idToEmails = new HashMap<>();
        //將同一個連通域內的
        for(Map.Entry<String, Integer> entry : emailToId.entrySet()){
            int id = myUnion.find(entry.getValue());
            List<String> emails = idToEmails.getOrDefault(id, new ArrayList<>());
            emails.add(entry.getKey());
            idToEmails.put(id,emails);
        }
        //經過上面的步驟,已經做到了id和郵箱集合對應起來,接下來把使用者名稱對應起來就可以了
        List<List<String>> res = new ArrayList<>();
        for(Map.Entry<Integer, List<String>> entry : idToEmails.entrySet()){
            List<String> emails = entry.getValue();
            Collections.sort(emails);
            List<String> tmp = new ArrayList<>();
            tmp.add(accounts.get(entry.getKey()).get(0));//先新增使用者名稱
            tmp.addAll(emails);
            res.add(tmp);
        }
        return res;
    }
}

class UnionFind {
    int[] parent;

    public UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    public void union(int index1, int index2) {
        parent[find(index2)] = find(index1);
    }

    public int find(int index) {
        if (parent[index] != index) {
            parent[index] = find(parent[index]);
        }
        return parent[index];
    }
}

鄙人認為,首先將郵箱相同的(此時利用HashMap儲存Id+Email,檢視是否含有郵箱,從而看是否有相同)id並起來(用到並查集,使得相同的郵箱的父點一樣),然後將父節點Id與對應的郵箱集結合起來,再將使用者名稱與之對應。

其中,由於Map中存放的元素均為鍵值對,故每一個鍵值對必然存在一個對映關係。
Map中採用Entry內部類來表示一個對映項,對映項包含Key和Value
Map.Entry裡面包含getKey()和getValue()方法
Set<Entry<T,V>> entrySet()
該方法返回值就是這個map中各個鍵值對對映關係的集合。