Skip to content

Commit cd07bff

Browse files
author
lucifer
committed
feat: 947
1 parent 9090dd0 commit cd07bff

File tree

3 files changed

+312
-42
lines changed

3 files changed

+312
-42
lines changed

README.md

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
简体中文 | [English](./README.en.md)
1717

1818
---
19-
19+
2020
**只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。** [在线阅读](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/)
2121

2222
## :blue_book:电子书
@@ -77,7 +77,6 @@
7777

7878
大家可以用 Github 提供的 [RSS](https://github.com/azl397985856/leetcode/commits.atom) 来订阅我的仓库更新。
7979

80-
8180
## :octocat:仓库介绍
8281

8382
leetcode 题解,记录自己的 leetcode 解题之路。
@@ -123,6 +122,46 @@ leetcode 题解,记录自己的 leetcode 解题之路。
123122

124123
## :door:  传送门
125124

125+
### 数据结构与算法的总结(22 篇)
126+
127+
- [数据结构总览](./thinkings/basic-data-structure.md)
128+
- [链表专题](./thinkings/linked-list.md) 🆕
129+
- [树专题](./thinkings/tree.md) 🆕
130+
- [堆专题(上)](./thinkings/heap.md) 🆕
131+
<!-- - [基础算法](./thinkings/basic-algorithm.md) -->
132+
- [二叉树的遍历](./thinkings/binary-tree-traversal.md)
133+
- [动态规划](./thinkings/dynamic-programming.md)
134+
- [回溯](./thinkings/backtrack.md)
135+
- [哈夫曼编码和游程编码](./thinkings/run-length-encode-and-huffman-encode.md)
136+
- [布隆过滤器](./thinkings/bloom-filter.md)🖊
137+
- [字符串问题](./thinkings/string-problems.md)
138+
- [前缀树](./thinkings/trie.md)🖊
139+
- [《日程安排》专题](https://lucifer.ren/blog/2020/02/03/leetcode-%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%E7%B3%BB%E5%88%97/)
140+
- [《构造二叉树》专题](https://lucifer.ren/blog/2020/02/08/%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%93%E9%A2%98/)
141+
- [《贪婪策略》专题](./thinkings/greedy.md)
142+
- [深度优先遍历](./thinkings/DFS.md)
143+
- [滑动窗口(思路 + 模板)](./thinkings/slide-window.md)
144+
- [位运算](./thinkings/bit.md)
145+
- [设计题](./thinkings/design.md)
146+
- [小岛问题](./thinkings/island.md)🖊
147+
- [最大公约数](./thinkings/GCD.md)
148+
- [并查集](./thinkings/union-find.md)
149+
- [平衡二叉树专题](./thinkings/balanced-tree.md)
150+
- [蓄水池抽样](./thinkings/reservoid-sampling.md) 🆕
151+
- [单调栈](./thinkings/monotone-stack.md) 🆕
152+
153+
### 精选题解(9 篇)
154+
155+
- [字典序列删除](./selected/a-deleted.md)
156+
- [一次搞定前缀和](./selected/atMostK.md)
157+
- [字节跳动的算法面试题是什么难度?](./selected/byte-dance-algo-ex.md)
158+
- [字节跳动的算法面试题是什么难度?(第二弹)](./selected/byte-dance-algo-ex-2017.md)
159+
- [《我是你的妈妈呀》 - 第一期](./selected/mother-01.md)
160+
- [一文带你看懂二叉树的序列化](./selected/serialize.md)
161+
- [穿上衣服我就不认识你了?来聊聊最长上升子序列](./selected/LIS.md)
162+
- [你的衣服我扒了 - 《最长公共子序列》](./selected/LCS.md)
163+
- [一文看懂《最大子序列和问题》](./selected/LSS.md)
164+
126165
### leetcode 经典题目的解析(200 多道)
127166

128167
> 这里仅列举具有**代表性题目**,并不是全部题目
@@ -291,6 +330,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。
291330
- [0911. 在线选举](./problems/911.online-election.md) 🆕
292331
- [0912. 排序数组](./problems/912.sort-an-array.md)
293332
- [0935. 骑士拨号器](./problems/935.knight-dialer.md)
333+
- [0947. 移除最多的同行或同列石头](./problems/947.most-stones-removed-with-same-row-or-column.md) 🆕
294334
- [0978. 最长湍流子数组](./problems/978.longest-turbulent-subarray.md) 🆕
295335
- [0987. 二叉树的垂序遍历](./problems/987.vertical-order-traversal-of-a-binary-tree.md) 91
296336
- [1011. 在 D 天内送达包裹的能力](./problems/1011.capacity-to-ship-packages-within-d-days.md)
@@ -382,46 +422,6 @@ leetcode 题解,记录自己的 leetcode 解题之路。
382422
- [1449. 数位成本和为目标值的最大数字](./problems/1449.form-largest-integer-with-digits-that-add-up-to-target.md) 🆕
383423
- [5640. 与数组中元素的最大异或值](./problems/5640.maximum-xor-with-an-element-from-array.md) 🆕
384424

385-
### 数据结构与算法的总结(22 篇)
386-
387-
- [数据结构总览](./thinkings/basic-data-structure.md)
388-
- [链表专题](./thinkings/linked-list.md) 🆕
389-
- [树专题](./thinkings/tree.md) 🆕
390-
- [堆专题(上)](./thinkings/heap.md) 🆕
391-
<!-- - [基础算法](./thinkings/basic-algorithm.md) -->
392-
- [二叉树的遍历](./thinkings/binary-tree-traversal.md)
393-
- [动态规划](./thinkings/dynamic-programming.md)
394-
- [回溯](./thinkings/backtrack.md)
395-
- [哈夫曼编码和游程编码](./thinkings/run-length-encode-and-huffman-encode.md)
396-
- [布隆过滤器](./thinkings/bloom-filter.md)🖊
397-
- [字符串问题](./thinkings/string-problems.md)
398-
- [前缀树](./thinkings/trie.md)🖊
399-
- [《日程安排》专题](https://lucifer.ren/blog/2020/02/03/leetcode-%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%E7%B3%BB%E5%88%97/)
400-
- [《构造二叉树》专题](https://lucifer.ren/blog/2020/02/08/%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%93%E9%A2%98/)
401-
- [《贪婪策略》专题](./thinkings/greedy.md)
402-
- [深度优先遍历](./thinkings/DFS.md)
403-
- [滑动窗口(思路 + 模板)](./thinkings/slide-window.md)
404-
- [位运算](./thinkings/bit.md)
405-
- [设计题](./thinkings/design.md)
406-
- [小岛问题](./thinkings/island.md)🖊
407-
- [最大公约数](./thinkings/GCD.md)
408-
- [并查集](./thinkings/union-find.md)
409-
- [平衡二叉树专题](./thinkings/balanced-tree.md)
410-
- [蓄水池抽样](./thinkings/reservoid-sampling.md) 🆕
411-
- [单调栈](./thinkings/monotone-stack.md) 🆕
412-
413-
### 精选题解(9 篇)
414-
415-
- [字典序列删除](./selected/a-deleted.md)
416-
- [一次搞定前缀和](./selected/atMostK.md)
417-
- [字节跳动的算法面试题是什么难度?](./selected/byte-dance-algo-ex.md)
418-
- [字节跳动的算法面试题是什么难度?(第二弹)](./selected/byte-dance-algo-ex-2017.md)
419-
- [《我是你的妈妈呀》 - 第一期](./selected/mother-01.md)
420-
- [一文带你看懂二叉树的序列化](./selected/serialize.md)
421-
- [穿上衣服我就不认识你了?来聊聊最长上升子序列](./selected/LIS.md)
422-
- [你的衣服我扒了 - 《最长公共子序列》](./selected/LCS.md)
423-
- [一文看懂《最大子序列和问题》](./selected/LSS.md)
424-
425425
## :trident: &nbsp;anki 卡片
426426

427427
Anki 主要分为两个部分:一部分是关键点到题目的映射,另一部分是题目到思路,关键点,代码的映射。

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
* [0911. 在线选举](../problems/911.online-election.md) 🆕
190190
* [0912. 排序数组](problems/912.sort-an-array.md)
191191
* [0935. 骑士拨号器](problems/935.knight-dialer.md)
192+
* [0947. 移除最多的同行或同列石头](./problems/947.most-stones-removed-with-same-row-or-column.md) 🆕
192193
* [0978. 最长湍流子数组](problems/978.longest-turbulent-subarray.md)
193194
* [0987. 二叉树的垂序遍历](problems/987.vertical-order-traversal-of-a-binary-tree.md) 91
194195
* [1011. 在 D 天内送达包裹的能力](problems/1011.capacity-to-ship-packages-within-d-days.md)
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
## 题目地址 (947. 移除最多的同行或同列石头)
2+
3+
https://leetcode-cn.com/problems/most-stones-removed-with-same-row-or-column/
4+
5+
## 题目描述
6+
7+
```
8+
n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。
9+
10+
如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。
11+
12+
给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。
13+
14+
 
15+
16+
示例 1:
17+
18+
输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
19+
输出:5
20+
解释:一种移除 5 块石头的方法如下所示:
21+
1. 移除石头 [2,2] ,因为它和 [2,1] 同行。
22+
2. 移除石头 [2,1] ,因为它和 [0,1] 同列。
23+
3. 移除石头 [1,2] ,因为它和 [1,0] 同行。
24+
4. 移除石头 [1,0] ,因为它和 [0,0] 同列。
25+
5. 移除石头 [0,1] ,因为它和 [0,0] 同行。
26+
石头 [0,0] 不能移除,因为它没有与另一块石头同行/列。
27+
示例 2:
28+
29+
输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
30+
输出:3
31+
解释:一种移除 3 块石头的方法如下所示:
32+
1. 移除石头 [2,2] ,因为它和 [2,0] 同行。
33+
2. 移除石头 [2,0] ,因为它和 [0,0] 同列。
34+
3. 移除石头 [0,2] ,因为它和 [0,0] 同行。
35+
石头 [0,0] 和 [1,1] 不能移除,因为它们没有与另一块石头同行/列。
36+
示例 3:
37+
38+
输入:stones = [[0,0]]
39+
输出:0
40+
解释:[0,0] 是平面上唯一一块石头,所以不可以移除它。
41+
 
42+
43+
提示:
44+
45+
1 <= stones.length <= 1000
46+
0 <= xi, yi <= 104
47+
不会有两块石头放在同一个坐标点上
48+
49+
```
50+
51+
## 前置知识
52+
53+
- [并查集](https://github.com/azl397985856/leetcode/blob/master/thinkings/union-find.md)
54+
55+
## 思路
56+
57+
读完题目之后, 看了下数据范围。我猜测可能和动态规划什么的有关,**而且时间复杂度是 $O(n^2)$ 左右**,其中 n 为 stones 的长度。继续看了下示例,然后跟着思考了一下,发现很像是某种联通关系。 类似的题目有很多,题目描述记不太清楚了。大概意思是给你一个二维网格,行和列需要**一起增加**,求最小和什么的。这类题目都是行和列具有某种绑定关系。 于是我想到了使用并查集来做。 后面想了一下,反正就是**求联通区域的个数**,因此使用 DFS 和 BFS 都可以。如果你想使用 DFS 或者 BFS 来解,可以结合我之前写的 [小岛专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/island.md) 练习一下哦。
58+
59+
继续分析下题目。 题目的意思是任意一个石头可以消除和它同行和同列的其他石子。于是我就想象出了下面这样一幅图,其中红色的方块表示石子,方块的连线表示离得最近的可以消除的石子。实际上,一个石子除了可以消除图中线条直接相连的石子,还可以消除邻居的邻居。**这提示我们使用并查集维护这种联通关系**,联通的依据自然就是列或者行一样。
60+
61+
![](https://pic.leetcode-cn.com/1610681616-aINVVn-008eGmZEly1gmo7njo2ksj30o00li75y.jpg)
62+
63+
上面是一个全联通的图。如下是有两个联通域的图。
64+
65+
![](https://pic.leetcode-cn.com/1610681616-tQEKNR-008eGmZEly1gmo7ryay3pj30og0lw0tp.jpg)
66+
67+
有了上面的知识,其实就可以将石子全部建立并查集的联系,并计算联通子图的个数。答案就是 n - 联通子图的个数,其中 n 为 stones 的长度。
68+
69+
核心代码:
70+
71+
```py
72+
n = len(stones)
73+
# 标准并查集模板
74+
uf = UF(n)
75+
# 两个 for 循环作用是将所有石子两两合并
76+
for i in range(n):
77+
for j in range(i + 1, n):
78+
# 如果行或者列相同,将其联通成一个子图
79+
if stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]: uf.union(i, j)
80+
return n - uf.cnt
81+
```
82+
83+
有的人想问,这可行么?即**我可以将一个联通子图的石子移除只剩下一个么?**
84+
85+
答案是肯定的。其实上面我提到了这道题也可使用 DFS 和 BFS 的方式来做。如果你使用 DFS 的方式来做,会发现其实 **DFS 路径的取反就是消除的顺序**,当然消除的顺序不唯一,因为遍历访问联通子图的序列并不唯一。 如果题目要求我们求移除顺序,那我们可以考虑使用 DFS 来做,同时记录路径信息即可。
86+
87+
![](https://pic.leetcode-cn.com/1610681616-WRXGgB-008eGmZEly1gmo7xnhkiqj315a0niq5v.jpg)
88+
89+
使用遍历的方式(BFS 或者 DFS),由于每次访问一个石子都需要使用 visited 来记录访问信息防止环的产生,因此 visited 的逆序也是一个可行的移除顺序。不过这要求你的 visited 的是有序的。实现的方法有很多,有点偏题了,这里就不赘述了。
90+
91+
实际上,上面的并查集代码仍然可以优化。上面的思路是直接将点作为并查集的联通条件。实际上,我们可以将点的横纵坐标分别作为联通条件。即如果横坐标相同的联通到一个子图,纵坐标相同的联通到一个子图。如下图:
92+
93+
![](https://tva1.sinaimg.cn/large/008eGmZEly1gmoaaz1j13j317v0u0teu.jpg)
94+
95+
为了达到这个模板,我们不能再初始化的时候计算联通域数量了,即不能像上面那样 `uf = UF(n)`(此时联通域个数为 n)。因为横坐标,纵坐标分别有多少不重复的我们是不知道的,一种思路是先计算出**横坐标,纵坐标分别有多少不重复的**。这当然可以,还有一种思路是在 find 过程中计算,这样 one pass 即可完成,具体见下方代码区。
96+
97+
由于我们需要**区分横纵坐标**(上面图也可以看出来),因此可**用一种映射**区分二者。由于题目限定了横纵坐标取值为 10000 以内(包含 10000),一种思路就是将 x 或者 y 加上 10001。
98+
99+
核心代码:
100+
101+
```py
102+
n = len(stones)
103+
uf = UF(0)
104+
for i in range(n):
105+
uf.union(stones[i][0] + 10001, stones[i][1])
106+
return n - uf.cnt
107+
```
108+
109+
## 代码
110+
111+
### 并查集
112+
113+
代码支持: Python3
114+
115+
其中 `class UF` 部分是标准的无权并查集模板,我一行代码都没变。关于模板可以去 [并查集](https://github.com/azl397985856/leetcode/blob/master/thinkings/union-find.md) 查看。
116+
117+
```python
118+
class UF:
119+
def __init__(self, M):
120+
self.parent = {}
121+
self.cnt = 0
122+
# 初始化 parent,size 和 cnt
123+
for i in range(M):
124+
self.parent[i] = i
125+
self.cnt += 1
126+
127+
def find(self, x):
128+
if x != self.parent[x]:
129+
self.parent[x] = self.find(self.parent[x])
130+
return self.parent[x]
131+
return x
132+
def union(self, p, q):
133+
if self.connected(p, q): return
134+
leader_p = self.find(p)
135+
leader_q = self.find(q)
136+
self.parent[leader_p] = leader_q
137+
self.cnt -= 1
138+
def connected(self, p, q):
139+
return self.find(p) == self.find(q)
140+
141+
class Solution:
142+
def removeStones(self, stones: List[List[int]]) -> int:
143+
n = len(stones)
144+
uf = UF(n)
145+
for i in range(n):
146+
for j in range(i + 1, n):
147+
if stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]: uf.union(i, j)
148+
return n - uf.cnt
149+
150+
```
151+
152+
**复杂度分析**
153+
154+
令 n 为数组 stones 的长度。
155+
156+
- 时间复杂度:$O(n^2logn)$
157+
- 空间复杂度:$O(n)$
158+
159+
### 优化的并查集
160+
161+
代码支持: Python3
162+
163+
```py
164+
class UF:
165+
def __init__(self, M):
166+
self.parent = {}
167+
self.cnt = 0
168+
169+
def find(self, x):
170+
if x not in self.parent:
171+
self.cnt += 1
172+
self.parent[x] = x
173+
if x != self.parent[x]:
174+
self.parent[x] = self.find(self.parent[x])
175+
return self.parent[x]
176+
return x
177+
def union(self, p, q):
178+
if self.connected(p, q): return
179+
leader_p = self.find(p)
180+
leader_q = self.find(q)
181+
self.parent[leader_p] = leader_q
182+
self.cnt -= 1
183+
def connected(self, p, q):
184+
return self.find(p) == self.find(q)
185+
186+
class Solution:
187+
def removeStones(self, stones: List[List[int]]) -> int:
188+
n = len(stones)
189+
uf = UF(0)
190+
for i in range(n):
191+
uf.union(stones[i][0] + 10001, stones[i][1])
192+
return n - uf.cnt
193+
```
194+
195+
**复杂度分析**
196+
197+
令 n 为数组 stones 的长度。
198+
199+
- 时间复杂度:$O(nlogn)$
200+
- 空间复杂度:$O(n)$
201+
202+
### DFS
203+
204+
代码支持: Java
205+
206+
> by 一位不愿透露姓名的热心网友。
207+
208+
```java
209+
public int removeStones(int[][] stones) {
210+
Set visit = new HashSet();
211+
int count = 0;
212+
int offset = 10000;
213+
HashMap <Integer,List<int []>>map = new HashMap();
214+
215+
// 构造图 每一项是一个节点
216+
for (int i = 0; i < stones.length; i++) {
217+
int [] node = stones[i];
218+
List<int []> list = map.getOrDefault(node[0]-offset,new ArrayList<>());
219+
list.add(node);
220+
map.put(node[0]-offset,list);
221+
222+
List<int []> list1 = map.getOrDefault(node[1],new ArrayList<>());
223+
list1.add(node);
224+
map.put(node[1],list1);
225+
}
226+
// 寻找联通分量
227+
for (int i = 0; i < stones.length; i++) {
228+
int [] node = stones[i];
229+
if (!visit.contains((node))){
230+
visit.add((node));
231+
dfs(node,visit,map);
232+
count++;
233+
}
234+
}
235+
return stones.length-count;
236+
}
237+
238+
// 遍历节点
239+
public void dfs(int[]node, Set set,HashMap <Integer,List<int []>>map){
240+
int offset = 10000;
241+
List<int []> list = map.getOrDefault(node[0]-offset,new ArrayList<>());
242+
for (int i = 0; i < list.size(); i++) {
243+
int[] item = list.get(i);
244+
if (!set.contains((item))){
245+
set.add((item));
246+
dfs(item,set,map);
247+
}
248+
}
249+
List<int []> list2 = map.getOrDefault(node[1],new ArrayList<>());
250+
for (int i = 0; i < list2.size(); i++) {
251+
int[] item = list2.get(i);
252+
if (!set.contains((item))){
253+
set.add((item));
254+
dfs(item,set,map);
255+
}
256+
}
257+
}
258+
```
259+
260+
**复杂度分析**
261+
262+
令 n 为数组 stones 的长度。
263+
264+
- 时间复杂度:建图和遍历图的时间均为 $O(n)$
265+
- 空间复杂度:$O(n)$
266+
267+
力扣的小伙伴的点下我头像的关注按钮,这样就会第一时间收到我的动态啦~
268+
269+
以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。

0 commit comments

Comments
 (0)