二叉树的概念和用途
# 二叉树的概念和用途
树是一种非线性的数据结构,分层存储。
树被用来存储具有层级关系的数据,还被用来存储有序列表。
二叉树进行查找特别快,为二叉树添加或删除元素也特别快。
# 二叉树的一些关键点
树由一组以边连接的节点组成。
一棵树最上面的节点称为根节点,如果一个节点下面连接多个节点,那么该节点称为父节点,它下面的节点被称为子节点。一个节点可以有 0 个、1 个或多个子节点。没有任何子节点的节点称为叶子节点。
二叉树是一种特殊的树,子节点数不超过两个。
从一个节点走到另一个节点的这一组边称为路径。
以某种特定顺序访问树中的所有节点称为树的遍历。
树分为几个层次,根节点是第 0 层,它的子节点第 1 层,以此类推。我们定义树的层数就是树的深度。
每个节点都有一个与之相关的值,该值有时被称为键。
一个父节点的两个子节点分别称为左节点和右节点。
# 二叉搜索树
二叉搜索树又称有序二叉树、排序二叉树,它具有以下性质:
左子树上所有节点的值均小于它的根节点的值;
右子树上所有节点的值均大于它的根节点的值;
左右子树也是二叉搜索树。
# 满二叉树和完全二叉树
- 一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。
- 对一个有 n 个节点的二叉树,按层级顺序编号,则所有节点的编号为从 1 到 n。如果这个树所有节点和同样深度的满二叉树的编号为从 1 到 n 的节点位置相同,则这个二叉树为完全二叉树。
在上图中,二叉树编号从 1 到 12 的 12 个节点,和前面满二叉树编号从 1 到 12 的节点位置完全对应。因此这个树是完全二叉树。
完全二叉树的条件没有满二叉树那么苛刻:满二叉树要求所有分支都是满的;而完全二叉树只需保证最后一个节点之前的节点都齐全即可。
# 二叉树的存储方式
📌 1. 链式存储结构
链式存储是二叉树最直观的存储方式。
二叉树中一个节点最多可以指向左右两个孩子节点,所以二叉树的每一个节点包含 3 部分。
存储数据的 data 变量
指向左孩子的 left 指针
指向右孩子的 right 指针
📌 2. 数组
使用数组存储时,会按照层级顺序把二叉树的节点放到数组中对应的位置上。如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也空出来。
为什么这样设计呢?因为这样可以更方便地在数组中定位二叉树的孩子节点和父节点。
假设一个父节点的下标是 parent,那么它的左孩子节点下标就是 2 × parent + 1;右孩子节点下标就是 2 × parent + 2。
反过来,假设一个左孩子节点的下标是 leftChild,那么它的父节点下标就是 (leftChild-1)/ 2。
假如节点 4 在数组中的下标是 3,节点 4 是节点 2 的左孩子,节点 2 的下标可以直接通过计算得出。
节点 2 的下标 = (3-1)/2 = 1
显然,对于一个稀疏的二叉树来说,用数组表示法是非常浪费空间的。
什么样的二叉树最适合用数组表示呢?
二叉堆,一种特殊的完全二叉树,就是用数组来存储的。
# 二叉树的代码实现
// 节点
function Node(data) {
this.data = data;
this.left = null;
this.right = null;
}
// 二叉搜索树
function Bst() {
this.root = null;
this.insert = insert;
this.preOrder = preOrder;
this.inOrder = inOrder;
this.lastOrder = lastOrder;
this.iteratePreOrder = iteratePreOrder;
this.iterateInOrder = iterateInOrder;
this.iterateLastOrder = iterateLastOrder;
this.getMin = getMin;
this.getMax = getMax;
this.find = find;
this.remove = remove;
}
// 向树中插入数据
function insert(data) {
var node = new Node(data);
if (this.root === null) {
this.root = node;
} else {
var current = this.root;
var parent;
while (true) {
parent = current;
if (data < current.data) {
current = current.left;
if (current === null) {
parent.left = node;
break;
}
} else {
current = current.right;
if (current === null) {
parent.right = node;
break;
}
}
}
}
}
/* 插入操作也可以这么写 */
// 向树中插入数据
function insert(data) {
var newNode = new Node(data); // 新节点
if (this.root === null) {
this.root = newNode;
} else {
insertNode(this.root, newNode);
}
}
// 插入节点方法
function insertNode(node, newNode) {
if (newNode.data > node.data) {
// 往右走
if (node.right === null) {
node.right = newNode;
} else {
insertNode(node.right, newNode); // 递归
}
} else if (newNode.data < node.data) {
// 往左走
if (node.left === null) {
node.left = newNode;
} else {
insertNode(node.left, newNode); // 递归
}
}
}
// 前序遍历:根->左->右
function preOrder(node, res) {
if (node) {
res.push(node.data);
preOrder(node.left, res);
preOrder(node.right, res);
}
return res;
}
// 中序遍历:左->根->右
function inOrder(node, res) {
if (node) {
inOrder(node.left, res);
res.push(node.data);
inOrder(node.right, res);
}
return res;
}
// 后序遍历:左->右->根
function lastOrder(node, res) {
if (node) {
lastOrder(node.left, res);
lastOrder(node.right, res);
res.push(node.data);
}
return res;
}
// 前序遍历的迭代实现
function iteratePreOrder(node, res) {
const stack = [];
while (node || stack.length) {
// 迭代访问节点的左孩子,并入栈
while (node) {
res.push(node.data);
stack.push(node);
node = node.left;
}
// 如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
if (stack.length) {
node = stack.pop();
node = node.right;
}
}
return res;
}
// 中序遍历的迭代实现
function iterateInOrder(node, res) {
const stack = [];
while (node || stack.length) {
// 迭代访问节点的左孩子,并入栈
while (node) {
stack.push(node);
node = node.left;
}
// 如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
if (stack.length) {
node = stack.pop();
res.push(node.data);
node = node.right;
}
}
return res;
}
// 后序遍历的迭代实现
function iterateLastOrder(node, res) {
const stack = [];
let prev = null;
while (node || stack.length) {
while (node) {
stack.push(node);
node = node.left;
}
node = stack.pop();
if (node.right === null || node.right === prev) {
res.push(node.data);
prev = node;
node = null;
} else {
stack.push(node);
node = node.right;
}
}
return res;
}
// 获取树中的最小节点
function getMin(node) {
var current = this.root || node;
while (current.left) {
current = current.left;
}
return current;
}
// 获取树中的最大节点
function getMax(node) {
var current = this.root || node;
while (current.right) {
current = current.right;
}
return current;
}
// 查找某个数据
function find(data) {
var current = this.root;
while (current) {
if (data === current.data) {
return current;
} else if (data < current.data) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
// 删除某个数据
function remove(data) {
removeNode(this.root, data);
}
// 删除节点方法
function removeNode(node, data) {
if (node === null) {
return null;
}
if (data === node.data) {
// 叶子节点
if (node.left === null && node.right === null) {
return null;
}
// 只有右节点
if (node.left === null) {
return node.right;
}
// 只有左节点
if (node.right === null) {
return node.left;
}
// 左右节点都有,记住删除后要替换成右子树的最小节点
var tempNode = getMin(node.right); // 找到右子树的最小节点
node.data = tempNode.data;
node.right = removeNode(node.right, tempNode.data);
return node;
} else if (data < node.data) {
node.left = removeNode(node.left, data);
return node;
} else {
node.right = removeNode(node.right, data);
return node;
}
}
var bst = new Bst();
bst.insert(23);
bst.insert(45);
bst.insert(16);
bst.insert(37);
bst.insert(3);
bst.insert(99);
bst.insert(22);
console.log("前序遍历:", bst.preOrder(bst.root, []));
console.log("中序遍历:", bst.inOrder(bst.root, []));
console.log("后序遍历:", bst.lastOrder(bst.root, []));
console.log("最小节点:", bst.getMin());
console.log("最大节点:", bst.getMax());
bst.remove(16);
console.log("中序遍历:", bst.inOrder(bst.root, []));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# 二叉树的前序遍历(DFS)✅
// 递归解法
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 为树高,递归调用栈深度
var preorderTraversal = function(root) {
const res = [];
const preOrder = (node) => {
if (node) {
res.push(node.val);
preOrder(node.left);
preOrder(node.right);
}
};
preOrder(root);
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 迭代解法,使用栈,虽然和递归解法复杂度一样,但是迭代写法可以避免栈溢出
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 是树的高度,如果算上结果数组的话,就应该是 O(n),因为 n ≥ h 恒成立
var preorderTraversal = function(root) {
if (root === null) return [];
const res = [];
const stack = [root];
while (stack.length) {
const node = stack.pop();
res.push(node.val);
if (node.right) {
stack.push(node.right);
}
if (node.left) {
stack.push(node.left);
}
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 二叉树的中序遍历(DFS)✅
// 递归解法
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 为树高,递归调用栈深度
var inorderTraversal = function(root) {
const res = [];
const inOrder = (node) => {
if (node) {
inOrder(node.left);
res.push(node.val);
inOrder(node.right);
}
};
inOrder(root);
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 迭代解法,使用栈,虽然和递归解法复杂度一样,但是迭代写法可以避免栈溢出
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 是树的高度,如果算上结果数组的话,就应该是 O(n),因为 n ≥ h 恒成立
var inorderTraversal = function(root) {
const res = [];
const stack = [];
let cur = root;
while (cur || stack.length) {
// 内层循环:一路向左,把路径上的节点全部压栈
while (cur) {
stack.push(cur);
cur = cur.left;
}
// 走到了最左侧的叶子(cur 变成 null),开始回溯
cur = stack.pop(); // 弹出最左侧节点,这是当前要访问的节点
res.push(cur.val); // 访问它
cur = cur.right; // 转向它的右子树,重复整个过程
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 二叉树的后序遍历(DFS)✅
// 递归解法
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 为树高,递归调用栈深度
var postorderTraversal = function(root) {
const res = [];
const postOrder = (node) => {
if (node) {
postOrder(node.left);
postOrder(node.right);
res.push(node.val);
}
};
postOrder(root);
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 迭代解法,使用栈,虽然和递归解法复杂度一样,但是迭代写法可以避免栈溢出
// 时间复杂度是 O(n) —— 每个节点访问一次
// 空间复杂度是 O(h) —— h 是树的高度,如果算上结果数组的话,就应该是 O(n),因为 n ≥ h 恒成立
var postorderTraversal = function(root) {
if (root === null) return [];
const res = [];
const stack = [root];
while (stack.length) {
const node = stack.pop();
res.push(node.val); // 得到 "根 → 右 → 左" 的顺序
if (node.left) {
stack.push(node.left);
}
if (node.right) {
stack.push(node.right);
}
}
return res.reverse(); // 最后统一反转一次,得到 "左 → 右 → 根"
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 二叉树的层序遍历(BFS)✅
// 广度优先遍历
// 时间复杂度是 O(n),优化前后空间复杂度都是 O(n),因为要把每层的值存下来
var levelOrder = function(root) {
if (root === null) return [];
const queue = [root];
const res = [];
let head = 0; // 头指针
// while (queue.length) {
while (head < queue.length) {
const temp = [];
// let levelSize = queue.length;
let levelSize = queue.length - head; // 当前层节点数
while (levelSize--) {
// const node = queue.shift(); // shift 会导致整体搬移,复杂度是 O(n),会让算法整体的时间复杂度变为 O(n²)
const node = queue[head++]; // 头指针后移,代替 shift()
temp.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
res.push(temp);
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 二叉树的最大深度 ✅
// 递归,先递归拿到左子树深度,再拿到右子树深度,最后返回 "两者最大值 + 1(算上自己这一层)"
// 时间复杂度 —— O(n),每个节点计算一次
// 空间复杂度 —— O(h),h 是树的高度,递归调用栈深度等于树高
var maxDepth = function(root) {
if (root === null) return 0;
const left = maxDepth(root.left);
const right = maxDepth(root.right);
return Math.max(left, right) + 1;
};
2
3
4
5
6
7
8
9
// 深度优先遍历,把节点和它所在层的深度一起打包压栈,这样每次弹出节点时,直接就知道它在第几层,省去了递归回溯时 "自动累加1" 的过程
// 时间复杂度 —— O(n)
// 空间复杂度 —— O(h),h 是树的高度,栈中同时存在的节点数不超过树高
var maxDepth = function(root) {
if (root === null) return 0;
let depth = 0;
const stack = [{ node: root, depth: 1 }]; // 把节点和它当前的深度打包存入栈
while (stack.length) {
const { node, depth: curDepth } = stack.pop();
depth = Math.max(depth, curDepth); // 每次弹出就更新最大深度
if (node.left) stack.push({ node: node.left, depth: curDepth + 1 });
if (node.right) stack.push({ node: node.right, depth: curDepth + 1 });
}
return depth;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 广度优先遍历,逐层处理节点,每处理完一整层深度加一,处理完所有层后返回总深度
// 时间复杂度 —— 优化前时间复杂度是 O(n²),受到 shift() 的影响;优化后时间复杂度是 O(n),每个节点只访问一次
// 空间复杂度 —— 优化前空间复杂度是 O(w),w 为某一层最多的节点数,因为不像层序遍历那样需要存每层的值;优化后空间复杂度是 O(n),因为树的节点一直都在数组里,属于是拿空间换了时间
var maxDepth = function(root) {
if (root === null) return 0;
const queue = [root];
let depth = 0;
let head = 0; // 头指针,代替 shift()
// while (queue.length) {
while (head < queue.length) {
// let levelSize = queue.length; // 当前层的节点数
let levelSize = queue.length - head; // 当前层节点数
while (levelSize--) {
// const node = queue.shift(); // shift 会导致整体搬移,复杂度是 O(n),会让算法整体的时间复杂度变为 O(n²)
const node = queue[head++]; // 头指针后移代替 shift()
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
depth++;
}
return depth;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 二叉树的最小深度 ✅
// 递归
// 时间复杂度是 O(n),每个节点访问一次
// 空间复杂度是 O(h),h 为树高
var minDepth = function(root) {
if (root === null) return 0;
const left = minDepth(root.left);
const right = minDepth(root.right);
if (left > 0 && right > 0) {
return Math.min(left, right) + 1;
} else if (left > 0) {
return left + 1;
} else if (right > 0) {
return right + 1;
} else {
return 1;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 深度优先遍历
// 时间和空间复杂度都是 O(n),shift() 会影响性能,使用头指针优化
var minDepth = function(root) {
if (root === null) return 0;
const queue = [[root, 1]];
let head = 0;
while (head < queue.length) {
// const [node, depth] = queue.shift();
const [node, depth] = queue[head++];
if (node.left === null && node.right === null) return depth;
if (node.left) queue.push([node.left, depth + 1]);
if (node.right) queue.push([node.right, depth + 1]);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
// 广度优先遍历
// 时间和空间复杂度都是 O(n),shift() 会影响性能,使用头指针优化
var minDepth = function(root) {
if (root === null) return 0;
const queue = [root];
let depth = 1;
let head = 0;
while (head < queue.length) {
// let levelSize = queue.length;
let levelSize = queue.length - head;
while (levelSize--) {
// const node = queue.shift();
const node = queue[head++];
if (node.left === null && node.right === null) return depth;
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
depth++;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 路径总和 ✅
// 递归,每向下走一步,就把当前节点的值从 targetSum 中减掉,相当于把 "还需要凑多少" 传下去。到达叶子节点时,剩余值等于叶子节点值说明找到了路径。
// 时间复杂度是 O(n),每个节点访问一次
// 空间复杂度是 O(h),h 为树高,递归调用栈深度
var hasPathSum = function(root, targetSum) {
if (root === null) return false;
if (root.left === null && root.right === null) return targetSum === root.val;
return (
hasPathSum(root.left, targetSum - root.val) ||
hasPathSum(root.right, targetSum - root.val)
);
};
2
3
4
5
6
7
8
9
10
11
// 深度优先遍历,用栈做 DFS,把 "路径累加和" 直接存在节点的 val 上。
// 每次把子节点入栈前,先把父节点的值加到子节点的值里,这样到达叶子时,叶子的 val 就等于从根到它的路径总和,直接和 targetSum 比较即可。
// 注意: 这个写法会修改原始树的节点值,如果题目要求不能修改输入则不适用。
// 时间复杂度是 O(n),每个节点访问一次
// 空间复杂度是 O(h),栈中最多存树高个节点
var hasPathSum = function(root, targetSum) {
if (root === null) return false;
const stack = [root];
while (stack.length) {
const node = stack.pop();
if (node.left === null && node.right === null) {
if (node.val === targetSum) return true;
}
if (node.left) {
node.left.val += node.val;
stack.push(node.left);
}
if (node.right) {
node.right.val += node.val;
stack.push(node.right);
}
}
return false;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 广度优先遍历,也是把路径累加和存在 val 上,区别只是把栈换成了队列,变成逐层的 BFS 遍历。
// 因为只是找是否存在而不是找最短路径,BFS 相对 DFS 没有提前终止的优势,两者都要遍历到叶子才能判断。
// 同样会修改原始树的节点值。另外和最小深度一样,如果使用 shift() 未做头指针优化,实际时间复杂度是 O(n²),优化后会变为 O(n)。
// 时间复杂度是 O(n),每个节点访问一次
// 空间复杂度是 O(w),w 为树的最大宽度
var hasPathSum = function(root, targetSum) {
if (root === null) return false;
const queue = [root];
let head = 0;
while (head < queue.length) {
let levelSize = queue.length - head;
while (levelSize--) {
// const node = queue.shift();
const node = queue[head++];
if (node.left === null && node.right === null) {
if (node.val === targetSum) return true;
}
if (node.left) {
node.left.val += node.val;
queue.push(node.left);
}
if (node.right) {
node.right.val += node.val;
queue.push(node.right);
}
}
}
return false;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 翻转二叉树 ✅
// 后序遍历:先翻转左右子树,再交换当前节点的左右子树
// 时间复杂度是 O(n),每个节点访问一次
// 空间复杂度是 O(h),h 为树的高度
var invertTree = function(root) {
// 空节点不需要翻转,也是递归的终止条件
if (root === null) return root;
// 递归翻转当前节点的左右子树
const left = invertTree(root.left);
const right = invertTree(root.right);
// 交换翻转后的左右子树;该操作会修改原二叉树
root.left = right;
root.right = left;
return root;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 相同的树 ✅
// 同时遍历两棵树,比较对应位置的结构和节点值
// 时间复杂度是 O(n),最坏需要比较所有节点
// 空间复杂度是 O(h),h 为树的高度
var isSameTree = function(p, q) {
// 两个节点都为空,说明当前位置的结构相同
if (p === null && q === null) return true;
// 只有一个节点为空,或者两个节点的值不同,说明两棵树不同
if (p === null || q === null || p.val !== q.val) return false;
// 左子树和左子树、右子树和右子树都相同,整棵树才相同
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
};
2
3
4
5
6
7
8
9
10
11
12
13
# 对称二叉树 ✅
// 递归比较二叉树中互为镜像的节点
// 时间复杂度是 O(n),最坏需要访问所有节点
// 空间复杂度是 O(h),h 为树的高度
var isSymmetric = function(root) {
// 判断两个节点及其子树是否互为镜像
function symmetryTree(l, r) {
// 镜像位置都为空,说明当前位置对称
if (l === null && r === null) return true;
// 镜像位置只有一个节点为空,说明结构不对称
if (l === null || r === null) return false;
// 当前节点值相同,并且外侧和内侧的子树分别互为镜像
return (
l.val === r.val &&
symmetryTree(l.left, r.right) &&
symmetryTree(l.right, r.left)
);
}
// 从根节点开始,将整棵树与自身进行镜像比较
return symmetryTree(root.left, root.right);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 平衡二叉树
var isBalanced = function(root) {
return checkTree(root) !== -1;
};
function checkTree(root) {
if (root === null) return 0;
const left = checkTree(root.left);
if (left === -1) return -1;
const right = checkTree(root.right);
if (right === -1) return -1;
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
}
2
3
4
5
6
7
8
9
10
11
12
# 验证二叉搜索树
方法一:中序遍历
二叉搜索树中序遍历得到的序列一定是升序的。
var isValidBST = function(root) {
let stack = [];
let inorder = -Infinity;
while (stack.length || root !== null) {
while (root !== null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (root.val <= inorder) {
// 如果当前节点的值小于等于前一个中序遍历到的节点的值,说明不是二叉搜索树
return false;
}
inorder = root.val;
root = root.right;
}
return true;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
方法二:递归
var isValidBST = function(root) {
return check(root, -Infinity, Infinity);
};
function check(root, leftVal, rightVal) {
if (root === null) return true;
if (root.val <= leftVal || root.val >= rightVal) return false;
return (
check(root.left, leftVal, root.val) && check(root.right, root.val, rightVal)
);
}
2
3
4
5
6
7
8
9
10
# 二叉树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
if (root === null || p === root || q === root) return root;
let left = lowestCommonAncestor(root.left, p, q);
let right = lowestCommonAncestor(root.right, p, q);
if (left === null) return right;
if (right === null) return left;
return root;
};
2
3
4
5
6
7
8
# 二叉搜索树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
if (root === null) return root;
if (p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q);
}
if (p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
};
2
3
4
5
6
7
8
9
10
var lowestCommonAncestor = function(root, p, q) {
while (root) {
if (p.val < root.val && q.val < root.val) {
root = root.left;
} else if (p.val > root.val && q.val > root.val) {
root = root.right;
} else {
break;
}
}
return root;
};
2
3
4
5
6
7
8
9
10
11
12
# 二叉树最大宽度
var widthOfBinaryTree = function(root) {
const queue = [[root, 1n]];
let res = -1;
while (queue.length) {
let levelSize = queue.length;
res = Math.max(res, Number(queue[queue.length - 1][1] - queue[0][1] + 1n));
while (levelSize--) {
const [node, index] = queue.shift();
if (node.left) queue.push([node.left, index * 2n]);
if (node.right) queue.push([node.right, index * 2n + 1n]);
}
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14



