Skip to content

Commit f206b20

Browse files
committed
108
1 parent 0d4c1c5 commit f206b20

4 files changed

+263
-4
lines changed

SUMMARY.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,12 @@
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 题到 107](leetcode-101-200.md)
105+
* [101 题到 108](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)
109109
* [104. Maximum Depth of Binary Tree](leetcode-104-Maximum-Depth-of-Binary-Tree.md)
110110
* [105. Construct Binary Tree from Preorder and Inorder Traversal](leetcode-105-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md)
111111
* [106. Construct Binary Tree from Inorder and Postorder Traversal](leetcode-106-Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal.md)
112-
* [107. Binary Tree Level Order Traversal II](leetcode-107-Binary-Tree-Level-Order-TraversalII.md)
112+
* [107. Binary Tree Level Order Traversal II](leetcode-107-Binary-Tree-Level-Order-TraversalII.md)
113+
* [108. Convert Sorted Array to Binary Search Tree](leetcode-108-Convert-Sorted-Array-to-Binary-Search-Tree.md)

leetcode-101-200.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010

1111
<a href="leetcode-106-Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal.html">106. Construct Binary Tree from Inorder and Postorder Traversal</a>
1212

13-
<a href="leetcode-107-Binary-Tree-Level-Order-TraversalII.html">107. Binary Tree Level Order Traversal II</a>
13+
<a href="leetcode-107-Binary-Tree-Level-Order-TraversalII.html">107. Binary Tree Level Order Traversal II</a>
14+
15+
<a href="leetcode-108-Convert-Sorted-Array-to-Binary-Search-Tree.html">108. Convert Sorted Array to Binary Search Tree</a>

leetcode-107-Binary-Tree-Level-Order-TraversalII.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,4 @@ public List<List<Integer>> levelOrderBottom(TreeNode root) {
170170

171171
#
172172

173-
这道题依旧考层次遍历,只需要在 [102 题](<https://leetcode.wang/leetcode-102-Binary-Tree-Level-Order-Traversal.html>) 的基础上,找到 `level``index` 的对应关系即可。
173+
这道题依旧考层次遍历,只需要在 [102 题](<https://leetcode.wang/leetcode-102-Binary-Tree-Level-Order-Traversal.html>) 的基础上,找到 `level``index` 的对应关系即可。此外,因为我们在头部添加元素,所以用链表会好一些。如果数组的话,还得整体后移才能添加新的元素。
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# 题目描述(简单难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/108.jpg)
4+
5+
给一个升序数组,生成一个平衡二叉搜索树。平衡二叉树定义如下:
6+
7+
> 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
8+
9+
二叉搜索树定义如下:
10+
11+
> 1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
12+
> 2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
13+
> 3. 任意节点的左、右子树也分别为二叉查找树;
14+
> 4. 没有键值相等的节点。
15+
16+
# 解法一 递归
17+
18+
如果做了 [98 题](<https://leetcode.wang/leetCode-98-Validate-Binary-Search-Tree.html>)[99 题](<https://leetcode.wang/leetcode-99-Recover-Binary-Search-Tree.html>),那么又看到这里的升序数组,然后应该会想到一个点,二叉搜索树的中序遍历刚好可以输出一个升序数组。
19+
20+
所以题目给出的升序数组就是二叉搜索树的中序遍历。
21+
22+
根据中序遍历还原一颗树,又想到了 [105 题](<https://leetcode.wang/leetcode-105-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.html>)[106 题](<https://leetcode.wang/leetcode-106-Construct-Binary-Tree-from-Inorder-and-Postorder-Traversal.html>),通过中序遍历加前序遍历或者中序遍历加后序遍历来还原一棵树。前序(后序)遍历的作用呢?提供根节点!然后根据根节点,就可以递归的生成左右子树。
23+
24+
这里的话怎么知道根节点呢?平衡二叉树,既然要做到平衡,我们只要把根节点选为数组的中点即可。
25+
26+
综上,和之前一样,找到了根节点,然后把数组一分为二,进入递归即可。注意这里的边界情况,包括左边界,不包括右边界。
27+
28+
```java
29+
public TreeNode sortedArrayToBST(int[] nums) {
30+
return sortedArrayToBST(nums, 0, nums.length);
31+
}
32+
33+
private TreeNode sortedArrayToBST(int[] nums, int start, int end) {
34+
if (start == end) {
35+
return null;
36+
}
37+
int mid = (start + end) >>> 1;
38+
TreeNode root = new TreeNode(nums[mid]);
39+
root.left = sortedArrayToBST(nums, start, mid);
40+
root.right = sortedArrayToBST(nums, mid + 1, end);
41+
42+
return root;
43+
}
44+
```
45+
46+
# 解法二 栈 DFS
47+
48+
递归都可以转为迭代的形式。
49+
50+
一部分递归算法,可以转成动态规划,实现空间换时间,例如 [5题](<https://leetcode.windliang.cc/leetCode-5-Longest-Palindromic-Substring.html>)[10题](<https://leetcode.windliang.cc/leetCode-10-Regular-Expression-Matching.html>)[53题](<https://leetcode.windliang.cc/leetCode-53-Maximum-Subarray.html?h=%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92>)[72题](<https://leetcode.wang/leetCode-72-Edit-Distance.html>),从自顶向下再向顶改为了自底向上。
51+
52+
一部分递归算法,只是可以用栈去模仿递归的过程,对于时间或空间的复杂度没有任何好处,比如这道题,唯一好处可能就是能让我们更清楚的了解递归的过程吧。
53+
54+
自己之前对于这种完全模仿递归思路写成迭代,一直也没写过,今天也就试试吧。
55+
56+
思路的话,我们本质上就是在模拟递归,递归其实就是压栈出栈的过程,我们需要用一个栈去把递归的参数存起来。这里的话,就是函数的参数 `start``end`,以及内部定义的 `root`。为了方便,我们就定义一个类。
57+
58+
```java
59+
class MyTreeNode {
60+
TreeNode root;
61+
int start;
62+
int end
63+
MyTreeNode(TreeNode r, int s, int e) {
64+
this.root = r;
65+
this.start = s;
66+
this.end = e;
67+
}
68+
}
69+
```
70+
71+
第一步,我们把根节点存起来。
72+
73+
```java
74+
Stack<MyTreeNode> rootStack = new Stack<>();
75+
int start = 0;
76+
int end = nums.length;
77+
int mid = (start + end) >>> 1;
78+
TreeNode root = new TreeNode(nums[mid]);
79+
TreeNode curRoot = root;
80+
rootStack.push(new MyTreeNode(root, start, end));
81+
```
82+
83+
然后开始递归的过程,就是不停的生成左子树。因为要生成左子树,`end - start` 表示当前树的可用数字的个数,因为根节点已经用去 1 个了,所以为了生成左子树,个数肯定需要大于 1
84+
85+
```java
86+
while (end - start > 1) {
87+
mid = (start + end) >>> 1; //当前根节点
88+
end = mid;//左子树的结尾
89+
mid = (start + end) >>> 1;//左子树的中点
90+
curRoot.left = new TreeNode(nums[mid]);
91+
curRoot = curRoot.left;
92+
rootStack.push(new MyTreeNode(curRoot, start, end));
93+
}
94+
```
95+
96+
在递归中,返回 `null` 以后,开始生成右子树。这里的话,当 `end - start <= 1` ,也就是无法生成左子树了,我们就可以出栈,来生成右子树。
97+
98+
```java
99+
MyTreeNode myNode = rootStack.pop();
100+
//当前作为根节点的 start end 以及 mid
101+
start = myNode.start;
102+
end = myNode.end;
103+
mid = (start + end) >>> 1;
104+
start = mid + 1; //右子树的 start
105+
curRoot = myNode.root; //当前根节点
106+
if (start < end) { //判断当前范围内是否有数
107+
mid = (start + end) >>> 1; //右子树的 mid
108+
curRoot.right = new TreeNode(nums[mid]);
109+
curRoot = curRoot.right;
110+
rootStack.push(new MyTreeNode(curRoot, start, end));
111+
}
112+
```
113+
114+
然后把上边几块内容组合起来就可以了。
115+
116+
```java
117+
class MyTreeNode {
118+
TreeNode root;
119+
int start;
120+
int end;
121+
122+
MyTreeNode(TreeNode r, int s, int e) {
123+
this.root = r;
124+
this.start = s;
125+
this.end = e;
126+
}
127+
}
128+
public TreeNode sortedArrayToBST(int[] nums) {
129+
if (nums.length == 0) {
130+
return null;
131+
}
132+
Stack<MyTreeNode> rootStack = new Stack<>();
133+
int start = 0;
134+
int end = nums.length;
135+
int mid = (start + end) >>> 1;
136+
TreeNode root = new TreeNode(nums[mid]);
137+
TreeNode curRoot = root;
138+
rootStack.push(new MyTreeNode(root, start, end));
139+
while (end - start > 1 || !rootStack.isEmpty()) {
140+
//考虑左子树
141+
while (end - start > 1) {
142+
mid = (start + end) >>> 1; //当前根节点
143+
end = mid;//左子树的结尾
144+
mid = (start + end) >>> 1;//左子树的中点
145+
curRoot.left = new TreeNode(nums[mid]);
146+
curRoot = curRoot.left;
147+
rootStack.push(new MyTreeNode(curRoot, start, end));
148+
}
149+
//出栈考虑右子树
150+
MyTreeNode myNode = rootStack.pop();
151+
//当前作为根节点的 start end 以及 mid
152+
start = myNode.start;
153+
end = myNode.end;
154+
mid = (start + end) >>> 1;
155+
start = mid + 1; //右子树的 start
156+
curRoot = myNode.root; //当前根节点
157+
if (start < end) { //判断当前范围内是否有数
158+
mid = (start + end) >>> 1; //右子树的 mid
159+
curRoot.right = new TreeNode(nums[mid]);
160+
curRoot = curRoot.right;
161+
rootStack.push(new MyTreeNode(curRoot, start, end));
162+
}
163+
164+
}
165+
166+
return root;
167+
}
168+
```
169+
170+
# 解法三 队列 BFS
171+
172+
参考 [这里](<https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/discuss/35218/Java-Iterative-Solution>)。 和递归的思路基本一样,不停的划分范围。
173+
174+
```java
175+
class MyTreeNode {
176+
TreeNode root;
177+
int start;
178+
int end;
179+
180+
MyTreeNode(TreeNode r, int s, int e) {
181+
this.root = r;
182+
this.start = s;
183+
this.end = e;
184+
}
185+
}
186+
public TreeNode sortedArrayToBST3(int[] nums) {
187+
if (nums.length == 0) {
188+
return null;
189+
}
190+
Queue<MyTreeNode> rootQueue = new LinkedList<>();
191+
TreeNode root = new TreeNode(0);
192+
rootQueue.offer(new MyTreeNode(root, 0, nums.length));
193+
while (!rootQueue.isEmpty()) {
194+
MyTreeNode myRoot = rootQueue.poll();
195+
int start = myRoot.start;
196+
int end = myRoot.end;
197+
int mid = (start + end) >>> 1;
198+
TreeNode curRoot = myRoot.root;
199+
curRoot.val = nums[mid];
200+
if (start < mid) {
201+
curRoot.left = new TreeNode(0);
202+
rootQueue.offer(new MyTreeNode(curRoot.left, start, mid));
203+
}
204+
if (mid + 1 < end) {
205+
curRoot.right = new TreeNode(0);
206+
rootQueue.offer(new MyTreeNode(curRoot.right, mid + 1, end));
207+
}
208+
}
209+
210+
return root;
211+
}
212+
```
213+
214+
最巧妙的地方是它先生成 `left` 和 `right` 但不进行赋值,只是把范围传过去,然后出队的时候再进行赋值。这样最开始的根节点也无需单独考虑了。
215+
216+
# 扩展 求中点
217+
218+
前几天和同学发现个有趣的事情,分享一下。
219+
220+
首先假设我们的变量都是 `int` 值。
221+
222+
二分查找中我们需要根据 `start` 和 `end` 求中点,正常情况下加起来除以 2 即可。
223+
224+
```java
225+
int mid = (start + end) / 2
226+
```
227+
228+
但这样有一个缺点,我们知道`int`的最大值是 `Integer.MAX_VALUE` ,也就是`2147483647`。那么有一个问题,如果 `start = 2147483645`,`end = = 2147483645`,虽然 `start` 和 `end`都没有超出最大值,但是如果利用上边的公式,加起来的话就会造成溢出,从而导致`mid`计算错误。
229+
230+
解决的一个方案就是利用数学上的技巧,我们可以加一个 `start` 再减一个 `start` 将公式变形。
231+
232+
```java
233+
(start + end) / 2 = (start + end + start - start) / 2 = start + (end - start) / 2
234+
```
235+
236+
这样的话,就解决了上边的问题。
237+
238+
然后当时和同学看到`jdk`源码中,求`mid`的方法如下
239+
240+
```java
241+
int mid = (start + end) >>> 1
242+
```
243+
244+
它通过移位实现了除以 2,但。。。这样难道不会导致溢出吗?
245+
246+
首先大家可以补一下 [补码](https://mp.weixin.qq.com/s/uvcQHJi6AXhPDJL-6JWUkw) 的知识。
247+
248+
其实问题的关键就是这里了`>>>` ,我们知道还有一种右移是`>>`。区别在于`>>`为有符号右移,右移以后最高位保持原来的最高位。而`>>>`这个右移的话最高位补 0
249+
250+
所以这里其实利用到了整数的补码形式,最高位其实是符号位,所以当 `start + end`溢出的时候,其实本质上只是符号位收到了进位,而`>>>`这个右移可以带着符号位右移,所以之前的信息没有丢掉。
251+
252+
但`>>`有符号右移就会出现问题了,事实上 JDK6 之前都用的`>>`,这个 BUG 在 java 里竟然隐藏了十年之久。
253+
254+
# 总
255+
256+
经过这么多的分析,大家估计体会到了递归的魅力了吧,简洁而优雅。另外的两种迭代的实现,可以让我们更清楚的了解递归到底发生了什么。关于求中点,大家以后就用`>>>`吧,比`start + (end - start) / 2`简洁不少,还能给别人科普一下补码的知识。

0 commit comments

Comments
 (0)