Skip to content

Commit 1e65b01

Browse files
authored
Merge branch 'youngyangyang04:master' into zhicheng-lee-patch-5
2 parents 89ac882 + 697b8ac commit 1e65b01

25 files changed

+1199
-173
lines changed

problems/0053.最大子序和(动态规划).md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,20 @@ Java:
120120
return res;
121121
}
122122
```
123+
```Java
124+
//因为dp[i]的递推公式只与前一个值有关,所以可以用一个变量代替dp数组,空间复杂度为O(1)
125+
class Solution {
126+
public int maxSubArray(int[] nums) {
127+
int res = nums[0];
128+
int pre = nums[0];
129+
for(int i = 1; i < nums.length; i++) {
130+
pre = Math.max(pre + nums[i], nums[i]);
131+
res = Math.max(res, pre);
132+
}
133+
return res;
134+
}
135+
}
136+
```
123137

124138
Python:
125139
```python

problems/0101.对称二叉树.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
# 思路
1717

18+
《代码随想录》算法视频公开课:[同时操作两个二叉树 | LeetCode:101. 对称二叉树](https://www.bilibili.com/video/BV1ue4y1Y7Mf),相信结合视频在看本篇题解,更有助于大家对本题的理解。
19+
1820
**首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!**
1921

2022
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。

problems/0102.二叉树的层序遍历.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
# 二叉树层序遍历登场!
88

9+
《代码随想录》算法视频公开课:[讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode:102.二叉树的层序遍历](https://www.bilibili.com/video/BV1GY4y1u7b2),相信结合视频在看本篇题解,更有助于大家对本题的理解。
10+
11+
912
学会二叉树的层序遍历,可以一口气打完以下十题:
1013

1114
* 102.二叉树的层序遍历

problems/0104.二叉树的最大深度.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
3333

34+
* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
35+
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
36+
3437
**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
3538

3639
这一点其实是很多同学没有想清楚的,很多题解同样没有讲清楚。

problems/0111.二叉树的最小深度.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,16 @@
3131

3232
直觉上好像和求最大深度差不多,其实还是差不少的。
3333

34-
遍历顺序上依然是后序遍历(因为要比较递归返回之后的结果),但在处理中间节点的逻辑上,最大深度很容易理解,最小深度可有一个误区,如图:
34+
本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
35+
36+
* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
37+
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
38+
39+
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,这不过这个最小距离 也同样是最小深度。
40+
41+
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。
42+
43+
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
3544

3645
![111.二叉树的最小深度](https://img-blog.csdnimg.cn/20210203155800503.png)
3746

@@ -150,6 +159,37 @@ public:
150159
151160
**精简之后的代码根本看不出是哪种遍历方式,所以依然还要强调一波:如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。**
152161
162+
前序遍历的方式:
163+
164+
```CPP
165+
class Solution {
166+
private:
167+
int result;
168+
void getdepth(TreeNode* node, int depth) {
169+
if (node->left == NULL && node->right == NULL) {
170+
result = min(depth, result);
171+
return;
172+
}
173+
// 中 只不过中没有处理的逻辑
174+
if (node->left) { // 左
175+
getdepth(node->left, depth + 1);
176+
}
177+
if (node->right) { // 右
178+
getdepth(node->right, depth + 1);
179+
}
180+
return ;
181+
}
182+
183+
public:
184+
int minDepth(TreeNode* root) {
185+
if (root == NULL) return 0;
186+
result = INT_MAX;
187+
getdepth(root, 1);
188+
return result;
189+
}
190+
};
191+
```
192+
153193
## 迭代法
154194

155195
相对于[104.二叉树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html),本题还可以使用层序遍历的方式来解决,思路是一样的。

problems/0127.单词接龙.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@
4242
* 图中的线是如何连在一起的
4343
* 起点和终点的最短路径长度
4444

45-
4645
首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。
4746

4847
然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
4948

50-
本题如果用深搜,会非常麻烦
49+
本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路。 而广搜只要达到终点,一定是最短路
5150

5251
另外需要有一个注意点:
5352

@@ -96,6 +95,8 @@ public:
9695
};
9796
```
9897

98+
当然本题也可以用双向BFS,就是从头尾两端进行搜索,大家感兴趣,可以自己去实现,这里就不再做详细讲解了。
99+
99100
# 其他语言版本
100101

101102
## Java

problems/0130.被围绕的区域.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
# 130. 被围绕的区域
3+
4+
[题目链接](https://leetcode.cn/problems/surrounded-regions/)
5+
6+
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
7+
8+
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220901104745.png)
9+
10+
* 输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
11+
* 输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
12+
* 解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
13+
14+
## 思路
15+
16+
这道题目和1020. 飞地的数量正好反过来了,[1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/solution/by-carlsun-2-7lt9/)是求 地图中间的空格数,而本题是要把地图中间的'O'都改成'X'。
17+
18+
那么两题在思路上也是差不多的。
19+
20+
依然是从地图周边出发,将周边空格相邻的'O'都做上标记,然后在遍历一遍地图,遇到 'O' 且没做过标记的,那么都是地图中间的'O',全部改成'X'就行。
21+
22+
有的录友可能想,我在定义一个 visited 二维数组,单独标记周边的'O',然后遍历地图的时候同时对 数组board 和 数组visited 进行判断,是否'O'改成'X'。
23+
24+
这样做其实就有点麻烦了,不用额外定义空间了,标记周边的'O',可以直接改board的数值为其他特殊值。
25+
26+
步骤一:深搜或者广搜将地图周边的'O'全部改成'A',如图所示:
27+
28+
![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220902102337.png)
29+
30+
步骤二:在遍历地图,将'O'全部改成'X'(地图中间的'O'改成了'X'),将'A'改回'O'(保留的地图周边的'O'),如图所示:
31+
32+
![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220902102831.png)
33+
34+
整体C++代码如下,以下使用dfs实现,其实遍历方式dfs,bfs都是可以的。
35+
36+
```CPP
37+
class Solution {
38+
private:
39+
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
40+
void dfs(vector<vector<char>>& board, int x, int y) {
41+
board[x][y] = 'A';
42+
for (int i = 0; i < 4; i++) { // 向四个方向遍历
43+
int nextx = x + dir[i][0];
44+
int nexty = y + dir[i][1];
45+
// 超过边界
46+
if (nextx < 0 || nextx >= board.size() || nexty < 0 || nexty >= board[0].size()) continue;
47+
// 不符合条件,不继续遍历
48+
if (board[nextx][nexty] == 'X' || board[nextx][nexty] == 'A') continue;
49+
dfs (board, nextx, nexty);
50+
}
51+
return;
52+
}
53+
54+
public:
55+
void solve(vector<vector<char>>& board) {
56+
int n = board.size(), m = board[0].size();
57+
// 步骤一:
58+
// 从左侧边,和右侧边 向中间遍历
59+
for (int i = 0; i < n; i++) {
60+
if (board[i][0] == 'O') dfs(board, i, 0);
61+
if (board[i][m - 1] == 'O') dfs(board, i, m - 1);
62+
}
63+
64+
// 从上边和下边 向中间遍历
65+
for (int j = 0; j < m; j++) {
66+
if (board[0][j] == 'O') dfs(board, 0, j);
67+
if (board[n - 1][j] == 'O') dfs(board, n - 1, j);
68+
}
69+
// 步骤二:
70+
for (int i = 0; i < n; i++) {
71+
for (int j = 0; j < m; j++) {
72+
if (board[i][j] == 'O') board[i][j] = 'X';
73+
if (board[i][j] == 'A') board[i][j] = 'O';
74+
}
75+
}
76+
}
77+
};
78+
```
79+
80+
## 其他语言版本
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
2+
3+
# 200. 岛屿数量
4+
5+
[题目链接](https://leetcode.cn/problems/number-of-islands/)
6+
7+
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
8+
9+
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
10+
11+
此外,你可以假设该网格的四条边均被水包围。
12+
13+
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220726093256.png)
14+
15+
提示:
16+
17+
* m == grid.length
18+
* n == grid[i].length
19+
* 1 <= m, n <= 300
20+
* grid[i][j] 的值为 '0' 或 '1'
21+
22+
## 思路
23+
24+
注意题目中每座岛屿只能由**水平方向和/或竖直方向上**相邻的陆地连接形成。
25+
26+
也就是说斜角度链接是不算了, 例如示例二,是三个岛屿,如图:
27+
28+
![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220726094200.png)
29+
30+
这道题题目是 DFS,BFS,并查集,基础题目。
31+
32+
本题思路,是用遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。
33+
34+
在遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。
35+
36+
那么如果把节点陆地所能遍历到的陆地都标记上呢,就可以使用 DFS,BFS或者并查集。
37+
38+
### 广度优先搜索
39+
40+
不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节:
41+
42+
根本原因是**只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过**
43+
44+
很多同学可能感觉这有区别吗?
45+
46+
如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。
47+
48+
![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220727100846.png)
49+
50+
超时写法 (从队列中取出节点再标记)
51+
52+
```CPP
53+
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
54+
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
55+
queue<pair<int, int>> que;
56+
que.push({x, y});
57+
while(!que.empty()) {
58+
pair<int ,int> cur = que.front(); que.pop();
59+
int curx = cur.first;
60+
int cury = cur.second;
61+
visited[curx][cury] = true; // 从队列中取出在标记走过
62+
for (int i = 0; i < 4; i++) {
63+
int nextx = curx + dir[i][0];
64+
int nexty = cury + dir[i][1];
65+
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过
66+
if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
67+
que.push({nextx, nexty});
68+
}
69+
}
70+
}
71+
72+
}
73+
```
74+
75+
加入队列 就代表走过,立刻标记,正确写法:
76+
77+
```CPP
78+
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
79+
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
80+
queue<pair<int, int>> que;
81+
que.push({x, y});
82+
visited[x][y] = true; // 只要加入队列,立刻标记
83+
while(!que.empty()) {
84+
pair<int ,int> cur = que.front(); que.pop();
85+
int curx = cur.first;
86+
int cury = cur.second;
87+
for (int i = 0; i < 4; i++) {
88+
int nextx = curx + dir[i][0];
89+
int nexty = cury + dir[i][1];
90+
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过
91+
if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
92+
que.push({nextx, nexty});
93+
visited[nextx][nexty] = true; // 只要加入队列立刻标记
94+
}
95+
}
96+
}
97+
98+
}
99+
```
100+
101+
以上两个版本其实,其实只有细微区别,就是 `visited[x][y] = true;` 放在的地方,着去取决于我们对 代码中队列的定义,队列中的节点就表示已经走过的节点。 **所以只要加入队列,理解标记该节点走过**
102+
103+
本题完整广搜代码:
104+
105+
```CPP
106+
class Solution {
107+
private:
108+
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
109+
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
110+
queue<pair<int, int>> que;
111+
que.push({x, y});
112+
visited[x][y] = true; // 只要加入队列,立刻标记
113+
while(!que.empty()) {
114+
pair<int ,int> cur = que.front(); que.pop();
115+
int curx = cur.first;
116+
int cury = cur.second;
117+
for (int i = 0; i < 4; i++) {
118+
int nextx = curx + dir[i][0];
119+
int nexty = cury + dir[i][1];
120+
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过
121+
if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {
122+
que.push({nextx, nexty});
123+
visited[nextx][nexty] = true; // 只要加入队列立刻标记
124+
}
125+
}
126+
}
127+
}
128+
public:
129+
int numIslands(vector<vector<char>>& grid) {
130+
int n = grid.size(), m = grid[0].size();
131+
vector<vector<bool>> visited = vector<vector<bool>>(n, vector<bool>(m, false));
132+
133+
int result = 0;
134+
for (int i = 0; i < n; i++) {
135+
for (int j = 0; j < m; j++) {
136+
if (!visited[i][j] && grid[i][j] == '1') {
137+
result++; // 遇到没访问过的陆地,+1
138+
bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
139+
}
140+
}
141+
}
142+
return result;
143+
}
144+
};
145+
146+
```

0 commit comments

Comments
 (0)