Skip to content

Commit b1bfdef

Browse files
committed
一刷721
1 parent 5ba23b8 commit b1bfdef

File tree

9 files changed

+207
-30
lines changed

9 files changed

+207
-30
lines changed

README.adoc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5072,14 +5072,14 @@ TIP: **公众号的微信号是: `jikerizhi`**。__因为众所周知的原因
50725072
//|{doc_base_url}/0720-longest-word-in-dictionary.adoc[题解]
50735073
//|Easy
50745074
//|
5075-
//
5076-
//|{counter:codes}
5077-
//|{leetcode_base_url}/accounts-merge/[721. Accounts Merge^]
5078-
//|{source_base_url}/_0721_AccountsMerge.java[Java]
5079-
//|{doc_base_url}/0721-accounts-merge.adoc[题解]
5080-
//|Medium
5081-
//|
5082-
//
5075+
5076+
|{counter:codes}
5077+
|{leetcode_base_url}/accounts-merge/[721. Accounts Merge^]
5078+
|{source_base_url}/_0721_AccountsMerge.java[Java]
5079+
|{doc_base_url}/0721-accounts-merge.adoc[题解]
5080+
|Medium
5081+
|
5082+
50835083
//|{counter:codes}
50845084
//|{leetcode_base_url}/remove-comments/[722. Remove Comments^]
50855085
//|{source_base_url}/_0722_RemoveComments.java[Java]

docs/0000-18-union-find-set.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ image::images/union-find-2.png[title="并查集合并",alt="并查集合并",{im
1515

1616
image::images/union-find-3.png[title="并查集再合并",alt="并查集再合并",{image_attr}]
1717

18+
TIP: 在合并时,是修改根节点的指针 `parent[ap] = bp`,而不是修改节点的指针 `parent[a] = bp`
19+
1820
并查集除了 `union`,还有一个重要操作是 `connnected(a, b)`。判断方法也很简单,从节点 `a``b` 开始,向上查找,直到两个节点的根节点,判断两个根节点是否相等即可判断两个节点是否已经连接。为了加快这个判断速度,可以对其进行“路径压缩”,直白点说,就是将所有树的节点,都直接指向根节点,这样只需要一步即可到达根节点。路径压缩如图所示:
1921

2022
image::images/union-find-4.png[title="并查集路径压缩",alt="并查集路径压缩",{image_attr}]

docs/0000-21-decrease-and-conquer.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ D瓜哥最早知道减治法是在 https://book.douban.com/subject/26337727/[《
1414
* **减去的规模是可变的。**在减治法的减可变规模(variable-size-decrease)变化形式中,算法在每次迭代时,规模减小的模式都是不同的。
1515
** 计算最大公约数的欧几里得算法是这种情况的一个很好的例子。 stem:[gcd(m, n)=gcd(n,m mod n)]
1616
17+
== 经典题目
18+
19+
. xref:0721-accounts-merge.adoc[721. Accounts Merge] -- https://leetcode.cn/problems/accounts-merge/solutions/2844329/qu-qiao-de-fang-fa-3miao-kan-dong-si-wei-llcx/[721. 账户合并 - 取巧的减治方法,3秒看懂^]
20+
1721
== 参考资料
1822

1923
. https://cloud.tencent.com/developer/article/1532598[【算法学习】减治 · 分治 · 变治^]

docs/0721-accounts-merge.adoc

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,101 @@
11
[#0721-accounts-merge]
2-
= 721. Accounts Merge
2+
= 721. 账户合并
33

4-
{leetcode}/problems/accounts-merge/[LeetCode - Accounts Merge^]
4+
https://leetcode.cn/problems/accounts-merge/[LeetCode - 721. 账户合并 ^]
55

6-
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.
6+
给定一个列表 `accounts`,每个元素 `accounts[i]` 是一个字符串列表,其中第一个元素 `accounts[i][0]` _名称 (name)_,其余元素是 *_emails_* 表示该账户的邮箱地址。
77

8-
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.
8+
现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
99

10-
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.
10+
合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是 *按字符 ASCII 顺序排列* 的邮箱地址。账户本身可以以 *任意顺序* 返回。
1111

12-
*Example 1:*
12+
*示例 1:*
1313

14-
[subs="verbatim,quotes,macros"]
15-
----
16-
*Input:*
17-
accounts = [["John", "[email protected]", "[email protected]"], ["John", "[email protected]"], ["John", "[email protected]", "[email protected]"], ["Mary", "[email protected]"]]
18-
*Output:* [["John", '[email protected]', '[email protected]', '[email protected]'], ["John", "[email protected]"], ["Mary", "[email protected]"]]
19-
*Explanation:*
20-
The first and third John's are the same person as they have the common email "[email protected]".
21-
The second John and Mary are different people as none of their email addresses are used by other accounts.
22-
We could return these lists in any order, for example the answer [['Mary', '[email protected]'], ['John', '[email protected]'],
23-
['John', '[email protected]', '[email protected]', '[email protected]']] would still be accepted.
24-
----
14+
....
15+
输入:accounts = [
16+
17+
["John", "[email protected]"],
18+
19+
["Mary", "[email protected]"]
20+
]
21+
输出:[
22+
23+
["John", "[email protected]"],
24+
["Mary", "[email protected]"]
25+
]
26+
解释:
27+
第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "[email protected]"。
28+
第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
29+
可以以任何顺序返回这些列表,例如答案 [['Mary','[email protected]'],['John','[email protected]'],
30+
['John','[email protected]','[email protected]','[email protected]']] 也是正确的。
31+
....
32+
33+
*示例 2:*
34+
35+
....
36+
输入:accounts = [
37+
38+
39+
40+
41+
42+
]
43+
输出:[
44+
45+
46+
47+
48+
49+
]
50+
....
51+
52+
*提示:*
53+
54+
* `+1 <= accounts.length <= 1000+`
55+
* `+2 <= accounts[i].length <= 10+`
56+
* `+1 <= accounts[i][j].length <= 30+`
57+
* `accounts[i][0]` 由英文字母组成
58+
* `accounts[i][j] (for j > 0)` 是有效的邮箱地址
2559
2660
27-
*Note:*
28-
. The length of `accounts` will be in the range `[1, 1000]`.</li>
29-
. The length of `accounts[i]` will be in the range `[1, 10]`.</li>
30-
. The length of `accounts[i][j]` will be in the range `[1, 30]`.</li>
61+
== 思路分析
3162

63+
并查集。
3264

65+
通过邮箱编号建立连接,而不是通过账户索引建立连接。
66+
67+
image::images/0721-10.png[{image_attr}]
68+
69+
也可以使用深度优先遍历。
70+
71+
image::images/0721-11.png[{image_attr}]
3372

3473
[[src-0721]]
74+
[tabs]
75+
====
76+
一刷::
77+
+
78+
--
3579
[{java_src_attr}]
3680
----
3781
include::{sourcedir}/_0721_AccountsMerge.java[tag=answer]
3882
----
83+
--
84+
85+
// 二刷::
86+
// +
87+
// --
88+
// [{java_src_attr}]
89+
// ----
90+
// include::{sourcedir}/_0721_AccountsMerge_2.java[tag=answer]
91+
// ----
92+
// --
93+
====
94+
95+
96+
== 参考资料
3997

98+
. https://leetcode.cn/problems/accounts-merge/solutions/564305/zhang-hu-he-bing-by-leetcode-solution-3dyq/[721. 账户合并 - 官方题解^]
99+
. https://leetcode.cn/problems/accounts-merge/solutions/2844186/ha-xi-biao-dfspythonjavacgojsrust-by-end-z9nh/[721. 账户合并 - 哈希表 + DFS^]
100+
. https://leetcode.cn/problems/accounts-merge/solutions/564451/tu-jie-yi-ran-shi-bing-cha-ji-by-yexiso-5ncf/[721. 账户合并 - 【图解】依然是并查集^]
101+
. https://leetcode.cn/problems/accounts-merge/solutions/2844329/qu-qiao-de-fang-fa-3miao-kan-dong-si-wei-llcx/[721. 账户合并 - 取巧的减治方法,3秒看懂,思维难度小,复杂度就大,图一乐 :)^]

docs/images/0721-10.png

150 KB
Loading

docs/images/0721-11.png

147 KB
Loading

docs/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,7 @@ include::0718-maximum-length-of-repeated-subarray.adoc[leveloffset=+1]
15241524

15251525
// include::0720-longest-word-in-dictionary.adoc[leveloffset=+1]
15261526

1527-
// include::0721-accounts-merge.adoc[leveloffset=+1]
1527+
include::0721-accounts-merge.adoc[leveloffset=+1]
15281528

15291529
// include::0722-remove-comments.adoc[leveloffset=+1]
15301530

logbook/202503.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,11 @@ endif::[]
418418
|{doc_base_url}/0131-palindrome-partitioning.adoc[题解]
419419
|✅ 回溯
420420

421+
|{counter:codes2503}
422+
|{leetcode_base_url}/accounts-merge/[721. 账户合并^]
423+
|{doc_base_url}/0721-accounts-merge.adoc[题解]
424+
|❌ 并查集。通过邮箱编号建立连接,而不是通过账户索引建立连接。
425+
421426
|===
422427

423428
截止目前,本轮练习一共完成 {codes2503} 道题。
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.diguage.algo.leetcode;
2+
3+
import java.util.*;
4+
5+
public class _0721_AccountsMerge {
6+
// tag::answer[]
7+
8+
/**
9+
* @author D瓜哥 · https://www.diguage.com
10+
* @since 2025-04-21 21:49:58
11+
*/
12+
public List<List<String>> accountsMerge(List<List<String>> accounts) {
13+
Map<String, Integer> emailToNoMap = new HashMap<>();
14+
Map<String, String> emailToNameMap = new HashMap<>();
15+
int emailCount = 0;
16+
for (List<String> account : accounts) {
17+
String name = account.getFirst();
18+
for (int i = 1; i < account.size(); i++) {
19+
String email = account.get(i);
20+
if (!emailToNoMap.containsKey(email)) {
21+
emailToNoMap.put(email, emailCount++);
22+
emailToNameMap.put(email, name);
23+
}
24+
}
25+
}
26+
UnionFind uf = new UnionFind(emailCount);
27+
for (List<String> account : accounts) {
28+
String firstEmail = account.get(1);
29+
Integer ai = emailToNoMap.get(firstEmail);
30+
for (int j = 2; j < account.size(); j++) {
31+
Integer bi = emailToNoMap.get(account.get(j));
32+
uf.union(ai, bi);
33+
}
34+
}
35+
Map<Integer, List<String>> noToEmailMap = new HashMap<>();
36+
emailToNoMap.forEach((email, no) -> {
37+
int id = uf.find(no);
38+
List<String> emails = noToEmailMap.computeIfAbsent(id, v -> new ArrayList<>());
39+
emails.add(email);
40+
});
41+
List<List<String>> result = new ArrayList<>();
42+
for (List<String> emails : noToEmailMap.values()) {
43+
List<String> account = new ArrayList<>(emails.size() + 1);
44+
account.add(emailToNameMap.get(emails.getFirst()));
45+
Collections.sort(emails);
46+
account.addAll(emails);
47+
result.add(account);
48+
}
49+
return result;
50+
}
51+
52+
private static class UnionFind {
53+
int[] parent;
54+
55+
public UnionFind(int size) {
56+
this.parent = new int[size];
57+
for (int i = 0; i < size; i++) {
58+
parent[i] = i;
59+
}
60+
}
61+
62+
public void union(int a, int b) {
63+
int ap = find(a);
64+
int bp = find(b);
65+
if (ap == bp) {
66+
return;
67+
}
68+
if (ap < bp) {
69+
parent[bp] = ap;
70+
} else {
71+
parent[ap] = bp;
72+
}
73+
}
74+
75+
public int find(int a) {
76+
int ap = parent[a];
77+
List<Integer> path = new ArrayList<>();
78+
path.add(a);
79+
while (ap != parent[ap]) {
80+
path.add(ap);
81+
ap = parent[ap];
82+
}
83+
for (Integer i : path) {
84+
parent[i] = ap;
85+
}
86+
return ap;
87+
}
88+
}
89+
// end::answer[]
90+
public static void main(String[] args) {
91+
new _0721_AccountsMerge().accountsMerge(new ArrayList<>(List.of(
92+
new ArrayList<>(List.of("David", "[email protected]", "[email protected]", "[email protected]")),
93+
new ArrayList<>(List.of("David", "[email protected]", "[email protected]", "[email protected]")),
94+
new ArrayList<>(List.of("David", "[email protected]", "[email protected]", "[email protected]")),
95+
new ArrayList<>(List.of("David", "[email protected]", "[email protected]", "[email protected]")),
96+
new ArrayList<>(List.of("David", "[email protected]", "[email protected]", "[email protected]")))));
97+
// new _0721_AccountsMerge().accountsMerge(new ArrayList<>(List.of(
98+
// new ArrayList<>(List.of("David", "[email protected]", "[email protected]")),
99+
// new ArrayList<>(List.of("David", "[email protected]", "[email protected]")),
100+
// new ArrayList<>(List.of("David", "[email protected]", "[email protected]")),
101+
// new ArrayList<>(List.of("David", "[email protected]", "[email protected]")),
102+
// new ArrayList<>(List.of("David", "[email protected]", "[email protected]")))));
103+
}
104+
}

0 commit comments

Comments
 (0)