1
1
## 题目地址
2
2
https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
3
3
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
+
4
21
## 思路
5
22
6
- 首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以后序数组最后一个元素为切割点 ,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
23
+ 首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点 ,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
7
24
8
25
如果让我们肉眼看两个序列,画一颗二叉树的话,应该分分钟都可以画出来。
9
26
10
27
流程如图:
11
28
12
-
13
29
<img src =' ../pics/106.从中序与后序遍历序列构造二叉树.png ' width =600 > </img ></div >
14
30
15
31
那么代码应该怎么写呢?
16
32
17
- 说道一层一层切割 ,就应该想到了递归。
33
+ 说到一层一层切割 ,就应该想到了递归。
18
34
19
35
来看一下一共分几步:
20
36
21
37
* 第一步:如果数组大小为零的话,说明是空节点了。
22
38
23
39
* 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
24
40
25
- * 第三步:找到后序数组最后一个元素在后序数组的位置 ,作为切割点
41
+ * 第三步:找到后序数组最后一个元素在中序数组的位置 ,作为切割点
26
42
27
43
* 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
28
44
@@ -35,36 +51,38 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
35
51
```
36
52
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
37
53
54
+ // 第一步
38
55
if (postorder.size() == 0) return NULL;
39
56
40
- // 后序遍历数组最后一个元素,就是当前的中间节点
57
+ // 第二步: 后序遍历数组最后一个元素,就是当前的中间节点
41
58
int rootValue = postorder[postorder.size() - 1];
42
59
TreeNode* root = new TreeNode(rootValue);
43
60
44
61
// 叶子节点
45
62
if (postorder.size() == 1) return root;
46
63
47
- 找切割点
64
+ // 第三步: 找切割点
48
65
int delimiterIndex;
49
66
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
50
67
if (inorder[delimiterIndex] == rootValue) break;
51
68
}
52
69
53
- 切割中序数组,得到 中序左数组和中序右数组
54
- 切割后序数组,得到 后序左数组和后序右数组
70
+ // 第四步: 切割中序数组,得到 中序左数组和中序右数组
71
+ // 第五步: 切割后序数组,得到 后序左数组和后序右数组
55
72
73
+ // 第六步
56
74
root->left = traversal(中序左数组, 后序左数组);
57
75
root->right = traversal(中序右数组, 后序右数组);
58
76
59
77
return root;
60
78
}
61
79
```
62
80
63
- 难点大家应该发现了,如何切割呢,边界值找不好很容易乱套。
81
+ ** 难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。 **
64
82
65
83
此时应该注意确定切割的标准,是左闭右开,还有左开又闭,还是左闭又闭,这个就是不变量,要在递归中保持这个不变量。
66
84
67
- 在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必要乱套!
85
+ ** 在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭又闭,必然乱套! **
68
86
69
87
我在[ 数组:每次遇到二分法,都是一看就会,一写就废] ( https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q ) 和[ 数组:这个循环可以转懵很多人!] ( https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg ) 中都强调过循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。
70
88
@@ -77,17 +95,16 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
77
95
78
96
79
97
```
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() );
91
108
```
92
109
93
110
接下来就要切割后序数组了。
@@ -96,7 +113,7 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
96
113
97
114
后序数组的切割点怎么找?
98
115
99
- 后序数组没有明确的切割元素来进行左右切割,不想中序数组有明确的切割左右,左右分开就可以了 。
116
+ 后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了 。
100
117
101
118
** 此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。**
102
119
@@ -105,28 +122,27 @@ https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorde
105
122
代码如下:
106
123
107
124
```
108
- // postorder 舍弃末尾元素
109
- postorder.resize(postorder.size() - 1);
125
+ // postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
126
+ postorder.resize(postorder.size() - 1);
110
127
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());
116
132
```
117
133
118
- 此时,中序数组切成了左中序数组和右中序数组,后序数组切割成序数组和右后序数组 。
134
+ 此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组 。
119
135
120
136
接下来可以递归了,代码如下:
121
137
122
138
```
123
- root->left = traversal(leftInorder, leftPostorder);
124
- root->right = traversal(rightInorder, rightPostorder);
139
+ root->left = traversal(leftInorder, leftPostorder);
140
+ root->right = traversal(rightInorder, rightPostorder);
125
141
```
126
142
127
143
完整代码如下:
128
144
129
- ## C++ 完整代码
145
+ ### C++完整代码
130
146
131
147
```
132
148
class Solution {
@@ -206,6 +222,7 @@ private:
206
222
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
207
223
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
208
224
225
+ // 一下为日志
209
226
cout << "----------" << endl;
210
227
211
228
cout << "leftInorder :";
@@ -244,11 +261,11 @@ public:
244
261
};
245
262
```
246
263
247
- ** 此时应该发现了,如上的代码性能并不好,应为每层递归定定义了新的vector,既耗时又耗空间,但是上面的代码是最好理解的 ,为了方便读者理解,所以用如上的代码来讲解。**
264
+ ** 此时应该发现了,如上的代码性能并不好,应为每层递归定定义了新的vector(就是数组) ,既耗时又耗空间,但上面的代码是最好理解的 ,为了方便读者理解,所以用如上的代码来讲解。**
248
265
249
266
下面给出用下表索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下表索引来分割)
250
267
251
- ## C++最终优化版本
268
+ ### C++优化版本
252
269
```
253
270
class Solution {
254
271
private:
@@ -368,7 +385,24 @@ public:
368
385
369
386
# 105. 从前序与中序遍历序列构造二叉树
370
387
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
+ 我就直接给出代码了。
372
406
373
407
带日志的版本C++代码如下: (** 带日志的版本仅用于调试,不要在leetcode上提交,会超时** )
374
408
@@ -444,9 +478,8 @@ public:
444
478
};
445
479
```
446
480
447
- 105 . 从前序与中序遍历序列构造二叉树,最后版本:
481
+ 105.从前序与中序遍历序列构造二叉树,最后版本,C++代码 :
448
482
449
- C++代码:
450
483
```
451
484
class Solution {
452
485
private:
@@ -493,3 +526,45 @@ public:
493
526
}
494
527
};
495
528
```
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
+
0 commit comments