0%

二叉树刷题

按照算法和数据结构进行分类,一起来刷题,用于自己在面试前查漏补缺。我的意向岗位是前端,选择用javascript来刷题,优点是动态语言,语法简单,缺点是遇见复杂数据结构会出现较难的写法,如堆、并查集,每题对应leetcode的题号。本篇是二叉树

专题部分

二叉树

94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

示例 1:

img

1
2
输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

1
2
输入:root = []
输出:[]

示例 3:

1
2
输入:root = [1]
输出:[1]

示例 4:

img

1
2
输入:root = [1,2]
输出:[2,1]

示例 5:

1
2
输入:root = [1,null,2]
输出:[1,2]

提示:

  • 树中节点数目在范围 [0, 100] 内
  • -100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

递归算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
let ans = [];
let inOrder = function (root) {
if (!root) return;
inOrder(root.left);
ans.push(root.val);
inOrder(root.right);
}
inOrder(root);
return ans;
};

迭代法

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* 迭代法,栈,非递归调用
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
let res = [];
let stack = [];
// 取根节点为目标节点开始遍历
let cur = root;
while (stack.length || cur) {
// 左孩子入栈 -> 直至左孩子为空的节点
while (cur) {
stack.push(cur);
cur = cur.left;
}
// 节点出栈 -> 访问该节点
cur = stack.pop();
res.push(cur.val);
// 以右孩子为目标节点,再依次执行
cur = cur.right;
}
return res;
};

使用了递归的骚方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* 迭代法,栈,非递归调用
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function(root) {
if (!root) return [];
return inorderTraversal(root.left).concat(root.val, inorderTraversal(root.right));
};

96. 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

img

1
2
输入:n = 3
输出:5

示例 2:

1
2
输入:n = 1
输出:1

提示:

  • 1 <= n <= 19

动态规划

给定一个有序序列 $$1 \cdots n$$,为了构建出一棵二叉搜索树,我们可以遍历每个数字$$i$$,将该数字作为树根,将 $$1 \cdots (i-1)$$ 序列作为左子树,将 $$(i+1) \cdots n$$ 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。

在上述构建的过程中,由于根的值不同,因此我们能保证每棵二叉搜索树是唯一的。

由此可见,原问题可以分解成规模较小的两个子问题,且子问题的解可以复用。因此,我们可以想到使用动态规划来求解本题。

题目要求是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:

$$G(n)$$: 长度为 $$n$$ 的序列能构成的不同二叉搜索树的个数。

$$F(i, n)$$: 以 $$i$$ 为根、序列长度为 $$n$$ 的不同二叉搜索树个数 $$(1 \leq i \leq n)$$。

可见,$$G(n)$$ 是我们求解需要的函数。

稍后我们将看到,$$G(n)$$ 可以从 $$F(i, n)$$ 得到,$$F(i, n)$$ 又会递归地依赖于 $$G(n)$$。

首先,根据上一节中的思路,不同的二叉搜索树的总数 $$G(n)$$,是对遍历所有 $$i (1 \le i \le n)$$的$$F(i, n)$$ 之和。换言之:

$$
G(n) = \sum_{i=1}^{n} F(i, n)\qquad \qquad (1)
$$
对于边界情况,当序列长度为 $$1$$(只有根)或为 $$0$$(空树)时,只有一种情况,即:

$$
G(0) = 1, \qquad G(1) = 1
$$
给定序列 $$1 \cdots n$$,我们选择数字 $$i$$ 作为根,则根为 $$i$$ 的所有二叉搜索树的集合是左子树集合和右子树集合的笛卡尔积,对于笛卡尔积中的每个元素,加上根节点之后形成完整的二叉搜索树,如下图所示:

img

举例而言,创建以 3 为根、长度为 7 的不同二叉搜索树,整个序列是 [1, 2, 3, 4, 5, 6, 7],我们需要从左子序列 [1, 2] 构建左子树,从右子序列 [4, 5, 6, 7]构建右子树,然后将它们组合(即笛卡尔积)。

对于这个例子,不同二叉搜索树的个数为 $$F(3, 7)$$。我们将 [1,2] 构建不同左子树的数量表示为 $$G(2)$$, 从 [4, 5, 6, 7] 构建不同右子树的数量表示为 $$G(4)$$,注意到 $$G(n)$$ 和序列的内容无关,只和序列的长度有关。于是,$$F(3,7) = G(2) \cdot G(4)$$。 因此,我们可以得到以下公式:

$$
F(i, n) = G(i-1) \cdot G(n-i) \qquad \qquad (2)
$$
将公式 $$(1)$$,$$(2)$$ 结合,可以得到 $$G(n)$$ 的递归表达式:

$$
G(n) = \sum_{i=1}^{n}G(i-1) \cdot G(n-i) \qquad \qquad (3)
$$
至此,我们从小到大计算 $$G$$ 函数即可,因为 $$G(n)$$ 的值依赖于 $$G(0) \cdots G(n-1)$$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @param {number} n
* @return {number}
*/
var numTrees = function(n) {
let G = new Array(n + 1).fill(0);
G[0] = 1, G[1] = 1;
for (let i = 2; i <= n; i++) {
for (let j = 1; j <= i; j++) {
// 以j 为根节点的数量
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
};

数学方法

事实上我们在方法一中推导出的 $$G(n)$$函数的值在数学上被称为卡塔兰数 $$C_n$$ 。卡塔兰数更便于计算的定义如下:

$$
C_0 = 1, \qquad C_{n+1} = \frac{2(2n+1)}{n+2}C_n
$$
证明过程可以参考上述文献,此处不再赘述。

1
2
3
4
5
6
7
8
9
10
11
/**
* @param {number} n
* @return {number}
*/
var numTrees = function(n) {
let C = 1;
for (let i = 0; i < n; i++) {
C = C * 2 * (2 * i + 1) / (i + 2);
}
return C;
};

98. 验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

1
2
3
4
5
输入:
2
/ \
1 3
输出: true

示例 2:

1
2
3
4
5
6
7
8
9
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。

如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
let helper = function(node, lower, upper){
if (node == null) return true;
if (node.val <= lower || node.val >= upper) return false;
return helper(node.left, lower, node.val) && helper(node.right, node.val, upper);
}
var isValidBST = function(root) {
return helper(root, -Infinity, Infinity);
};

中序遍历是从小到大的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root.val <= inorder) {
return false;
}
inorder = root.val;
root = root.right;
}
return true;
};

102. 二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:
二叉树:[3,9,20,null,null,15,7],

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回其层序遍历结果:

1
2
3
4
5
[
[3],
[9,20],
[15,7]
]
1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if (root == null) return [];
let queue = [root];
let level = 0;
let number = [];
while (queue.length != 0) {
let l = queue.length;
number[level] = []
for (let i = 0; i < l; i++) {
let node = queue.shift();
number[level][i] = node.val;
if (node.left !== null) queue.push(node.left);
if (node.right !== null) queue.push(node.right);
}
level++;
}
return number;
};

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

给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。

示例 1:

img

1
2
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

1
2
Input: preorder = [-1], inorder = [-1]
Output: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorderinorder 均无重复元素
  • inorder 均出现在 preorder
  • preorder 保证为二叉树的前序遍历序列
  • inorder 保证为二叉树的中序遍历序列

利用二叉树前序遍历头部是根节点,后面左子树和右子树。中序遍历前面左子树中间根节点最后右子树特点,递归构建

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
// 递归出口
if (preorder.length <= 0) return null;
// 前序遍历第一个节点为根节点
let node = new TreeNode(preorder[0]);
// 找到中序遍历对应的节点
let i = inorder.indexOf(preorder[0]);
// 左节点递归构造子二叉树
node.left = buildTree(preorder.slice(1, i + 1), inorder.slice(0, i));
// 右节点递归构造子二叉树
node.right = buildTree(preorder.slice(i + 1, preorder.length), inorder.slice(i + 1, inorder.length));
// 返回根节点
return node;
};

使用哈希存储空间换时间

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
let preorder_qt;
let hashMap = {};
var buildTree = function(preorder, inorder) {
let len = inorder.length;
for(let i = 0;i<len;i++)
{
hashMap[inorder[i]] = i;
}
preorder_qt = preorder;
return dg(0, len);
};
let dg = function(in_left,in_right) {
if(in_left >= in_right)
return null;
let val = preorder_qt.shift();
let root = new TreeNode(val);
let index = hashMap[val];
root.left = dg(in_left, index);
root.right = dg(index + 1,in_right);
return root;
}

改用迭代法,使用栈实现构建

迭代法是一种非常巧妙的实现方法。

对于前序遍历中的任意两个连续节点 u 和 v,根据前序遍历的流程,我们可以知道 u 和 v 只有两种可能的关系:

  • v 是 u 的左儿子。这是因为在遍历到 u 之后,下一个遍历的节点就是 u 的左儿子,即 v;
  • u 没有左儿子,并且 v 是 u 的某个祖先节点(或者 u 本身)的右儿子。如果 u 没有左儿子,那么下一个遍历的节点就是 u 的右儿子。如果 u 没有右儿子,我们就会向上回溯,直到遇到第一个有右儿子(且 u 不在它的右儿子的子树中)的节点 $$u_a$$ ,那么 v 就是 $$u_a$$ 的右儿子。

归纳出算法流程:

  • 我们用一个栈和一个指针辅助进行二叉树的构造。初始时栈中存放了根节点(前序遍历的第一个节点),指针指向中序遍历的第一个节点;

  • 我们依次枚举前序遍历中除了第一个节点以外的每个节点。如果 index 恰好指向栈顶节点,那么我们不断地弹出栈顶节点并向右移动 index,并将当前节点作为最后一个弹出的节点的右儿子;如果 index 和栈顶节点不同,我们将当前节点作为栈顶节点的左儿子;

  • 无论是哪一种情况,我们最后都将当前的节点入栈。

最后得到的二叉树即为答案。

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function (preorder, inorder) {
if (preorder.length === 0) {
return null;
}
const treeNodeList = [];
const root = new TreeNode(preorder[0]);
treeNodeList.push(root);
// j从0开始,为中序遍历的序数
let j = 0;
for (let i = 1; i < preorder.length; i++) {
const current = new TreeNode(preorder[i]);
let curParent;

// 中序遍历至当前根结点时,右子树根结点确定,当前根结点出栈
while (treeNodeList.length !== 0 && treeNodeList[treeNodeList.length - 1].val === inorder[j]) {
curParent = treeNodeList[treeNodeList.length - 1];
treeNodeList.length--;
j++;
}
if (curParent) {
curParent.right = current;
} else {
treeNodeList[treeNodeList.length - 1].left = current;
}
treeNodeList.push(current);
}
return root;
};

114. 二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:

img

1
2
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]

示例 2:

1
2
输入:root = []
输出:[]

示例 3:

1
2
输入:root = [0]
输出:[0]

提示:

  • 树中结点数在范围 [0, 2000]
  • -100 <= Node.val <= 100

进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?

维护上一个访问的节点 prev,每次访问一个节点时,令当前访问的节点为 curr,将 prev 的左子节点设为 null 以及将 prev 的右子节点设为 curr,然后将 curr 赋值给 prev,进入下一个节点的访问,直到遍历结束。需要注意的是,初始时 prev 为 null,只有在 prev 不为 null 时才能对 prev 的左右子节点进行更新。

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {void} Do not return anything, modify root in-place instead.
*/
var flatten = function(root) {
if(root == null) return null;
let stack = [],
node = null,
cur = new TreeNode('head');
stack.push(root);
while (stack.length) {
node = stack.pop();
cur.right = node;
cur.left = null;
if(node.right){
stack.push(node.right);
}
if(node.left){
stack.push(node.left);
}
cur = cur.right;
}
return root;
};

要想做到O(1) 额外空间,不能存储已遍历节点,只能保存上一个指针

注意到前序遍历访问各节点的顺序是根节点、左子树、右子树。如果一个节点的左子节点为空,则该节点不需要进行展开操作。如果一个节点的左子节点不为空,则该节点的左子树中的最后一个节点被访问之后,该节点的右子节点被访问。该节点的左子树中最后一个被访问的节点是左子树中的最右边的节点,也是该节点的前驱节点。因此,问题转化成寻找当前节点的前驱节点。

具体做法是,对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {void} Do not return anything, modify root in-place instead.
*/
var flatten = function(root) {
let curr = root;
while (curr !== null) {
if (curr.left !== null) {
const next = curr.left;
let predecessor = next;
while (predecessor.right !== null) {
predecessor = predecessor.right;
}
predecessor.right = curr.right;
curr.left = null;
curr.right = next;
}
curr = curr.right;
}
};

124. 二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和

示例 1:

img

1
2
3
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

示例 2:

img

1
2
3
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

从计算每个节点开始的路径,找到最大路径和

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxPathSum = function(root) {
// 纪录保持者
let maxSum = Number.MIN_SAFE_INTEGER;
function dfs (root) {
// 递归的出口
if (!root) return 0;
const left = Math.max(0, dfs(root.left));
const right = Math.max(0, dfs(root.right));
// 当前子树的maxSum挑战最大值
maxSum = Math.max(maxSum, left + root.val + right);
// 向父节点提供的最大和,要包括自己
return root.val + Math.max(left, right);
}
// 递归的入口
dfs(root);
return maxSum;
};

226. 翻转二叉树

翻转一棵二叉树。

示例:

输入:

1
2
3
4
5
     4
/ \
2 7
/ \ / \
1 3 6 9

输出:

1
2
3
4
5
     4
/ \
7 2
/ \ / \
9 6 3 1

备注:
这个问题是受到 Max Howell 原问题 启发的 :

谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。

递归翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function (root) {
if (!root) return null;
let temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
};

利用栈实现翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function (root) {
if (!root) return root;
const stack = [root];
while (stack.length) {
const node = stack.pop();
[node.left, node.right] = [node.right, node.left];
if (node.left) stack.push(node.left);
if (node.right) stack.push(node.right);
}
return root;
};

437. 路径总和 III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

img

1
2
3
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。

示例 2:

1
2
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3

提示:

  • 二叉树的节点个数的范围是 [0,1000]
  • -109 <= Node.val <= 109
  • -1000 <= targetSum <= 1000

创建一个辅助函数计算

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} sum
* @return {number}
*/
var pathSum = function(root, sum) {
if (root == null) return 0;
// 自己为开头的路径数
let pathImLeading = count(root, sum);
// 左边路径总数(相信他能算出来)
let leftPathSum = pathSum(root.left, sum);
// 右边路径总数(相信他能算出来)
let rightPathSum = pathSum(root.right, sum);
return pathImLeading + leftPathSum + rightPathSum;
};
function count(node, sum) {
if (node == null) return 0;
// 我自己能不能独当一面,作为一条单独的路径呢?
let isMe = (node.val == sum) ? 1 : 0;
// 左边的小老弟,你那边能凑几个 sum - node.val 呀?
let leftBrother = count(node.left, sum - node.val);
// 右边的小老弟,你那边能凑几个 sum - node.val 呀?
let rightBrother = count(node.right, sum - node.val);
// 我这能凑这么多个
return isMe + leftBrother + rightBrother;
};

简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} sum
* @return {number}
*/
var pathSum = function(root, sum) {
return root ? pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum): 0;
}
// 辅函数
function pathSumStartWithRoot(root, sum) {
if (!root) {
return 0;
}
let count = root.val == sum ? 1: 0;
count += pathSumStartWithRoot(root.left, sum - root.val);
count += pathSumStartWithRoot(root.right, sum - root.val);
return count;
}

判断该二叉树是否为搜索二叉树和完全二叉树

题目描述

给定一棵二叉树,已经其中没有重复值的节点,请判断该二叉树是否为搜索二叉树和完全二叉树。

示例1

输入

1
{2,1,3}

返回值

1
[true,true]

备注:

1
n≤500000

使用

采用迭代法分别中序遍历和广度优先遍历

1
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
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/

/**
*
* @param root TreeNode类 the root
* @return bool布尔型一维数组
*/
function judgeIt( root ) {
// write code here
let res = [true, true];
let pre = -Infinity;
let stack = [];
let cur = root;
while (stack.length || cur) {
while (cur) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
if (cur.val < pre) {
res[0] = false;
break;
}
pre = cur.val;
cur = cur.right;
}

let queue = [root];
while (queue[0] !== null) {
let node = queue.shift();
queue.push(node.left);
queue.push(node.right);
}
while(queue.length && queue[0] === null) {
queue.shift();
}
if (queue.length) res[1] = false;
return res;
}
module.exports = {
judgeIt : judgeIt
};

543. 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
给定二叉树

1
2
3
4
5
    1
/ \
2 3
/ \
4 5

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

DFS遍历,返回节点深度,然后左子树深度+右子树深度+1即为直径

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var diameterOfBinaryTree = function(root) {
if (root === null) return 0;
let ans = 0;

// 用根节点的深度来求任意节点之间的最短路径
let maxDepth = (root) => {
if (root === null) return 0;
let L = maxDepth(root.left);
let R = maxDepth(root.right);
ans = Math.max(ans, L + R); // 随时遍历,保证路径长度最长
return Math.max(L, R) + 1;
}
maxDepth(root);
return ans;
};

538. 把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同

示例 1:

img

1
2
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

示例 2:

1
2
输入:root = [0,null,1]
输出:[1,null,1]

示例 3:

1
2
输入:root = [1,0,2]
输出:[3,3,2]

示例 4:

1
2
输入:root = [3,2,4,1]
输出:[7,9,4,10]

提示:

  • 树中的节点数介于 0104 之间。
  • 每个节点的值介于 -104104 之间。
  • 树中的所有值 互不相同
  • 给定的树为二叉搜索树。

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var convertBST = function(root) {
if (!root) return root;
let sum = 0;
let dfs = function (root) {
if (root == null) return;
dfs(root.right);
sum += root.val;
root.val = sum;
dfs(root.left);
}
dfs(root);
return root;
};

迭代法

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var convertBST = function(root) {
let sum = 0;
let stack = [];
let cur = root;
while (cur || stack.length) {
while (cur) {
stack.push(cur);
cur = cur.right;
}
cur = stack.pop();
sum += cur.val;
cur.val = sum;
cur = cur.left;
}
return root;
};

617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
输入: 
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7

注意: 合并必须从两个树的根节点开始。

需要计算和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} t1
* @param {TreeNode} t2
* @return {TreeNode}
*/
var mergeTrees = function(t1, t2) {
if (t1 === null && t2 === null) return null;
num1 = t1 === null ? 0 : t1.val;
num2 = t2 === null ? 0 : t2.val;
let sum = num1 + num2;
let node = new TreeNode(sum);
node.left = mergeTrees(t1 && t1.left ? t1.left : null, t2 && t2.left ? t2.left : null);
node.right = mergeTrees(t1 && t1.right ? t1.right : null, t2 && t2.right ? t2.right : null);
return node;
};

872. 叶子相似的树

请考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列

img

举个例子,如上图所示,给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。

如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。

如果给定的两个根结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。

示例 1:

img

1
2
输入:root1 = [3,5,1,6,2,9,8,null,null,7,4], root2 = [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8]
输出:true

示例 2:

1
2
输入:root1 = [1], root2 = [1]
输出:true

示例 3:

1
2
输入:root1 = [1], root2 = [2]
输出:false

示例 4:

1
2
输入:root1 = [1,2], root2 = [2,2]
输出:true

示例 5:

img

1
2
输入:root1 = [1,2,3], root2 = [1,3,2]
输出:false

提示:

给定的两棵树可能会有 1 到 200 个结点。
给定的两棵树上的值介于 0 到 200 之间。

可以使用DFS,获取叶子节点的序列,进行比对,我选择了相对麻烦的迭代法,不需要保存叶子节点的序列,但需要用到队列结构占用空间

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root1
* @param {TreeNode} root2
* @return {boolean}
*/
var leafSimilar = function(root1, root2) {
// 两个栈同步迭代
let stack1 = [root1], stack2 = [root2];
// 两个栈非空
while (stack1.length !== 0 && stack2.length !== 0) {
// 栈顶节点
let node1 = stack1.pop(), node2 = stack2.pop();
// 不是叶子节点
while (node1.left || node1.right) {
if (node1.left) {
// 左孩子不为空时,如果右孩子也不为空,则将其推入栈作为下次的起始点。然后继续往左搜寻叶子节点。
if (node1.right) stack1.push(node1.right);
node1 = node1.left;
} else {
// 左孩子为空时,那么右孩子一定不为空,则出发往右孩子寻找第一个叶子节点。
node1 = node1.right;
}
}
// 同样的操作对树2进行一遍
while (node2.left || node2.right) {
if (node2.left) {
// 左孩子不为空时,如果右孩子也不为空,则将其推入栈作为下次的起始点。然后继续往左搜寻叶子节点。
if (node2.right) stack2.push(node2.right);
node2 = node2.left;
} else {
// 左孩子为空时,那么右孩子一定不为空,则出发往右孩子寻找第一个叶子节点。
node2 = node2.right;
}
}
// 此时node1与node2分别指向树1与树2的叶子节点
if (node1.val !== node2.val) return false;
}
// 到此两种情况:
// 1. 两个栈都空了,并且叶子节点都相等,因此返回true
// 2. 只有一颗树空了,证明另一棵树一定还有别的叶子节点, 返回false;
return stack1.length === 0 && stack2.length === 0;
};

897. 递增顺序搜索树

给你一棵二叉搜索树,请你 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

示例 1:

img

1
2
输入:root = [5,3,6,2,4,null,8,1,null,null,null,7,9]
输出:[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]

示例 2:

img

1
2
输入:root = [5,1,7]
输出:[1,null,5,null,7]

提示:

  • 树中节点数的取值范围是 [1, 100]
  • 0 <= Node.val <= 1000

采用中序遍历深度优先遍历

1
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
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
* 递归
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var increasingBST = function(root) {
let pre, head;
function inOrderTravel (node) {
if(!node) {return; }
inOrderTravel(node.left);
if (pre) {
pre.right = node;
node.left = null;
pre = node;
} else {
head = node;
pre = node;
}
inOrderTravel(node.right);
}
inOrderTravel(root);
return head;
};

938. 二叉搜索树的范围和

给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。

示例 1:

img

1
2
输入:root = [10,5,15,3,7,null,18], low = 7, high = 15
输出:32

示例 2:

img

1
2
输入:root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
输出:23

提示:

  • 树中节点数目在范围 [1, 2 * 104] 内
  • 1 <= Node.val <= 105
  • 1 <= low <= high <= 105
  • 所有 Node.val 互不相同

按深度优先搜索的顺序计算范围和。记当前子树根节点为 \textit{root}root,分以下四种情况讨论:

  • root 节点为空

    返回 0。

  • root 节点的值大于high

    由于二叉搜索树右子树上所有节点的值均大于根节点的值,即均大于 high,故无需考虑右子树,返回左子树的范围和。

  • root 节点的值小于low

    由于二叉搜索树左子树上所有节点的值均小于根节点的值,即均小于 low,故无需考虑左子树,返回右子树的范围和。

  • root 节点的值在 [low,high] 范围内

    此时应返回 root 节点的值、左子树的范围和、右子树的范围和这三者之和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} L
* @param {number} R
* @return {number}
*/
var rangeSumBST = function(root, L, R) {
if (!root) return 0;
if (root.val < L) return rangeSumBST(root.right, L, R);
if (root.val > R) return rangeSumBST(root.left, L, R);
return root.val + rangeSumBST(root.left, L, R) + rangeSumBST(root.right, L, R);
};
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道