Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cc36e27

Browse files
✨feat: add 1210、1786、1797、2335、456、629、648
1 parent ff0bb30 commit cc36e27

8 files changed

+587
-36
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1210. 穿过迷宫的最少移动次数]()** ,难度为 **困难**
4+
5+
Tag : 「BFS」
6+
7+
8+
9+
你还记得那条风靡全球的贪吃蛇吗?
10+
11+
我们在一个 `n*n` 的网格上构建了新的迷宫地图,蛇的长度为 `2`,也就是说它会占去两个单元格。蛇会从左上角(`(0, 0)``(0, 1)`)开始移动。我们用 `0` 表示空单元格,用 `1` 表示障碍物。蛇需要移动到迷宫的右下角(`(n-1, n-2)``(n-1, n-1)`)。
12+
13+
每次移动,蛇可以这样走:
14+
15+
* 如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
16+
* 如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
17+
* 如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 `90` 度。蛇从(`(r, c)``(r, c+1)`)移动到 (`(r, c)``(r+1, c)`)。
18+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image-2.png)
19+
* 如果它处于竖直状态并且其右面的两个单元都是空的,就逆时针旋转 `90` 度。蛇从(`(r, c)``(r+1, c)`)移动到(`(r, c)``(r, c+1)`)。
20+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image-1.png)
21+
22+
返回蛇抵达目的地所需的最少移动次数。
23+
24+
如果无法到达目的地,请返回 `-1`
25+
26+
示例 1:
27+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image.png)
28+
```
29+
输入:grid = [[0,0,0,0,0,1],
30+
[1,1,0,0,1,0],
31+
[0,0,0,0,1,1],
32+
[0,0,1,0,1,0],
33+
[0,1,1,0,0,0],
34+
[0,1,1,0,0,0]]
35+
36+
输出:11
37+
38+
解释:
39+
一种可能的解决方案是 [右, 右, 顺时针旋转, 右, 下, 下, 下, 下, 逆时针旋转, 右, 下]。
40+
```
41+
示例 2:
42+
```
43+
输入:grid = [[0,0,1,1,1,1],
44+
[0,0,0,0,1,1],
45+
[1,1,0,0,0,1],
46+
[1,1,1,0,0,1],
47+
[1,1,1,0,0,1],
48+
[1,1,1,0,0,0]]
49+
50+
输出:9
51+
```
52+
53+
提示:
54+
* 2ドル <= n <= 100$
55+
* 0ドル <= grid[i][j] <= 1$
56+
* 蛇保证从空单元格开始出发。
57+
58+
---
59+
60+
### BFS
61+
62+
题目要我们求从特定起点到特定终点的最少步数,由于我们蛇的长度固定为 2ドル,ドル因此我们可用三元组 $(x, y, cd)$ 来代表蛇的实际位置。其中 $(x, y)$ 代表蛇尾位置,$cd$ 代表当前蛇的方向状态,0ドル$ 代表水平状态,1ドル$ 代表竖直状态。
63+
64+
蛇尾加上方向状态可确定其蛇头位置 :`tx = cd == 0 ? nx : nx + 1``ty = cd == 0 ? ny + 1 : ny`
65+
66+
对四种移动规则所导致三元组变化进行分情况讨论:
67+
68+
1. 往右移动:对于蛇尾而言,只有维度 $y$ 进行加一,其余维度不变。三元组变化总结为 $(0, 1, 0)$
69+
2. 往下移动:对于蛇尾而言,只有维度 $x$ 进行加一,其余维度不变。三元组变化总结为 $(1, 0, 0)$
70+
3. 旋转:对于蛇尾,只有 $cd$ 维度对进行翻转,其余维度不变。三元组变化总结定为 $(0, 0, 1)$
71+
72+
综上,所有移动规则可总结为 `int[][] dirs = new int[][]{{1,0,0},{0,1,0},{0,0,1}}`
73+
74+
在进行 `BFS` 时,通过遍历 `dirs` 来得到新的三元组:原位置 `(x, y, cd)` 转换到新位置 `(x + dir[0], y + dir[1], cd ^ dir[2])`
75+
76+
在得到新蛇尾位置 $(nx, ny)$ 之后,计算新蛇头的位置 $(tx, ty)$。需要确保整条蛇没有越界,没有碰到障碍物,并且旋转转移时,额外检查 $(x + 1, y + 1)$ 位置是否合法。
77+
78+
79+
代码:
80+
```Java
81+
class Solution {
82+
int[][] dirs = new int[][]{{1,0,0},{0,1,0},{0,0,1}};
83+
public int minimumMoves(int[][] g) {
84+
int n = g.length;
85+
Deque<int[]> d = new ArrayDeque<>();
86+
d.addLast(new int[]{0,0,0,0});
87+
boolean[][][] vis = new boolean[n][n][2];
88+
vis[0][0][0] = true;
89+
while (!d.isEmpty()) {
90+
int[] info = d.pollFirst();
91+
int x = info[0], y = info[1], cd = info[2], step = info[3];
92+
for (int[] dir : dirs) {
93+
int nx = x + dir[0], ny = y + dir[1], nd = cd ^ dir[2]; // 新蛇尾位置和方向
94+
int tx = nd == 0 ? nx : nx + 1, ty = nd == 0 ? ny + 1 : ny; // 新蛇头
95+
if (nx >= n || ny >= n || tx >= n || ty >= n) continue; // 整条蛇不越界
96+
if (g[nx][ny] == 1 || g[tx][ty] == 1) continue; // 没有触及障碍物
97+
if (vis[nx][ny][nd]) continue;
98+
if (cd != nd && g[x + 1][y + 1] == 1) continue; // 旋转时,额外检查多一个位置
99+
if (nx == n - 1 && ny == n - 2 && nd == 0) return step + 1;
100+
d.addLast(new int[]{nx, ny, nd, step + 1});
101+
vis[nx][ny][nd] = true;
102+
}
103+
}
104+
return -1;
105+
}
106+
}
107+
```
108+
* 时间复杂度:$O(n^2)$
109+
* 空间复杂度:$O(n^2 \times C),ドル其中 $C = 2$ 代表蛇可变状态方向
110+
111+
---
112+
113+
### 最后
114+
115+
这是我们「刷穿 LeetCode」系列文章的第 `No.1210` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
116+
117+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
118+
119+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
120+
121+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
122+
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1233. 删除子文件夹]()** ,难度为 **中等**
4+
5+
Tag : 「字典树」
6+
7+
8+
9+
你是一位系统管理员,手里有一份文件夹列表 `folder`,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。
10+
11+
如果文件夹 `folder[i]` 位于另一个文件夹 `folder[j]` 下,那么 `folder[i]` 就是 `folder[j]` 的 子文件夹 。
12+
13+
文件夹的「路径」是由一个或多个按以下格式串联形成的字符串:`'/'` 后跟一个或者多个小写英文字母。
14+
15+
例如,`"/leetcode"``"/leetcode/problems"` 都是有效的路径,而空字符串和 "/" 不是。
16+
17+
示例 1:
18+
```
19+
输入:folder = ["/a","/a/b","/c/d","/c/d/e","/c/f"]
20+
21+
输出:["/a","/c/d","/c/f"]
22+
23+
解释:"/a/b" 是 "/a" 的子文件夹,而 "/c/d/e" 是 "/c/d" 的子文件夹。
24+
```
25+
示例 2:
26+
```
27+
输入:folder = ["/a","/a/b/c","/a/b/d"]
28+
29+
输出:["/a"]
30+
31+
解释:文件夹 "/a/b/c" 和 "/a/b/d" 都会被删除,因为它们都是 "/a" 的子文件夹。
32+
```
33+
示例 3:
34+
```
35+
输入: folder = ["/a/b/c","/a/b/ca","/a/b/d"]
36+
37+
输出: ["/a/b/c","/a/b/ca","/a/b/d"]
38+
```
39+
40+
提示:
41+
* 1ドル <= folder.length <= 4 \times 10^4$
42+
* 2ドル <= folder[i].length <= 100$
43+
* `folder[i]` 只包含小写字母和 `'/'`
44+
* `folder[i]` 总是以字符 `'/'` 起始
45+
* 每个文件夹名都是 唯一 的
46+
47+
---
48+
49+
### 字典树
50+
51+
一道字典树裸题,不熟悉字典树的同学可以看前置 🧀 : [【设计数据结构】实现 Trie (前缀树)](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247488490&idx=1&sn=db2998cb0e5f08684ee1b6009b974089)
52+
53+
定义类 `Trie` 代表字典树节点,对应物理含义为某个文件夹。该节点含有属性 `s``isEnd``stries`,分别代表「当前节点所代表文件夹名」、「是否为当前路径的最终文件夹」以及「当前文件夹下的子文件夹集合」。
54+
55+
并且由于每个文件夹名的长度不定,我们使用 `Map<String, Trie>` 结构来构建 `stries`
56+
57+
起始先将所有的 $folder[i]$ 加入字典树,随后查询每个 $folder[i]$ 是否为子文件夹,将所有非子文件夹加入答案。
58+
59+
代码:
60+
```Java
61+
class Solution {
62+
class Trie {
63+
String s;
64+
boolean isEnd = false;
65+
Map<String, Trie> stries = new HashMap<>();
66+
Trie (String _s) {
67+
s = _s;
68+
}
69+
}
70+
void add(String f) {
71+
String[] ss = f.split("/");
72+
Trie p = root;
73+
for (int i = 1; i < ss.length; i++) {
74+
String s = ss[i];
75+
if (!p.stries.containsKey(s)) p.stries.put(s, new Trie(s));
76+
p = p.stries.get(s);
77+
}
78+
p.isEnd = true;
79+
}
80+
boolean isSubFolder(String f) {
81+
String[] ss = f.split("/");
82+
Trie p = root;
83+
for (int i = 1; i < ss.length - 1; i++) {
84+
String s = ss[i];
85+
if (p.stries.get(s).isEnd) return true;
86+
p = p.stries.get(s);
87+
}
88+
return false;
89+
}
90+
Trie root = new Trie("");
91+
public List<String> removeSubfolders(String[] folder) {
92+
for (String f : folder) add(f);
93+
List<String> ans = new ArrayList<>();
94+
for (String f : folder) {
95+
if (!isSubFolder(f)) ans.add(f);
96+
}
97+
return ans;
98+
}
99+
}
100+
```
101+
* 时间复杂度:$O(\sum_{i = 0}^{n - 1} folder[i].length)$
102+
* 空间复杂度:$O(\sum_{i = 0}^{n - 1} folder[i].length)$
103+
104+
---
105+
106+
### 最后
107+
108+
这是我们「刷穿 LeetCode」系列文章的第 `No.1223` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
109+
110+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
111+
112+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
113+
114+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
115+

‎LeetCode/1781-1790/1786. 从第一个节点出发到最后一个节点的受限路径数(中等).md‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ Tag : 「最短路」、「线性 DP」
77

88

99

10-
现有一个加权无向连通图。给你一个正整数 n ,表示图中有 n 个节点,并按从 1n 给节点编号;另给你一个数组 edges,其中每个 edges[i] = [ui, vi, weighti] 表示存在一条位于节点 uivi 之间的边,这条边的权重为 weighti
10+
现有一个加权无向连通图。给你一个正整数 `n` ,表示图中有 `n` 个节点,并按从 `1``n` 给节点编号;另给你一个数组 `edges`,其中每个 $edges[i] = [u_{i}, v_{i}, weight_{i}]$ 表示存在一条位于节点 $u_{i}$$v_{i}$ 之间的边,这条边的权重为 $weight_{i}$
1111

12-
从节点 start 出发到节点 end 的路径是一个形如 [z0, z1, z2, ..., zk] 的节点序列,满足 z0 = start 、zk = end 且在所有符合 0 <= i <= k-1 的节点 zizi+1 之间存在一条边。
12+
从节点 start 出发到节点 `end` 的路径是一个形如 $[z_{0}, z_{1}, z_{2}, ..., z_{k}]$ 的节点序列,满足 $z_{0} = start$ 、$z_{k} = end$ 且在所有符合 $0 <= i <= k-1$ 的节点 $z_{i}$$z_{i}+1$ 之间存在一条边。
1313

14-
路径的距离定义为这条路径上所有边的权重总和。用 distanceToLastNode(x) 表示节点 nx 之间路径的最短距离。受限路径 为满足 distanceToLastNode(zi) > distanceToLastNode(zi+1) 的一条路径,其中 0 <= i <= k-1 。
14+
路径的距离定义为这条路径上所有边的权重总和。用 `distanceToLastNode(x)` 表示节点 `n``x` 之间路径的最短距离。受限路径 为满足 $distanceToLastNode(z_{i}) > distanceToLastNode(z_{i}+1)$ 的一条路径,其中 $0 <= i <= k-1$
1515

16-
返回从节点 1 出发到节点 n 的 受限路径数 。由于数字可能很大,请返回对 109 + 7 取余 的结果。
16+
返回从节点 `1` 出发到节点 `n` 的 受限路径数 。由于数字可能很大,请返回对 10ドル^9 + 7$ 取余 的结果。
1717

1818
示例 1:
1919

@@ -38,48 +38,48 @@ Tag : 「最短路」、「线性 DP」
3838
```
3939

4040
提示:
41-
* 1 <= n <= 2 * $10^4$
42-
* n - 1 <= edges.length <= 4 * $10^4$
43-
* edges[i].length == 3
44-
* 1 <= ui, vi <= n
45-
* ui != vi
46-
* 1 <= weighti <= $10^5$
41+
* $1 <= n <= 2 \times 10^4$
42+
* $n - 1 <= edges.length <= 4 \times 10^4$
43+
* $edges[i].length == 3$
44+
* $1 <= ui, vi <= n$
45+
* $u_i != v_i$
46+
* $1 <= weighti <= 10^5$
4747
* 任意两个节点之间至多存在一条边
4848
* 任意两个节点之间至少存在一条路径
4949

5050
---
5151

5252
### 堆优化 Dijkstra + 动态规划
5353

54-
n 为点的数量,m 为边的数量。
54+
`n` 为点的数量,`m` 为边的数量。
5555

56-
为了方便理解,我们将第 n 个点称为「起点」,第 1 个点称为「结尾」。
56+
为了方便理解,我们将第 `n` 个点称为「起点」,第 `1` 个点称为「结尾」。
5757

5858
按照题意,我们需要先求每个点到结尾的「最短路」,求最短路的算法有很多,通常根据「有无负权边」& 「稠密图还是稀疏图」进行选择。
5959

60-
该题只有正权变,而且"边"和"点"的数量在一个数量级上,属于稀疏图。
60+
该题只有正权变,而且"边"和"点"的数量在一个数量级上,属于稀疏图。
6161

6262
因此我们可以采用「最短路」算法:堆优化的 Dijkstra,复杂度为 $O(m\log{n})$。
6363

64-
*PS. 通常会优先选择 SPFA,SPFA 通常情况下复杂度为 $O(m),ドル但最坏情况下复杂度为 $O(n*m)$。从数据上来说 SPFA 也会超,而且本题还结合了 DP,因此可能会卡掉图论部分的 SPFA。出于这些考虑,我直接使用堆优化 Dijkstra。*
64+
> PS. 通常会优先选择 SPFA,SPFA 通常情况下复杂度为 $O(m),ドル但最坏情况下复杂度为 $O(n \times m)$。从数据上来说 SPFA 也会超,而且本题还结合了 DP,因此可能会卡掉图论部分的 SPFA。出于这些考虑,我直接使用堆优化 Dijkstra。
6565
6666
当我们求得了每个点到结尾的「最短路」之后,接下来我们需要求得从「起点」到「结尾」的**受限路径数量**
6767

6868
这显然可以用 DP 来做。
6969

70-
我们定义 f(i) 为从第 i 个点到结尾的受限路径数量,f(1) 就是我们的答案,而 f(n) = 1 是一个显而易见的起始条件。
70+
我们定义 `f(i)` 为从第 `i` 个点到结尾的受限路径数量,`f(1)` 就是我们的答案,而 `f(n) = 1` 是一个显而易见的起始条件。
7171

7272
因为题目的**受限路径数**的定义,我们需要找的路径所包含的点,必须是其距离结尾的最短路越来越近的。
7373

74-
举个🌰,对于示例 1,其中一条符合要求的路径为 1 --> 2 --> 3 --> 5。
74+
举个🌰,对于示例 `1`,其中一条符合要求的路径为 `1 --> 2 --> 3 --> 5`
7575

7676
这条路径的搜索过程可以看做,从结尾(第 5 个点)出发,逆着走,每次选择一个点(例如 a)之后,再选择下一个点(例如 b)时就必须**满足最短路距离比上一个点(点 a)要远**,如果最终能选到起点(第一个点),说明统计出一条有效路径。
7777

7878
我们的搜索方式决定了需要先按照最短路距离进行从小到大排序。
7979

80-
**不失一般性,当我们要求 f(i) 的时候,其实找的是 i 点可以到达的点 j,并且 j 点到结尾的最短路要严格小于 i 点到结尾的最短路。**
80+
**不失一般性,当我们要求 `f(i)` 的时候,其实找的是 `i` 点可以到达的点 `j`,并且 `j` 点到结尾的最短路要严格小于 `i` 点到结尾的最短路。**
8181

82-
符合条件的点 j 有很多个,将所有的 f(j) 累加即是 f(i)。
82+
符合条件的点 `j` 有很多个,将所有的 `f(j)` 累加即是 `f(i)`
8383

8484
代码:
8585
```java

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /