721. 賬戶合併+並查集
阿新 • • 發佈:2021-01-19
技術標籤: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對親戚關係。
接下來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中各個鍵值對對映關係的集合。