定义
二叉树的定义
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
- 根节点:二叉树最顶层的节点
- 分支节点:除了根节点以外且拥有叶子节点的节点
- 叶子节点:没有子节点
满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
完全二叉树
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。从满二叉树和完全二叉树的定义可以看出, 满二叉树是完全二叉树的特殊形态, 即如果一棵二叉树是满二叉树, 则它必定是完全二叉树。堆一般都由完全二叉树实现。
接下来是笔者自己对leetcode中关于二叉树的一些题做的分类,分三个系列。
二叉树的遍历
二叉树的前序遍历
给定一个二叉树,返回它的前序遍历。
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
来源: leetcode-cn.com/problems/bi…
递归实现:
var preorderTraversal = function(root) {
if(root === null) return [];
let arr = [];
let help = (root) => {
if(!root) return;
arr.push(root.val);
help(root.left);
help(root.right);
}
help(root);
return arr;
};
迭代实现:
var preorderTraversal = function(root) {
let nums = [];
let stack = [];
if (root) stack.push(root);
while (stack.length) {
root = stack.pop();
nums.push(root.val);
root.right && stack.push(root.right);
root.left && stack.push(root.left);
}
return nums;
};
二叉树的中序遍历
给定一个二叉树,返回它的中序遍历。
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
来源: leetcode-cn.com/problems/bi…
递归实现:
var inorderTraversal = function(root) {
let arr = [];
let traverse = (node) => {
if(!node) return null;
traverse(node.left);
arr.push(node.val);
traverse(node.right)
}
traverse(root)
return arr;
};
迭代实现:
var inorderTraversal = function(root) {
if(root === null) return root;
let res = [],stack = [];
stack.push(root);
while (stack.length){
while(root !== null){
stack.push(root);
root = root.left;
}
let node = stack.pop()
res.push(node.val);
root = node.right;
}
//根节点添加了两次
return res.slice(0,res.length-1);
};
二叉树的后序遍历
给定一个二叉树,返回它的后序遍历。
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
来源: leetcode-cn.com/problems/bi…
递归实现:
var postorderTraversal = function(root) {
if(root === null) return [];
let arr = [];
let help = (root) => {
if(!root) return;
help(root.left);
help(root.right);
arr.push(root.val);
}
help(root);
return arr;
};
迭代实现:
var postorderTraversal = function(root) {
if(root === null) return root;
let res = [],stack = [];
stack.push(root);
while (stack.length){
let node = stack.pop();
res.push(node.val);
node.left && stack.push(node.left);
node.right && stack.push(node.right);
}
return res.reverse();
};
二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
输入: [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
[
[3],
[9,20],
[15,7]
]
来源: leetcode-cn.com/problems/bi…
BFS实现:
var levelOrder = function(root) {
if(!root) return [];
let queue = [];
let res = [];
let level = 0;
queue.push(root);
let temp;
while(queue.length) {
res.push([]);
let size = queue.length;
// 注意一下: size -- 在层次遍历中是一个非常重要的技巧
while(size --) {
// 出队
let front = queue.shift();
res[level].push(front.val);
// 入队
if(front.left) queue.push(front.left);
if(front.right) queue.push(front.right);
}
level++;
}
return res;
};
DFS实现:
var levelOrder = function(root) {
let res = [];
let dfs = (node, level) => {
if(!node) return;
if(!res[level]) res[level] = [];
res[level].push(node.val);
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
dfs(root, 0);
return res;
};
二叉树的锯齿形层次遍历
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
输入: [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
[
[3],
[20,9],
[15,7]
]
来源: leetcode-cn.com/problems/bi…
BFS实现:
var zigzagLevelOrder = function(root) {
if(!root) return [];
let queue = [root], res = [], level = 0;
while(queue.length) {
res.push([]);
let size = queue.length;
while(size--) {
let front = queue.shift();
res[level].push(front.val);
if(front.left) queue.push(front.left);
if(front.right) queue.push(front.right);
}
if(level % 2) res[level].reverse();
level++;
}
return res;
};
DFS实现:
var zigzagLevelOrder = function(root) {
var res = [];
dfs(0, root);
return res;
function dfs(i, root){
if(!root) return;
if(!res[i]) res[i] = [];
if(i & 1) res[i].unshift(root.val);
else res[i].push(root.val);
dfs(i+1, root.left);
dfs(i+1, root.right);
}
};
二叉树的层平均值
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
输入: [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
[3, 14.5, 11]
解释:第 0 层的平均值是 3 , 第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。
来源: leetcode-cn.com/problems/av…
// BFS实现
var averageOfLevels = function(root) {
let res = [];
if(!root) return res;
let queue = [root];
while(queue.length) {
let len = queue.length;
let sum = 0;
for (let i = 0; i < len; i++) {
let node = queue.shift();
sum += node.val;
if(node.left) queue.push(node.left);
if(node.right) queue.push(node.right);
}
res.push(sum / len)
}
return res;
};
二叉树的右视图
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
来源: leetcode-cn.com/problems/bi…
// BFS实现
var rightSideView = function(root) {
if(!root) return [];
let queue = [root];
let res = [];
while(queue.length) {
let path = [];
let size = queue.length;
while(size--) {
let front = queue.shift();
path.push(front.val);
if(front.left) queue.push(front.left);
if(front.right) queue.push(front.right);
}
res.push(path.pop());
}
return res;
};
从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
输入: [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
[3,9,20,15,7]
来源: leetcode-cn.com/problems/co…
// BFS实现
var levelOrder = function(root) {
if(!root) return [];
let res = [];
const queue = [root];
while(queue.length) {
const front = queue.shift();
res.push(front.val);
front.left && queue.push(front.left);
front.right && queue.push(front.right);
}
return res;
};
N叉树
N叉树的前序遍历
给定一个N叉树,返回它的前序遍历。
输出: [1,3,5,6,2,4]
来源: leetcode-cn.com/problems/n-…
// 递归实现
var preorder = function(root) {
let res = [];
let help = (root) => {
if(!root) return;
res.push(root.val);
for (let i = 0; i < root.children.length; i++) {
help(root.children[i])
}
}
help(root);
return res;
};
N叉树的后序遍历
给定一个N叉树,返回它的后序遍历。
输出: [5,6,3,2,4,1]
来源: leetcode-cn.com/problems/n-…
// 递归实现
var postorder = function(root) {
let res = [];
let help = (root) => {
if(!root) return;
for (let i = 0; i < root.children.length; i++) {
help(root.children[i])
}
res.push(root.val)
}
help(root);
return res;
};
N叉树的层序遍历
给定一个N叉树,返回它的层序遍历。
输出:
[
[1],
[3,2,4],
[5,6]
]
来源: leetcode-cn.com/problems/n-…
// BFS实现
var levelOrder = function(root) {
if(!root) return [];
let queue = [root], ans = [], level = 0;
while(queue.length) {
ans[level] = [];
let levelNum = queue.length;
while(levelNum--) {
let front = queue.shift();
ans[level].push(front.val);
if(front.children && front.children.length > 0) {
queue.push(...front.children);
}
}
level++;
}
return ans;
};
N叉树的最大深度
给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
输出: 3
来源: leetcode-cn.com/problems/ma…
var maxDepth = function(root) {
if(!root) return 0;
let num = 0;
if(root.children) {
root.children.forEach(item => {
let max = maxDepth(item);
num = Math.max(max, num)
})
}
return num + 1;
};
二叉树的序列化
二叉树的序列化与反序列化
请实现两个函数,分别用来序列化和反序列化二叉树。
1
/ \
2 3
/ \
4 5 序列化为 "[1,2,3,null,null,4,5]"
来源: leetcode-cn.com/problems/xu…
var serialize = function(root) {
if(!root) return [];
let arr = [];
let queue = [root];
while(queue.length) {
let node = queue.shift();
if(!node) arr.push(null);
else {
arr.push(node.val);
queue.push(node.left);
queue.push(node.right);
}
}
return arr;
};
var deserialize = function(data) {
if(data.length === 0) return null;
let root = new TreeNode(data.shift());
let queue = [root];
while(queue.length > 0) {
let node = queue.shift();
if(data.length <= 0) break;
let left = data.shift();
if(left === null) {
node.left = null;
} else {
node.left = new TreeNode(left);
queue.push(node.left)
}
if(data.length <= 0) break;
let right = data.shift();
if(right === null) {
node.right = null;
} else {
node.right = new TreeNode(right);
queue.push(node.right)
}
}
return root;
};
验证二叉树的前序序列化
序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。
输入: "9,3,4,#,#,1,#,#,2,#,6,#,#"
输出: true
_9_
/ \
3 2
/ \ / \
4 1 # 6
/ \ / \ / \
# # # # # #
来源: leetcode-cn.com/problems/ve…
var isValidSerialization = function(preorder) {
preorder = preorder.substring(0, preorder.length).split(',');
let stack = [];
for (let i = 0; i < preorder.length; i++) {
stack.push(preorder[i]);
while(stack[stack.length - 1] === '#' && stack[stack.length - 2] === '#') {
stack.pop();
stack.pop();
stack[stack.length - 1] = '#';
}
}
return stack.length === 1 && stack[0] === '#';
};
平衡/对称二叉树
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。
3
/ \
9 20
/ \
15 7
true
来源: leetcode-cn.com/problems/ba…
var isBalanced = function(root) {
let ans = true;
const dfs = (root) => {
if(!root) return 0;
let left = dfs(root.left);
let right = dfs(root.right);
if(Math.abs(left - right) > 1) ans = false;
return Math.max(left, right) + 1;
}
dfs(root);
return ans;
};
将二叉搜索树变平衡
给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。
输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。
来源: leetcode-cn.com/problems/ba…
var balanceBST = function(root) {
let valArr = [];
let help = (root) => {
if(!root) return;
help(root.left);
valArr.push(root.val);
help(root.right);
}
help(root);
let createTree = (l, r) => {
if(l > r) return null;
let mid = (l + r) >> 1;
let t = new TreeNode(valArr[mid]);
t.left = createTree(l, mid - 1);
t.right = createTree(mid + 1, r);
return t;
}
let res = createTree(0, valArr.length - 1);
return res;
};
对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
来源: leetcode-cn.com/problems/sy…
var isSymmetric = function(root) {
if(!root) return true;
let queue = [root.left, root.right], node1, node2;
while(queue.length) {
node1 = queue.pop();
node2 = queue.pop();
if(!node1 && !node2) continue;
if(!node1 || !node2 || node1.val !== node2.val) return false;
queue.push(node1.left);
queue.push(node2.right);
queue.push(node1.right);
queue.push(node2.left);
}
return true;
};
深度/宽度问题
二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
输入: [3,9,20,null,null,15,7]
输出: 3
3
/ \
9 20
/ \
15 7
来源: leetcode-cn.com/problems/ma…
递归实现:
var maxDepth = function(root) {
if(!root) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
非递归实现:
var maxDepth = function(root) {
if(root == null) return 0;
let queue = [root];
let level = 0;
while(queue.length) {
let size = queue.length;
while(size --) {
let front = queue.shift();
if(front.left) queue.push(front.left);
if(front.right) queue.push(front.right);
}
// level ++ 后的值代表着现在已经处理完了几层节点
level ++;
}
return level;
};
二叉树的最小深度
给定一个二叉树,找出其最小深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
输入: [3,9,20,null,null,15,7]
输出: 2
3
/ \
9 20
/ \
15 7
来源: leetcode-cn.com/problems/mi…
递归实现:
var minDepth = function(root) {
if(!root) return 0;
if(!root.left) return minDepth(root.right) + 1;
if(!root.right) return minDepth(root.left) + 1;
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
};
非递归实现:
var minDepth = function(root) {
if(root == null) return 0;
let queue = [root];
let level = 0;
while(queue.length) {
let size = queue.length;
while(size --) {
let front = queue.shift();
// 找到叶子节点
if(!front.left && !front.right) return level + 1;
if(front.left) queue.push(front.left);
if(front.right) queue.push(front.right);
}
// level ++ 后的值代表着现在已经处理完了几层节点
level ++;
}
return level;
};
N叉树的最大深度
给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
输出:3
来源: leetcode-cn.com/problems/ma…
var maxDepth = function(root) {
if(!root) return 0;
let num = 0;
if(root.children) {
root.children.forEach(item => {
let max = maxDepth(item);
num = Math.max(max, num)
})
}
return num + 1;
};
二叉树的最大宽度
给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。 每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
输入:
1
/ \
3 2
/ \ \
5 3 9
输出: 4
解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。
来源: leetcode-cn.com/problems/ma…
var widthOfBinaryTree = function(root) {
if(!root) return 0;
let maxWidth = 0, res = [];
help(root, 0, 0);
return maxWidth;
function help(root, level, num) {
if(res[level]) {
res[level].push(num)
} else {
res[level] = [num]
}
let tmpArr = res[level];
let tmpWidth = tmpArr[tmpArr.length - 1] - tmpArr[0] + 1;
if(tmpWidth > maxWidth) {
maxWidth = tmpWidth
}
if(root.left) {
help(root.left, level + 1, num * 2 + 1)
}
if(root.right) {
help(root.right, level + 1, num * 2 + 2)
}
}
};