Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 71d8678

Browse files
Update
1 parent dd2d8a0 commit 71d8678

7 files changed

+170
-44
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,16 @@
6767
* [字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw)
6868
* [字符串:总结篇!](https://mp.weixin.qq.com/s/gtycjyDtblmytvBRFlCZJg)
6969

70-
* [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)
70+
* 双指针法
71+
* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
72+
* [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
73+
* [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
74+
* [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
75+
* [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)
76+
* [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)
77+
* [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)
78+
* [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)
79+
* [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA)
7180

7281
* 栈与队列
7382
* [栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)
Loading
Loading
Loading

problems/0106.从中序与后序遍历序列构造二叉树.md

Lines changed: 113 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
## 题目地址
22
https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
33

4+
> 给出两个序列
5+
6+
# 106.从中序与后序遍历序列构造二叉树
7+
8+
根据一棵树的中序遍历与后序遍历构造二叉树。
9+
10+
注意:
11+
你可以假设树中没有重复的元素。
12+
13+
例如,给出
14+
15+
中序遍历 inorder = [9,3,15,20,7]
16+
后序遍历 postorder = [9,15,7,20,3]
17+
返回如下的二叉树:
18+
19+
<img src='../pics/106. 从中序与后序遍历序列构造二叉树1.png' width=600> </img></div>
20+
421
## 思路
522

6-
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以后序数组最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
23+
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
724

825
如果让我们肉眼看两个序列,画一颗二叉树的话,应该分分钟都可以画出来。
926

1027
流程如图:
1128

12-
1329
<img src='../pics/106.从中序与后序遍历序列构造二叉树.png' width=600> </img></div>
1430

1531
那么代码应该怎么写呢?
1632

17-
说道一层一层切割,就应该想到了递归。
33+
说到一层一层切割,就应该想到了递归。
1834

1935
来看一下一共分几步:
2036

2137
* 第一步:如果数组大小为零的话,说明是空节点了。
2238

2339
* 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
2440

25-
* 第三步:找到后序数组最后一个元素在后序数组的位置,作为切割点
41+
* 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
2642

2743
* 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
2844

@@ -35,36 +51,38 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
3551
```
3652
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
3753
54+
// 第一步
3855
if (postorder.size() == 0) return NULL;
3956
40-
// 后序遍历数组最后一个元素,就是当前的中间节点
57+
// 第二步:后序遍历数组最后一个元素,就是当前的中间节点
4158
int rootValue = postorder[postorder.size() - 1];
4259
TreeNode* root = new TreeNode(rootValue);
4360
4461
// 叶子节点
4562
if (postorder.size() == 1) return root;
4663
47-
找切割点
64+
// 第三步:找切割点
4865
int delimiterIndex;
4966
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
5067
if (inorder[delimiterIndex] == rootValue) break;
5168
}
5269
53-
切割中序数组,得到 中序左数组和中序右数组
54-
切割后序数组,得到 后序左数组和后序右数组
70+
// 第四步:切割中序数组,得到 中序左数组和中序右数组
71+
// 第五步:切割后序数组,得到 后序左数组和后序右数组
5572
73+
// 第六步
5674
root->left = traversal(中序左数组, 后序左数组);
5775
root->right = traversal(中序右数组, 后序右数组);
5876
5977
return root;
6078
}
6179
```
6280

63-
难点大家应该发现了,如何切割呢,边界值找不好很容易乱套。
81+
**难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。**
6482

6583
此时应该注意确定切割的标准,是左闭右开,还有左开又闭,还是左闭又闭,这个就是不变量,要在递归中保持这个不变量。
6684

67-
在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必要乱套!
85+
**在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必然乱套!**
6886

6987
我在[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)[数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。
7088

@@ -77,17 +95,16 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
7795

7896

7997
```
80-
// 找到中序遍历的切割点
81-
int delimiterIndex;
82-
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
83-
if (inorder[delimiterIndex] == rootValue) break;
84-
}
85-
86-
// 左闭右开区间
87-
// [0, delimiterIndex)
88-
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
89-
// [delimiterIndex + 1, end)
90-
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );
98+
// 找到中序遍历的切割点
99+
int delimiterIndex;
100+
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
101+
if (inorder[delimiterIndex] == rootValue) break;
102+
}
103+
104+
// 左闭右开区间:[0, delimiterIndex)
105+
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
106+
// [delimiterIndex + 1, end)
107+
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );
91108
```
92109

93110
接下来就要切割后序数组了。
@@ -96,7 +113,7 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
96113

97114
后序数组的切割点怎么找?
98115

99-
后序数组没有明确的切割元素来进行左右切割,不想中序数组有明确的切割左右,左右分开就可以了
116+
后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了
100117

101118
**此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。**
102119

@@ -105,28 +122,27 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
105122
代码如下:
106123

107124
```
108-
// postorder 舍弃末尾元素
109-
postorder.resize(postorder.size() - 1);
125+
// postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
126+
postorder.resize(postorder.size() - 1);
110127
111-
// 依然左闭右开,注意这里使用了左中序数组大小作为切割点
112-
// [0, leftInorder.size)
113-
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
114-
// [leftInorder.size(), end)
115-
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
128+
// 左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
129+
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
130+
// [leftInorder.size(), end)
131+
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
116132
```
117133

118-
此时,中序数组切成了左中序数组和右中序数组,后序数组切割成序数组和右后序数组
134+
此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组
119135

120136
接下来可以递归了,代码如下:
121137

122138
```
123-
root->left = traversal(leftInorder, leftPostorder);
124-
root->right = traversal(rightInorder, rightPostorder);
139+
root->left = traversal(leftInorder, leftPostorder);
140+
root->right = traversal(rightInorder, rightPostorder);
125141
```
126142

127143
完整代码如下:
128144

129-
## C++ 完整代码
145+
### C++完整代码
130146

131147
```
132148
class Solution {
@@ -206,6 +222,7 @@ private:
206222
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
207223
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
208224
225+
// 一下为日志
209226
cout << "----------" << endl;
210227
211228
cout << "leftInorder :";
@@ -244,11 +261,11 @@ public:
244261
};
245262
```
246263

247-
**此时应该发现了,如上的代码性能并不好,应为每层递归定定义了新的vector,既耗时又耗空间,但是上面的代码是最好理解的,为了方便读者理解,所以用如上的代码来讲解。**
264+
**此时应该发现了,如上的代码性能并不好,应为每层递归定定义了新的vector(就是数组),既耗时又耗空间,但上面的代码是最好理解的,为了方便读者理解,所以用如上的代码来讲解。**
248265

249266
下面给出用下表索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下表索引来分割)
250267

251-
## C++最终优化版本
268+
### C++优化版本
252269
```
253270
class Solution {
254271
private:
@@ -368,7 +385,24 @@ public:
368385

369386
# 105. 从前序与中序遍历序列构造二叉树
370387

371-
同样的道理
388+
根据一棵树的前序遍历与中序遍历构造二叉树。
389+
390+
注意:
391+
你可以假设树中没有重复的元素。
392+
393+
例如,给出
394+
395+
前序遍历 preorder = [3,9,20,15,7]
396+
中序遍历 inorder = [9,3,15,20,7]
397+
返回如下的二叉树:
398+
399+
<img src='../pics/105. 从前序与中序遍历序列构造二叉树.png' width=600> </img></div>
400+
401+
## 思路
402+
403+
本题和106是一样的道理。
404+
405+
我就直接给出代码了。
372406

373407
带日志的版本C++代码如下: (**带日志的版本仅用于调试,不要在leetcode上提交,会超时**
374408

@@ -444,9 +478,8 @@ public:
444478
};
445479
```
446480

447-
105. 从前序与中序遍历序列构造二叉树,最后版本:
481+
105.从前序与中序遍历序列构造二叉树,最后版本,C++代码
448482

449-
C++代码:
450483
```
451484
class Solution {
452485
private:
@@ -493,3 +526,45 @@ public:
493526
}
494527
};
495528
```
529+
530+
# 思考题
531+
532+
前序和中序可以唯一确定一颗二叉树。
533+
534+
后序和中序可以唯一确定一颗二叉树。
535+
536+
那么前序和后序可不可以唯一确定一颗二叉树呢?
537+
538+
**前序和后序不能唯一确定一颗二叉树!**,因为没有中序遍历无法确定左右部分,也就是无法分割。
539+
540+
举一个例子:
541+
542+
<img src='../pics/106.从中序与后序遍历序列构造二叉树2.png' width=600> </img></div>
543+
544+
tree1 的前序遍历是[1 2 3], 后序遍历是[3 2 1]
545+
546+
tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]
547+
548+
那么tree1 和 tree2 的前序和后序完全相同,这是一棵树么,很明显是两棵树!
549+
550+
所以前序和后序不能唯一确定一颗二叉树!
551+
552+
# 总结
553+
554+
之前我们讲的二叉树题目都是各种遍历二叉树,这次开始构造二叉树了,思路其实比较简单,但是真正代码实现出来并不容易。
555+
556+
所以要避免眼高手低,踏实的把代码写出来。
557+
558+
我同时给出了添加日志的代码版本,因为这种题目是不太容易写出来调一调就能过的,所以一定要把流程日志打出来,看看符不符合自己的思路。
559+
560+
大家遇到这种题目的时候,也要学会打日志来调试(如何打日志有时候也是个技术活),不要脑动模拟,脑动模拟很容易越想越乱。
561+
562+
最后我还给出了为什么前序和中序可以唯一确定一颗二叉树,后序和中序可以唯一确定一颗二叉树,而前序和后序却不行。
563+
564+
认真研究完本篇,相信大家对二叉树的构造会清晰很多。
565+
566+
如果学到了,就赶紧转发给身边需要的同学吧!
567+
568+
加个油!
569+
570+

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,17 @@ public:
165165
queue<TreeNode*> que;
166166
que.push(root);
167167
while(!que.empty()) {
168-
int size = que.size();
168+
int size = que.size();
169169
depth++; // 记录最小深度
170-
int flag = 0;
171170
for (int i = 0; i < size; i++) {
172171
TreeNode* node = que.front();
173172
que.pop();
174173
if (node->left) que.push(node->left);
175174
if (node->right) que.push(node->right);
176175
if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出
177-
flag = 1;
178-
break;
176+
return depth;
179177
}
180178
}
181-
if (flag == 1) break;
182179
}
183180
return depth;
184181
}

problems/0141.环形链表.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
## 思路
3+
4+
可以使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
5+
6+
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?
7+
8+
首先第一点: **fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。**
9+
10+
那么来看一下,**为什么fast指针和slow指针一定会相遇呢?**
11+
12+
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
13+
14+
会发现最终都是这种情况, 如下图:
15+
16+
<img src='../pics/142环形链表1.png' width=600> </img></div>
17+
18+
fast和slow各自再走一步, fast和slow就相遇了
19+
20+
这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。
21+
22+
23+
## C++代码如下
24+
25+
```
26+
class Solution {
27+
public:
28+
bool hasCycle(ListNode *head) {
29+
ListNode* fast = head;
30+
ListNode* slow = head;
31+
while(fast != NULL && fast->next != NULL) {
32+
slow = slow->next;
33+
fast = fast->next->next;
34+
// 快慢指针相遇,说明有环
35+
if (slow == fast) return true;
36+
}
37+
return false;
38+
}
39+
};
40+
```
41+
## 扩展
42+
43+
做完这道题目,可以在做做142.环形链表II,不仅仅要找环,还要找环的入口。
44+
45+
142.环形链表II题解:[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)

0 commit comments

Comments
 (0)