Skip to content

Commit 314e6ac

Browse files
committed
105
1 parent a20cb8b commit 314e6ac

File tree

4 files changed

+332
-5
lines changed

4 files changed

+332
-5
lines changed

SUMMARY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@
102102
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
103103
* [100. Same Tree](leetcode-100-Same-Tree.md)
104104
* [leetcode 100 斩!回顾](leetcode100斩回顾.md)
105-
* [101 题到 104](leetcode-101-200.md)
105+
* [101 题到 105](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
109-
* [104. Maximum Depth of Binary Tree](leetcode-104-Maximum-Depth-of-Binary-Tree.md)
109+
* [104. Maximum Depth of Binary Tree](leetcode-104-Maximum-Depth-of-Binary-Tree.md)
110+
* [105. Construct Binary Tree from Preorder and Inorder Traversal](leetcode-105-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md)

leetCode-4-Median-of-Two-Sorted-Arrays.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public double findMedianSortedArrays(int[] A, int[] B) {
129129

130130
![](http://windliang.oss-cn-beijing.aliyuncs.com/mid1.jpg)
131131

132-
我们比较两个数组的第 k / 2 个数字,如果 k 是奇数,向下取整。也就是比较第 3 个数字,上边数组中的 8 和 下边数组中的 3 ,如果哪个小,就表明该数组的前 k / 2 个数字都不是第 k 小数字,所以可以排除。也就是 1,2,3 这三个数字不可能是第 7 小的数字,我们可以把它排除掉。将 1389 和 45678910 两个数组作为新的数组进行比较。
132+
我们比较两个数组的第 k / 2 个数字,如果 k 是奇数,向下取整。也就是比较第 3 个数字,上边数组中的 4 和 下边数组中的 3 ,如果哪个小,就表明该数组的前 k / 2 个数字都不是第 k 小数字,所以可以排除。也就是 1,2,3 这三个数字不可能是第 7 小的数字,我们可以把它排除掉。将 1349 和 45678910 两个数组作为新的数组进行比较。
133133

134134
更一般的情况 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] ... ,B[ 1 ],B [ 2 ],B [ 3 ],B[ k / 2] ... ,如果 A [ k / 2 ] < B [ k / 2 ] ,那么 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] 都不可能是第 k 小的数字。
135135

@@ -143,7 +143,7 @@ A 数组中比 A [ k / 2 ] 小的数有 k / 2 - 1 个,B 数组中,B [ k / 2
143143

144144
![](http://windliang.oss-cn-beijing.aliyuncs.com/mid3.jpg)
145145

146-
我们又排除掉 2 个数字,所以现在找第 4 - 2 = 2 小的数字就可以了。此时比较两个数组中的第 k / 2 = 1 个数,4 = 4 ,怎么办呢?由于两个数相等,所以我们无论去掉哪个数组中的都行,因为去掉 1 个总会保留 1 个的,所以没有影响。为了统一,我们就假设 4 > 4 吧,所以此时将下边的 4 去掉。
146+
我们又排除掉 2 个数字,所以现在找第 4 - 2 = 2 小的数字就可以了。此时比较两个数组中的第 k / 2 = 1 个数,4 == 4 ,怎么办呢?由于两个数相等,所以我们无论去掉哪个数组中的都行,因为去掉 1 个总会保留 1 个的,所以没有影响。为了统一,我们就假设 4 > 4 吧,所以此时将下边的 4 去掉。
147147

148148
![](http://windliang.oss-cn-beijing.aliyuncs.com/mid4.jpg)
149149

leetcode-101-200.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
<a href="leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.html">103. Binary Tree Zigzag Level Order Traversal</a>
88

9-
<a href="leetcode-104-Maximum-Depth-of-Binary-Tree.html">104. Maximum Depth of Binary Tree</a>
9+
<a href="leetcode-105-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.html">105. Construct Binary Tree from Preorder and Inorder Traversal</a>
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/105.jpg)
4+
5+
根据二叉树的先序遍历和中序遍历还原二叉树。
6+
7+
# 解法一 递归
8+
9+
先序遍历的顺序是根节点,左子树,右子树。中序遍历的顺序是左子树,根节点,右子树。
10+
11+
所以我们只需要根据先序遍历得到根节点,然后在中序遍历中找到根节点的位置,它的左边就是左子树的节点,右边就是右子树的节点。
12+
13+
生成左子树和右子树就可以递归的进行了。
14+
15+
比如上图的例子,我们来分析一下。
16+
17+
```java
18+
preorder = [3,9,20,15,7]
19+
inorder = [9,3,15,20,7]
20+
首先根据 preorder 找到根节点是 3
21+
22+
然后根据根节点将 inorder 分成左子树和右子树
23+
左子树
24+
inorder [9]
25+
26+
右子树
27+
inorder [12,20,7]
28+
29+
把相应的前序遍历的数组也加进来
30+
左子树
31+
preorder[9]
32+
inorder [9]
33+
34+
右子树
35+
preorder[20 15 7]
36+
inorder [12,20,7]
37+
38+
现在我们只需要构造左子树和右子树即可,成功把大问题化成了小问题
39+
然后重复上边的步骤继续划分,直到 preorder 和 inorder 都为空,返回 null 即可
40+
```
41+
42+
事实上,我们不需要真的把 `preorder``inorder` 切分了,只需要用分别用两个指针指向开头和结束位置即可。注意下边的两个指针指向的数组范围是包括左边界,不包括右边界。
43+
44+
对于下边的树的合成。
45+
46+
![](https://windliang.oss-cn-beijing.aliyuncs.com/105_3.jpg)
47+
48+
左子树
49+
50+
![](https://windliang.oss-cn-beijing.aliyuncs.com/105_4.jpg)
51+
52+
右子树
53+
54+
![](https://windliang.oss-cn-beijing.aliyuncs.com/105_5.jpg)
55+
56+
```java
57+
public TreeNode buildTree(int[] preorder, int[] inorder) {
58+
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
59+
}
60+
61+
private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
62+
// preorder 为空,直接返回 null
63+
if (p_start == p_end) {
64+
return null;
65+
}
66+
int root_val = preorder[p_start];
67+
TreeNode root = new TreeNode(root_val);
68+
//在中序遍历中找到根节点的位置
69+
int i_root_index = 0;
70+
for (int i = i_start; i < i_end; i++) {
71+
if (root_val == inorder[i]) {
72+
i_root_index = i;
73+
break;
74+
}
75+
}
76+
int leftNum = i_root_index - i_start;
77+
//递归的构造左子树
78+
root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
79+
//递归的构造右子树
80+
root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
81+
return root;
82+
}
83+
```
84+
85+
上边的代码很好理解,但存在一个问题,在中序遍历中找到根节点的位置每次都得遍历中序遍历的数组去寻找,参考[这里](<https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/discuss/34538/My-Accepted-Java-Solution>) ,我们可以用一个`HashMap`把中序遍历数组的每个元素的值和下标存起来,这样寻找根节点的位置就可以直接得到了。
86+
87+
```java
88+
public TreeNode buildTree3(int[] preorder, int[] inorder) {
89+
HashMap<Integer, Integer> map = new HashMap<>();
90+
for (int i = 0; i < inorder.length; i++) {
91+
map.put(inorder[i], i);
92+
}
93+
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length, map);
94+
}
95+
96+
private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end,
97+
HashMap<Integer, Integer> map) {
98+
if (p_start == p_end) {
99+
return null;
100+
}
101+
int root_val = preorder[p_start];
102+
TreeNode root = new TreeNode(root_val);
103+
int i_root_index = map.get(root_val);
104+
int leftNum = i_root_index - i_start;
105+
root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index, map);
106+
root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end, map);
107+
return root;
108+
}
109+
```
110+
111+
本以为已经完美了,在 [这里](<https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/discuss/34543/Simple-O(n)-without-map>) 又看到了令人眼前一亮的思路,就是 StefanPochmann 大神,经常逛 Discuss 一定会注意到他,拥有 3 万多的赞。
112+
113+
![](https://windliang.oss-cn-beijing.aliyuncs.com/105_2.jpg)
114+
115+
他也发现了每次都得遍历一次去找中序遍历数组中的根节点的麻烦,但他没有用 `HashMap`就解决了这个问题,下边来说一下。
116+
117+
`pre`变量保存当前要构造的树的根节点,从根节点开始递归的构造左子树和右子树,`in`变量指向当前根节点可用数字的开头,然后对于当前`pre`有一个停止点`stop`,从`in``stop`表示要构造的树当前的数字范围。
118+
119+
```java
120+
public TreeNode buildTree(int[] preorder, int[] inorder) {
121+
return buildTreeHelper(preorder, inorder, (long)Integer.MAX_VALUE + 1);
122+
}
123+
int pre = 0;
124+
int in = 0;
125+
private TreeNode buildTreeHelper(int[] preorder, int[] inorder, long stop) {
126+
//到达末尾返回 null
127+
if(pre == preorder.length){
128+
return null;
129+
}
130+
//到达停止点返回 null
131+
//当前停止点已经用了,in 后移
132+
if (inorder[in] == stop) {
133+
in++;
134+
return null;
135+
}
136+
int root_val = preorder[pre++];
137+
TreeNode root = new TreeNode(root_val);
138+
//左子树的停止点是当前的根节点
139+
root.left = buildTreeHelper(preorder, inorder, root_val);
140+
//右子树的停止点是当前树的停止点
141+
root.right = buildTreeHelper(preorder, inorder, stop);
142+
return root;
143+
}
144+
```
145+
146+
代码很简洁,但如果细想起来真的很难理解了。
147+
148+
把他的原话也贴过来吧。
149+
150+
> Consider the example again. Instead of finding the `1` in `inorder`, splitting the arrays into parts and recursing on them, just recurse on the full remaining arrays and **stop** when you come across the `1` in `inorder`. That's what my above solution does. Each recursive call gets told where to stop, and it tells its subcalls where to stop. It gives its own root value as stopper to its left subcall and its parent`s stopper as stopper to its right subcall.
151+
152+
本来很想讲清楚这个算法,但是各种画图,还是太难说清楚了。这里就画几个过程中的图,大家也只能按照上边的代码走一遍,理解一下了。
153+
154+
```java
155+
3
156+
/ \
157+
9 7
158+
/ \
159+
20 15
160+
161+
前序遍历数组和中序遍历数组
162+
preorder = [ 3, 9, 20, 15, 7 ]
163+
inorder = [ 20, 9, 15, 3, 7 ]
164+
p 代表 pre,i 代表 in,s 代表 stop
165+
166+
首先构造根节点为 3 的树,可用数字是 i 到 s
167+
s 初始化一个树中所有的数字都不会相等的数,所以代码中用了一个 long 来表示
168+
3, 9, 20, 15, 7
169+
^
170+
p
171+
20, 9, 15, 3, 7
172+
^ ^
173+
i s
174+
175+
考虑根节点为 3 的左子树, 考虑根节点为 3 的树的右子树,
176+
stop 值是当前根节点的值 3 只知道 stop 值是上次的 s
177+
新的根节点是 9,可用数字是 i 到 s
178+
不包括 s
179+
3, 9, 20, 15, 7 3, 9, 20, 15, 7
180+
^
181+
p
182+
20, 9, 15, 3, 7 20, 9, 15, 3, 7
183+
^ ^ ^
184+
i s s
185+
186+
递归出口的情况
187+
3, 9, 20, 15, 7
188+
^
189+
p
190+
20, 9, 15, 3, 7
191+
^
192+
i
193+
s
194+
此时 in 和 stop 相等,表明没有可用的数字,所以返回 null,并且表明此时到达了某个树的根节点,所以 i 后移。
195+
```
196+
197+
总之他的思想就是,不再从中序遍历中寻找根节点的位置,而是直接把值传过去,表明当前子树的结束点。不过总感觉还是没有 get 到他的点,`in``stop` 变量的含义也是我赋予的,对于整个算法也只是勉强说通,大家有好的想法可以和我交流。
198+
199+
# 解法二 迭代 栈
200+
201+
参考 [这里](<https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/discuss/34555/The-iterative-solution-is-easier-than-you-think!>),我们可以利用一个栈,用迭代实现。
202+
203+
假设我们要还原的树是下图
204+
205+
```java
206+
3
207+
/ \
208+
9 7
209+
/ \
210+
20 15
211+
```
212+
213+
首先假设我们只有先序遍历的数组,如果还原一颗树,会遇到什么问题。
214+
215+
```java
216+
preorder = [3, 9, 20, 15, 7 ]
217+
```
218+
219+
首先我们把 `3` 作为根节点,然后到了 `9` ,就出现一个问题,`9` 是左子树还是右子树呢?
220+
221+
所以需要再加上中序遍历的数组来确定。
222+
223+
```java
224+
inorder = [ 20, 9, 15, 3, 7 ]
225+
```
226+
227+
我们知道中序遍历,首先遍历左子树,然后是根节点,最后是右子树。这里第一个遍历的是 `20` ,说明先序遍历的 `9` 一定是左子树,利用反证法证明。
228+
229+
假如 `9` 是右子树,根据先序遍历 `preorder = [ 3, 9, 20, 15, 7 ]`,说明根节点 `3` 的左子树是空的,
230+
231+
左子树为空,那么中序遍历就会先遍历根节点 `3`,而此时是 `20`,假设不成立,说明 `9` 是左子树。
232+
233+
接下来的 `20` 同理,所以可以目前构建出来的树如下。
234+
235+
```java
236+
3
237+
/
238+
9
239+
/
240+
20
241+
```
242+
243+
同时,还注意到此时先序遍历的 `20` 和中序遍历 `20` 相等了,说明什么呢?
244+
245+
说明中序遍历的下一个数 `15` 不是左子树了,如果是左子树,那么中序遍历的第一个数就不会是 `20`
246+
247+
所以 `15` 一定是右子树了,现在还有个问题,它是 `20` 的右子树,还是 `9` 的右子树,还是 `3` 的右子树?
248+
249+
我们来假设几种情况,来想一下。
250+
251+
1. 如果是 `3` 的右子树, `20``9` 的右子树为空,那么中序遍历就是`20 9 3 15`
252+
253+
2. 如果是 `9` 的右子树,`20` 的右子树为空,那么中序遍历就是`20 9 15`
254+
255+
3. 如果是 `20` 的右子树,那么中序遍历就是`20 15`
256+
257+
之前已经遍历的根节点是 `3 9 20`**把它倒过来,即`20 9 3`**,然后和上边的三种中序遍历比较,会发现 `15` 就是**最后一次相等**的节点的右子树。
258+
259+
第 1 种情况,中序遍历是`20 9 3 15`,和`20 9 3` 都相等,所以 `15``3` 的右子树。
260+
261+
第 2 种情况,中序遍历是`20 9 15`,只有`20 9` 相等,所以 `15``9` 的右子树。
262+
263+
第 3 种情况,中序遍历就是`20 15`,只有`20` 相等,所以 `20``15` 的右子树。
264+
265+
而此时我们的中序遍历数组是`inorder = [ 20, 9 ,15, 3, 7 ]``20` 匹配,`9`匹配,最后一次匹配是 `9`,所以 `15``9`的右子树。
266+
267+
```java
268+
3
269+
/
270+
9
271+
/ \
272+
20 15
273+
```
274+
275+
综上所述,我们用一个栈保存已经遍历过的节点,遍历前序遍历的数组,一直作为当前根节点的左子树,直到当前节点和中序遍历的数组的节点相等了,那么我们正序遍历中序遍历的数组,倒着遍历已经遍历过的根节点(用栈的 pop 实现),找到最后一次相等的位置,把它作为该节点的右子树。
276+
277+
上边的分析就是迭代总体的思想,代码的话还有一些细节注意一下。用一个栈保存已经遍历的节点,用 curRoot 保存当前正在遍历的节点。
278+
279+
```java
280+
public TreeNode buildTree4(int[] preorder, int[] inorder) {
281+
if (preorder.length == 0) {
282+
return null;
283+
}
284+
Stack<TreeNode> roots = new Stack<TreeNode>();
285+
int pre = 0;
286+
int in = 0;
287+
//先序遍历第一个值作为根节点
288+
TreeNode curRoot = new TreeNode(preorder[pre]);
289+
TreeNode root = curRoot;
290+
roots.push(curRoot);
291+
pre++;
292+
//遍历前序遍历的数组
293+
while (pre < preorder.length) {
294+
//出现了当前节点的值和中序遍历数组的值相等,寻找是谁的右子树
295+
if (curRoot.val == inorder[in]) {
296+
//每次进行出栈,实现倒着遍历
297+
while (!roots.isEmpty() && roots.peek().val == inorder[in]) {
298+
curRoot = roots.peek();
299+
roots.pop();
300+
in++;
301+
}
302+
//设为当前的右孩子
303+
curRoot.right = new TreeNode(preorder[pre]);
304+
//更新 curRoot
305+
curRoot = curRoot.right;
306+
roots.push(curRoot);
307+
pre++;
308+
} else {
309+
//否则的话就一直作为左子树
310+
curRoot.left = new TreeNode(preorder[pre]);
311+
curRoot = curRoot.left;
312+
roots.push(curRoot);
313+
pre++;
314+
}
315+
}
316+
return root;
317+
}
318+
319+
```
320+
321+
#
322+
323+
用常规的递归和 HashMap 做的话这道题是不难的,用 `stop` 变量省去 HashMap 的使用以及解法二的迭代可以了解一下吧,不是很容易想到。
324+
325+
326+

0 commit comments

Comments
 (0)