# 二叉树相关算法
# 1. 剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字
前序遍历 preorder = [3, 9, 20, 15, 7] 中序遍历 inorder = [9, 3, 15, 20, 7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
/**
* 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 || !inorder.length) return null;
// 前序遍历:根 -> 左 -> 右
// 中序遍历:左 -> 根 -> 右
const rootValue = preorder[0];
const node = new TreeNode(rootValue);
// index有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
let index = 0,
len = inorder.length;
for (; index < len; i++) {
if (inorder[index] === rootValue) {
break;
}
}
// 获取前序和中序所有的左节点
node.left = buildTree(preorder.slice(1, index + 1), inorder.slice(0, index));
// 获取前序和中序所有的右节点
node.right = buildTree(preorder.slice(index + 1), inorder.slice(index + 1));
return node;
}
# 98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 1:
输入:
2
/ \
1 3
输出: true
var isValidBST = function(root) {
function helper(root, min, max) {
if (root == null) return true;
// 在(min, max) 区间的数才是正常的
if (root.val <= min || root.val >= max) return false;
// 左子树节点的数范围应该在(min, root.val) 右子树节点的数范围应该在(root.val, max);
return helper(root.left, min, root.val) && helper(root.right, root.val, max);
}
// 输入的值应该在-Infinity~Infinity之间才算对
return helper(root, -Infinity, Infinity);
}
# 剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1, 2, 2, 3, 4, 4, 3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1, 2, 2, null, 3, null, 3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function(root) {
function helper(root) {
return root == null ? true : compare(root.left, root.right);
}
function compare(leftNode, rightNode) {
if (leftNode === null && rightNode === null) return true;
// 只要左右子树中,有一个空的节点,或者左右节点值不一样,那就不是对称的
if (leftNode === null || rightNode === null || leftNode.val !== rightNode.val) return false;
// 递归比较子节点
return compare(leftNode.left, rightNode.right) && compare(leftNode.right, rightNode.left);
}
return helper(root);
};
# 剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isBalanced = function(root) {
function helper(root) {
if (root == null) return true;
// 左子树和右子树的深度不超过1,然后再判断左子树的子树和右子树的子树的深度不超过1
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
function depth(root) {
if (root === null) return 0;
// 判断二叉树的深度在于左子树的深度和右子树深度,两个值的最大值,再+1
return Math.max(depth(root.left), depth(root.right)) + 1;
}
return helper(root);
};
# 剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
// 方法一:递归
function mirrorTree(root) {
// 1. 递归终结条件
if (root == null) return root;
// 2. 处理当前层逻辑
const temp = root.left;
// 3. 下探到下一层
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
// 方法二:借助栈来迭代
function mirrorTree(root) {
if (root == null) return null;
const stack = [root];
while (stack.length) {
// 1. 出栈
const node = stack.pop();
// 2. 添加子节点: 将 nodenode 左和右子节点入栈;
if (node.left !== null) stack.push(node.left);
if (node.right !== null) stack.push(node.right);
// 3. 交换子元素
const temp = node.left;
node.left = node.right;
node.right = temp;
}
return root;
}
# 剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3, 9, 20, null, null, 15, 7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
# 方法一:暴力递归
var maxDepth = function(root) {
if (root == null) return 0;
// 判断二叉树的深度在于左子树的深度和右子树深度,两个值的最大值,再+1
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
# 方法二:层次遍历
var maxDepth = function(root) {
if (root == null) return 0;
let height = 0,
queue = [root],
len = 0;
while (len = queue.length) {
while (len--) {
const node = queue.shift();
node.left !== null && queue.push(node.left);
node.right !== null && queue.push(node.right);
}
height++;
}
return height;
}
# 236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4]
示例 1:
输入: root = [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4], p = 5, q = 1 输出: 3 解释: 节点 5 和节点 1 的最近公共祖先是节点 3。 示例 2:
输入: root = [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4], p = 5, q = 4 输出: 5 解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(root, p, q) {
// 如果root是null,说明我们在这条寻址线路没有找到,我们返回null表示没找到
if (root === null) return null;
// 最近公共祖先节点可以为节点本身
if (root === p || root === q) return root;
// 左子树
const leftSubTree = lowestCommonAncestor(root.left, p, q);
// 右子树
const rightSubTree = lowestCommonAncestor(root.right, p, q);
// 左子树找不到,就返回右子树
if (leftSubTree === null) return rightSubTree;
// 右子树找不到,就返回左子树
if (rightSubTree === null) return leftSubTree;
return root;
};
# 144. 二叉树的前序遍历
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1, null, 2, 3]
1
\
2
/
3
输出: [1, 2, 3]
# 方法一:递归法
var preorderTraversal = function(root) {
if (root === null) return [];
const res = [];
function traverse(root) {
// 前序遍历: 根 -> 左 -> 右
if (root) {
// 先处理根节点
res.push(root.val);
// 再递归处理左节点
traverse(root.left);
// 然后递归处理右节点
traverse(root.right);
}
}
traverse(root);
return res;
}
# 方法二: 栈迭代法
var preorderTraversal = function(root) {
if (root === null) return [];
const res = [],
stack = [root];
while (stack.length) {
// 出栈
const node = stack.pop();
node != null && res.push(node.val);
// 栈是先进后出(后进先出),因此为了保证顺序是左->右,因此应该先右后左
node.right != null && stack.push(node.right);
node.left != null && stack.push(node.left);
}
return res;
}
# 94. 二叉树的中序遍历
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1, null, 2, 3]
1
\
2
/
3
输出: [1, 3, 2]
# 方法一: 递归法
var inorderTraversal = function(root) {
if (root == null) return [];
const res = [];
function inorderHelper(root) {
if (root !== null) {
// 左 -> 根 -> 右
root.left !== null && inorderHelper(root.left);
root.val !== null && res.push(root.val);
root.right !== null && inorderHelper(root.right);
}
}
inorderHelper(root);
return res;
}
# 方法二:栈迭代法
var inorderTraversal = function(root) {
if (root == null) return [];
const stack = [],
res = [];
while (stack.length || root != null) {
// 遍历左节点
while (root != null) {
stack.push(root);
root = root.left;
}
// 左节点遍历完毕,取值,再遍历右节点
root = stack.pop();
if (root.val != null) res.push(root.val);
if (root != null) root = root.right;
}
return res;
}
# 145. 二叉树的后序遍历
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1, null, 2, 3]
1
\
2
/
3
输出: [3, 2, 1]
# 方法一: 递归法
var postorderTraversal = function(root) {
if (root == null) return [];
const res = [];
function postorderHelper(root) {
if (root) {
// 左 -> 右 -> 中
if (root.left != null) postorderHelper(root.left);
if (root.right != null) postorderHelper(root.right);
if (root.val != null) res.push(root.val);
}
}
postorderHelper(root);
return res;
}
# 方法二:迭代法
var postorderTraversal = function(root) {
if (root == null) return [];
const res = [],
queue = [];
while (queue.length || root != null) {
if (root.val != null) res.unshift(root.val);
// 左 -> 右 -> 中
if (root.left != null) queue.push(root.left);
if (root.right != null) queue.push(root.right);
// 队列:先进先出
root = queue.pop();
}
return res;
}
# 102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if (root == null) return [];
let res = [],
queue = [root];
while (queue && queue.length) {
let cur = [],
temp = [];
while (queue && queue.length) {
// 从左往右压栈(先入后出)
const node = queue.shift();
if (node.val != null) cur.push(node.val);
if (node.left != null) temp.push(node.left);
if (node.right != null) temp.push(node.right);
}
queue = temp;
res.push(cur);
}
return res;
}
# 剑指 Offer 32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如: 给定二叉树: [3, 9, 20, null, null, 15, 7],
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
/**
* 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 [];
const res = [],
queue = [root];
while (queue.length && root != null) {
root = queue.shift();
if (root.val != null) res.push(root.val);
if (root.left != null) queue.push(root.left);
if (root.right != null) queue.push(root.right);
}
return res;
};
# 剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如: 给定二叉树: [3, 9, 20, null, null, 15, 7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
/**
* 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 res = [],
queue = [root];
while (queue && queue.length) {
const curr = [], // 用来保存本轮循环的结果
temp = []; // 用来缓存本轮循环后queue的结果
while (queue && queue.length) {
const node = queue.shift();
if (node.val != null) curr.push(node.val);
if (node.left != null) temp.push(node.left);
if (node.right != null) temp.push(node.right);
}
// 更新本轮循环后的queue
queue = temp;
// curr(数组)入栈
res.push(curr);
}
return res;
};
# 剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如: 给定二叉树: [3, 9, 20, null, null, 15, 7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
/**
* 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 res = [],
queue = [root];
while (queue && queue.length) {
const curr = [], // 用来存放每一轮循环后的结果
temp = []; // 用来缓存每一轮循环后的队列
while (queue && queue.length) {
const node = queue.shift();
if (node.val != null) curr.push(node.val);
if (node.right != null) temp.unshift(node.right);
if (node.left != null) temp.unshift(node.left);
}
console.log(temp);
queue = temp; // 更新每轮循环后的结果给队列
res.push(curr); // 每轮循环后的结果入栈
}
return res;
};
# 剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 4
# 方法一:栈遍历
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/
var kthLargest = function(root, k) {
if (root == null) return null;
const res = [],
queue = [root];
while (queue && queue.length) {
const node = queue.pop();
if (node.val != null) res.push(node.val);
if (node.left != null) queue.push(node.left);
if (node.right != null) queue.push(node.right);
}
const arr = res.sort((a, b) => a - b);
return arr[arr.length - k];
};
# 方法二:中序遍历
var kthLargest = function(root, k) {
if (root == null) return null;
const res = [];
function inorder(root) {
if (root != null) {
// 左 -> 根 -> 右
root.left != null && inorder(root.left);
root.val != null && res.push(root.val);
root.right != null && inorder(root.right);
}
}
inorder(root);
// 二叉搜素树中序排列后的结果就是有序的,第K大元素即为倒数res.length - k(索引)项
return res[res.length - k];
}
# 剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
/**
* @param {number[]} postorder
* @return {boolean}
*/
// 终止条件: 当 i >= j ,说明此子树节点数量 <= 1 ,无需判别正确性,因此直接返回 true ;
// 递推工作:
// 划分左右子树: 遍历后序遍历的 [i, j][i, j] 区间元素,寻找 第一个大于根节点 的节点,索引记为 mm 。此时,可划分出左子树区间 [i, m-1][i, m−1] 、右子树区间 [m, j - 1][m, j−1] 、根节点索引 jj 。
// 判断是否为二叉搜索树:
// 左子树区间 [i, m - 1][i, m−1] 内的所有节点都应 < postorder[j] 。而第 1. 划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
// 右子树区间 [m, j-1][m, j−1] 内的所有节点都应 > postorder[j] 。实现方式为遍历,当遇到 <= postorder[j] 的节点则跳出;则可通过 p = j判断是否为二叉搜索树。
function verifyPostorder(postorder) {
return traverse(postorder, 0, postorder.length - 1);
}
function traverse(postorder, i, j) {
// 说明此子树节点数量 <= 1 ,无需判别正确性,因此直接返回 true
if (i >= j) return true;
// 记录左子树指针
let leftIdx = i;
while (postorder[leftIdx] < postorder[j]) leftIdx++;
// 记录中间节点的指针
let middleIdx = leftIdx;
// 找出中间节点指针
while (postorder[leftIdx] > postorder[j]) leftIdx++;
return leftIdx === j && traverse(postorder, i, middleIdx - 1) && traverse(postorder, middleIdx, j - 1);
}
# 剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例: 给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
var pathSum = function(root, sum) {
const res = [];
if (root == null) return res;
function helper(node, target, path) {
// 初始化path
path.push(node.val);
// 1. 递归终止条件
if (node.left == null && node.right == null && target === node.val) {
res.push(path);
}
// 2. 处理当前层逻辑
if (node.left != null) {
// 3. 下探到下一层
// 更新target, path浅拷贝
helper(node.left, target - node.val, path.slice());
}
if (node.right != null) {
helper(node.right, target - node.val, path.slice());
}
}
helper(root, sum, []);
return res;
}
# 559. N叉树的最大深度
# 1. 深度优先
// 即递归求出每个子树的最大深度,再加上根节点
var maxDepth = function(root) {
if (root == null) return 0;
if (root.children == null) return 1;
let max = 0;
for (let i = 0; i < root.children.length; i++) {
let childDepth = maxDepth(root.children[i]);
// 对比之前的最大深度和现在的, 取最大值
max = Math.max(max, childDepth);
}
// 最后需要记得加上根节点
return max + 1;
}
# 2. 广度优先
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
例如,给定一个 3叉树 :
我们应返回其最大深度,3。
1
/ | \
3 2 4
/ \
5 6
// 算出总共有几层即可
var maxDepth = function(root) {
if (root == null) return 0;
let queue = [root],
level = 0,
length = 0;
while (length = queue.length) {
while (length--) {
// 队列出队
const node = queue.shift();
if (node.children) {
queue = queue.concat(node.children);
}
}
level++;
}
return level;
}
# 590. N叉树的后序遍历
给定一个 N 叉树,返回其节点值的后序遍历。
例如,给定一个 3叉树 :
1
/ | \
3 2 4
/ \
5 6
返回其后序遍历: [5, 6, 3, 2, 4, 1].
/**
* @param {Node} root
* @return {number[]}
*/
// 队列迭代法
var postorder = function(root) {
if (root == null) return [];
const res = [root.val],
queue = root.children;
while (queue.length) {
// 队列: 先进先出
const child = queue.pop();
if (child == null) continue;
if (child.val != null) res.unshift(child.val);
// 有儿子节点,那就全部放到队列中
if (child.children != null) queue.push(...child.children);
}
return res;
};
# 429. N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
1
/ | \
3 2 4
/ \
5 6
返回其层序遍历:
[
[1],
[3,2,4],
[5,6]
]
/**
* // Definition for a Node.
* function Node(val,children) {
* this.val = val;
* this.children = children;
* };
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if (root == null) return [];
let res = [],
queue = [root];
while (queue && queue.length) {
let cur = [],
temp = [];
while (queue && queue.length) {
// 从左往右压栈(先入后出)
const node = queue.shift();
if (node.val != null) cur.push(node.val);
if (node.children != null) temp.push(...node.children);
}
queue = temp;
res.push(cur);
}
return res;
}
# 最后
文中若有不准确或错误的地方,欢迎指出,有兴趣可以的关注下Github,一起学习呀~~
← 链表相关算法题 腾讯算法题-数组与字符串 →