以 leetcode 上常见的一些题为例进行说明。
二叉树的操作一般都需要用到其遍历方法,各种遍历方式一般都可以完成,同时也分为递归的写法和循环迭代的写法,本文优先使用前序遍历递归的写法,因为易于理解。如果不了解,可以看我上一篇博客。
合并二叉树
两颗树同步遍历即可,遇到有空的,直接返回另一个棵树的节点就即可。以前序遍历给出示例。
var mergeTrees = function(t1, t2) {
//使用递归进行遍历,
if(t1 && t2){
t1.val = t1.val + t2.val
t1.left = mergeTrees(t1.left, t2.left)
t1.right = mergeTrees(t1.right, t2.right)
return t1;
}else if(!t1){
return t2
}else{
return t1
}
};
翻转二叉树
每个节点的操作动作是交换左右分支。
var invertTree = function(root) {
if(root){
//当前节点,左右交换
var copy = root.left;
root.left = root.right;
root.right = copy;
//左节点再进行
invertTree(root.left);
//右节点在进行
invertTree(root.right);
}
return root;
};
二叉树的最大深度
递归,求当前节点为根节点的树的最大深度就是左右分支最大深度 + 1。原理就是每个节点都会因为在某一层中而使得自己所在路径的深度 + 1。
可能这里用层序遍历对层数进行记录的方法更好理解。
var maxDepth = function(root) {
//到底了
if (!root) return 0;
//左边的深度
var leftDepth = maxDepth(root.left);
//右边的深度
var rightDepth = maxDepth(root.right);
//当前层的深度等于子深度+1
return Math.max(leftDepth, rightDepth)+1;
};
二叉树的最小深度
递归,求当前节点为根节点的树的最小深度就是左右分支最小深度 + 1。
var minDepth = function(root) {
if(!root) return 0 ;
let left = minDepth(root.left);
let right = minDepth(root.right);
if(left === 0 || right === 0) return left + right + 1;
//如果有一个为1说明当前节点不是叶子,要继续往下找叶子。如果都是0,那么就返回1;
return Math.min(left, right) + 1;
};
平衡二叉树判断
本题中,一棵高度平衡二叉树定义为:
>一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。
判断当前树,递归左右分支是否平衡,这里我们要用到上面的最大深度计算函数。
var isBalanced = function(root) {
//空检测
if(!root) return true;
//左右子深度计算
var leftDepth = maxDepth(root.left);
var rightDepth = maxDepth(root.right);
//左右深度差
differ = Math.abs(leftDepth-rightDepth);
if (differ > 1){
return false;
}
else{
return isBalanced(root.left) && isBalanced(root.right)
}
};
对称二叉树判断
内部定义一个比较左右分支的递归函数即可,代码很好理解。
var isSymmetric = function(root) {
if (!root) return true;
//递归
var compare = function(left, right) {
//空检测
if (!left && !right) {
return true;
}
else if ((!left && right) || (left && !right)) {
return false;
}
else {
if (left.val === right.val) {
return compare(left.left, right.right) && compare(left.right, right.left);
}
else {
return false
}
}
}
return compare(root.left, root.right);
};
二叉树的直径
一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
首先想到的思路是,遍历每个节点,再计算其左右深度,左深度+右深度+1即可。但其实没有必要,因为计算深度的过程其实也是遍历的过程。
这里我们要用到上面的最大深度计算函数,但是要稍作修改。我们需要定义了一个全局变量,来记录当前遍历过程中出现的最大直径。
var max;
var diameterOfBinaryTree = function(root) {
//空或者只有一个节点检测
if( !root || (!root.left && !root.right) ) return 0;
max = 1;
//求最大深度的过程中,每个节点都会递归到,直接对max进行更新即可
maxDepth(root, max);
return max ;
};
//最大深度
var maxDepth = function(root){
//空检测
if(!root) return 0;
var leftDepth = maxDepth(root.left);
var rightDepth = maxDepth(root.right);
max = Math.max(leftDepth+rightDepth,max); //不是包含节点数,路径是边数
return Math.max(leftDepth, rightDepth)+1;
};
路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。递归一把梭!
var hasPathSum = function(root, sum) {
//空检测
if(!root) return false;
//叶子检测
if(!root.left && !root.right) return sum === root.val;
//左右子路径检测
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
};
子树判断
需要额外定义一个判断是否是同一棵树的函数。
遍历+递归实现。
var isSubtree = function(s, t) {
//递归遍历的
if(s && t){
return isSubtreeNode(s,t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
else if (!s && !t) {
return true;
}
else {
return false;
}
};
var isSubtreeNode = function(node, t){
if((!node && t) || (node && !t)) return false;
if(!node && !t) return true;
if(node.val === t.val){
return isSubtreeNode(node.left, t.left) && isSubtreeNode(node.right, t.right);
} else {
return false;
}
};
找到二叉树最后一行最左边的数
层序遍历即可达到目的。
var findBottomLeftValue = function(root) {
//层序遍历 每层先右后左
var levOrderTraversal = function(root) {
if (!root) return [];
let array = [root],
current = null,
result = [];
while(array.length > 0){
current = array.shift();
result.push(current.val);
//从右到左压入
if (current.right) array.push(current.right);
if (current.left) array.push(current.left);
}
return result;
};
let res = [];
res = levOrderTraversal(root);
return res[res.length-1];
};
二叉树中的最大路径和
递归,求左右最大路径 ,求出来更新结果即可。
var maxPathSum = function(root) {
let result = -Infinity
function maxLineSum(root){
if(!root) return 0
let left = Math.max(maxLineSum(root.left),0) //小于0 的路径,就不要了
let right = Math.max(maxLineSum(root.right),0)
result = Math.max(left + right + root.val, result)
return Math.max(left, right) + root.val
}
maxLineSum(root)
return result
};
二叉树转链表
利用递归的思想
-
将左边变成链表;
-
找到左边的tail;
-
将右边编程链表;
-
将root的左边置为null;
-
将左边的链表接到root的right;
-
将右边的链表接到左边链表的tail上。
var flatten = function(root) { if(!root) return root
let leftHead = flatten(root.left) //找到左子树的最右边的,也就是反中序遍历的第一步 let leftTail = root.left //如果一开始就是null,那么head那里会出了,否则不会找到null的 while(leftTail && leftTail.right){ leftTail = leftTail.right } let rightHead = flatten(root.right) //清空左子树 root.left =null //左边为null的情况 if(leftHead){ root.right = leftHead leftTail.right = rightHead }else{ root.right = rightHead } return root
};