Given a list accounts
, each element accounts[i]
is a list of strings, where the first element accounts[i][0]
is a name, and the rest of the elements are emails representing emails of the account.
Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some email that is common to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.
After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails in sorted order. The accounts themselves can be returned in any order.
Example 1:
Input: accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]] Output: [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'], ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]] Explanation: The first and third John's are the same person as they have the common email "johnsmith@mail.com". The second John and Mary are different people as none of their email addresses are used by other accounts. We could return these lists in any order, for example the answer [['Mary', 'mary@mail.com'], ['John', 'johnnybravo@mail.com'], ['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com']] would still be accepted.
Note:
- The length of
accounts
will be in the range[1, 1000]
. - The length of
accounts[i]
will be in the range[1, 10]
. - The length of
accounts[i][j]
will be in the range[1, 30]
.
這道題給了一堆人名和郵箱,一個名字可能有多個郵箱,但是一個郵箱只屬於一個人,讓我們把同一個人的郵箱都合並到一起,名字相同不一定是同一個人,只有當兩個名字有相同的郵箱,才能確定是同一個人,題目中的例子很好說明了這個問題,輸入有三個 John,最后合並之后就只有兩個了。這道題博主最開始嘗試使用貪婪算法來做,結果發現對於下面這個例子不適用:
["John", "a@gmail.com", "b@gmail.com"]
["John", "c@gmail.com", "d@gmail.com"]
["John", "a@gmail.com", "c@gmail.com"]
可以看到其實這三個 John 是同一個人,但是貪婪算法遍歷完前兩個 John,還是認為其是兩個不同的人,當遍歷第三個 John 時,就直接加到第一個 John 中了,而沒有同時把第二個 John 加進來,也可能博主寫的是假的貪婪算法,反正不管了,還是參考大神們的解法吧。這個歸組類的問題,最典型的就是島嶼問題(例如 Number of Islands II),很適合使用 Union Find 來做,LeetCode 中有很多道可以使用這個方法來做的題,比如 Friend Circles,Graph Valid Tree,Number of Connected Components in an Undirected Graph,和 Redundant Connection 等等。都是要用一個 root 數組,每個點開始初始化為不同的值,如果兩個點屬於相同的組,就將其中一個點的 root 值賦值為另一個點的位置,這樣只要是相同組里的兩點,通過 find 函數得到相同的值。在這里,由於郵件是字符串不是數字,所以 root 可以用 HashMap 來代替,還需要一個 HashMap 映射owner,建立每個郵箱和其所有者姓名之前的映射,另外用一個 HashMap 來建立用戶和其所有的郵箱之間的映射,也就是合並后的結果。
首先遍歷每個賬戶和其中的所有郵箱,先將每個郵箱的 root 映射為其自身,然后將 owner 賦值為用戶名。然后開始另一個循環,遍歷每一個賬號,首先對帳號的第一個郵箱調用 find 函數,得到其父串p,然后遍歷之后的郵箱,對每個遍歷到的郵箱先調用 find 函數,將其父串的 root 值賦值為p,這樣做相當於將相同賬號內的所有郵箱都鏈接起來了。接下來要做的就是再次遍歷每個賬戶內的所有郵箱,先對該郵箱調用 find 函數,找到父串,然后將該郵箱加入該父串映射的集合匯總,這樣就就完成了合並。最后只需要將集合轉為字符串數組,加入結果 res 中,通過 owner 映射找到父串的用戶名,加入字符串數組的首位置,參見代碼如下:
解法一:
class Solution { public: vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) { vector<vector<string>> res; unordered_map<string, string> root; unordered_map<string, string> owner; unordered_map<string, set<string>> m; for (auto account : accounts) { for (int i = 1; i < account.size(); ++i) { root[account[i]] = account[i]; owner[account[i]] = account[0]; } } for (auto account : accounts) { string p = find(account[1], root); for (int i = 2; i < account.size(); ++i) { root[find(account[i], root)] = p; } } for (auto account : accounts) { for (int i = 1; i < account.size(); ++i) { m[find(account[i], root)].insert(account[i]); } } for (auto a : m) { vector<string> v(a.second.begin(), a.second.end()); v.insert(v.begin(), owner[a.first]); res.push_back(v); } return res; } string find(string s, unordered_map<string, string>& root) { return root[s] == s ? s : find(root[s], root); } };
下面這種方法是使用 BFS 來解的,建立了每個郵箱和其所有出現的賬戶數組之間的映射,比如還是這個例子:
["John", "a@gmail.com", "b@gmail.com"]
["John", "c@gmail.com", "d@gmail.com"]
["John", "a@gmail.com", "c@gmail.com"]
那么建立的映射就是:
"a@gmail.com" -> [0, 2]
"b@gmail.com" -> [0]
"c@gmail.com" -> [1, 2]
"d@gmail.com" -> [1]
然后還需要一個 visited 數組,來標記某個賬戶是否已經被遍歷過,0表示為未訪問,1表示已訪問。在建立好 HashMap 之后,遍歷所有的賬戶,如果賬戶未被訪問過,將其加入隊列 queue,新建一個 TreeSet,此時進行隊列不為空的 while 循環,取出隊首賬戶,將該該賬戶標記已訪問1,此時將該賬戶的所有郵箱取出來放入數組 mails 中,然后遍歷 mails 中的每一個郵箱,將遍歷到的郵箱加入 TreeSet 中,根據映射來找到該郵箱所屬的所有賬戶,如果該賬戶未訪問,則加入隊列中並標記已訪問。當 while 循環結束后,當前賬戶的所有合並后的郵箱都保存在 TreeSet 中,將其轉為字符串數組,並且加上用戶名在首位置,最后加入結果 res 中即可,參見代碼如下:
解法二:
class Solution { public: vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) { vector<vector<string>> res; int n = accounts.size(); unordered_map<string, vector<int>> m; vector<int> visited(n, 0); for (int i = 0; i < n; ++i) { for (int j = 1; j < accounts[i].size(); ++j) { m[accounts[i][j]].push_back(i); } } for (int i = 0; i < n; ++i) { if (visited[i] != 0) continue; queue<int> q{{i}}; set<string> s; while (!q.empty()) { int t = q.front(); q.pop(); visited[t] = 1; vector<string> mails(accounts[t].begin() + 1, accounts[t].end()); for (string mail : mails) { s.insert(mail); for (int user : m[mail]) { if (visited[user] != 0) continue; q.push(user); visited[user] = 1; } } } vector<string> out(s.begin(), s.end()); out.insert(out.begin(), accounts[i][0]); res.push_back(out); } return res; } };
類似題目:
參考資料:
https://leetcode.com/problems/accounts-merge/
https://leetcode.com/problems/accounts-merge/discuss/109189/simple-c-sol
https://leetcode.com/problems/accounts-merge/discuss/109157/javac-union-find
https://leetcode.com/problems/accounts-merge/discuss/109158/java-solution-build-graph-dfs-search