Day15-Day16-二叉树-leetcode110,257,404,222 - 513,112,113,106
- 平衡二叉树
-
给定一个二叉树,判断它是否是 平衡二叉树
-
思路
-
平衡二叉树,一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
-
深度,从上到下去查,需要前序遍历(中左右),而高度,从下到上去查,只能后序遍历(左右中)
-
递归三步曲分析:
-
- 明确递归函数的参数和返回值:参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度
-
- 明确终止条件:递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0
-
- 明确单层递归的逻辑:如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
-
深度适合用前序遍历,而高度适合用后序遍历。
var isBalanced = function(root) {
//还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1
// 1. 确定递归函数参数以及返回值
const getDepth = function(node) {
// 2. 确定递归函数终止条件
if(node === null) return 0;
// 3. 确定单层递归逻辑
let leftDepth = getDepth(node.left); //左子树高度
// 当判定左子树不为平衡二叉树时,即可直接返回-1
if(leftDepth === -1) return -1;
let rightDepth = getDepth(node.right); //右子树高度
// 当判定右子树不为平衡二叉树时,即可直接返回-1
if(rightDepth === -1) return -1;
if(Math.abs(leftDepth - rightDepth) > 1) {
return -1;
} else {
return 1 + Math.max(leftDepth, rightDepth);
}
}
return !(getDepth(root) === -1);
};
- 二叉树的所有路径
-
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。
-
思路
-
从根节点到叶子的路径,所以需要前序遍历,方便让父节点指向孩子节点,找到对应的路径。
// 递归
var binaryTreePaths = function(root) {
//递归遍历+递归三部曲
// res 用于保存所有路径的结果(字符串数组)。
// getPath 是递归辅助函数,参数是当前节点 node 和当前路径字符串 curPath。
// 从根节点和空路径开始递归。
let res = [];
//1. 确定递归函数 函数参数
const getPath = function(node,curPath) {
//2. 确定终止条件,到叶子节点就终止
// 如果当前节点是叶子节点(左右孩子都为空),
// 把当前节点值拼接到路径末尾,并把完整路径加入结果数组 res,然后返回。
if(node.left === null && node.right === null) {
curPath += node.val;
res.push(curPath);
return;
}
//3. 确定单层递归逻辑
// 不是叶子节点时,把当前节点值和 "->" 拼接到路径字符串后面。
// 如果有左孩子,递归左孩子;有右孩子,递归右孩子。
curPath += node.val + '->';
node.left && getPath(node.left, curPath);
node.right && getPath(node.right, curPath);
}
getPath(root, '');
return res;
};
- 左叶子之和
-
给定二叉树的根节点 root ,返回所有左叶子之和。
-
思路
-
节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点。通过节点的父节点来判断其左孩子是不是左叶子了。
// 迭代
var sumOfLeftLeaves = function(root) {
//采用层序遍历
if(root === null) {
return null;
}
let queue = [];
let sum = 0;
queue.push(root);
while(queue.length) {
let node = queue.shift();
if(node.left !== null && node.left.left === null && node.left.right === null) {
sum+=node.left.val;
}
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
return sum;
};
- 完全二叉树的节点个数
-
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
-
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。
-
思路:
- 普通二叉树的节点数
- 递归法
-
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量
-
- 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
-
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
var countNodes = function(root) {
//递归法计算二叉树节点数 后序遍历
// 1. 确定递归函数参数
const getNodeSum = function(node) {
//2. 确定终止条件
if(node === null) {
return 0;
}
//3. 确定单层递归逻辑
let leftNum = getNodeSum(node.left);
let rightNum = getNodeSum(node.right);
return leftNum + rightNum + 1;
}
return getNodeSum(root);
};
// 迭代,层序遍历
var countNodes = function(root) {
let queue = []
if (root === null) {
return 0
}
queue.push(root)
let nodeNums = 0
while(queue.length) {
let len = queue.length
while(len--) {
let node = queue.shift()
nodeNums++
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
}
return nodeNums
}
- 完全二叉树
- 完全二叉树只有两种情况,情况一:就是满二叉树,2^树深度 - 1 ;情况二:最后一层叶子节点没有满,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
var countNodes = function(root) {
if(root === null) {
return 0
}
let left = root.left
let right = root.right
let leftDepth = 0, rightDepth = 0
while(left) {
left = left.left
leftDepth++
}
while(right){
right = right.right
rightDepth++
}
if(leftDepth == rightDepth) {
return Math.pow(2, leftDepth+1) - 1
}
return countNodes(root.left) + countNodes(root.right) + 1
}
- 找树左下角的值
-
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
-
假设二叉树中至少有一个节点。
-
思路
-
找出树的最后一行的最左边的值。
// 迭代,层序遍历
var findBottomLeftValue = function(root) {
//考虑层序遍历 记录最后一行的第一个节点
let queue = [];
if(root === null) {
return null;
}
queue.push(root);
let resNode;
while(queue.length) {
let length = queue.length;
for(let i = 0; i < length; i++) {
let node = queue.shift();
if(i === 0) {
resNode = node.val;
}
node.left && queue.push(node.left);
node.right && queue.push(node.right);
}
}
return resNode;
};
- 路径总和
-
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
-
叶子节点 是指没有子节点的节点。
-
思路
-
递归
-
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
-
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
-
如果遍历到了叶子节点,count不为0,就是没找到。
// 递归
let hasPathSum = function(root, targetSum) {
if (!root) return false;
// 到叶子节点且和正好等于targetSum
if (!root.left && !root.right && root.val === targetSum) return true;
// 递归左右子树
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
};
// 迭代
let hasPathSum = function(root, targetSum) {
if(root === null) return false;
// nodeArr 用于存储待遍历的节点(队列)。
// valArr 用于存储到当前节点为止的路径和(与节点一一对应)。
let nodeArr = [root];
let valArr = [0];
while(nodeArr.length) {
// 每次从队列取出一个节点和对应的路径和。
// 路径和加上当前节点的值。
let curNode = nodeArr.shift();
let curVal = valArr.shift();
curVal += curNode.val;
// 为叶子结点,且和等于目标数,返回true
if (curNode.left === null && curNode.right === null && curVal === targetSum) {
return true;
}
// 如果有左/右子节点,把它们加入队列,并把当前路径和也加入对应位置。
// 左节点,将当前的数值也对应记录下来
if (curNode.left) {
nodeArr.push(curNode.left);
valArr.push(curVal);
}
// 右节点,将当前的数值也对应记录下来
if (curNode.right) {
nodeArr.push(curNode.right);
valArr.push(curVal);
}
}
return false;
};
- 路径总和ii
- 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
- 说明: 叶子节点是指没有子节点的节点。
// 递归
let pathsum = function (root, targetsum) {
// 递归法
// 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同
const res = [];
const travelsal = (node, cnt, path) => {
// 遇到了叶子节点且找到了和为sum的路径
if (cnt === 0 && !node.left && !node.right) {
res.push([...path]); // 不能写res.push(path), 要深拷贝
return;
}
if (!node.left && !node.right) return; // 遇到叶子节点而没有找到合适的边,直接返回
// 左 (空节点不遍历)
if (node.left) {
path.push(node.left.val);
travelsal(node.left, cnt - node.left.val, path); // 递归
path.pop(); // 回溯
}
// 右 (空节点不遍历)
if (node.right) {
path.push(node.right.val);
travelsal(node.right, cnt - node.right.val, path); // 递归
path.pop(); // 回溯
}
return;
};
if (!root) return res;
travelsal(root, targetsum - root.val, [root.val]); // 把根节点放进路径
return res;
};
var pathsum = function(root, targetsum) {
//递归方法
let respath = [],curpath = [];
// 1. 确定递归函数参数
const traveltree = function(node,count) {
curpath.push(node.val);
count -= node.val;
if(node.left === null && node.right === null && count === 0) {
respath.push([...curpath]);
}
node.left && traveltree(node.left, count);
node.right && traveltree(node.right, count);
let cur = curpath.pop();
count -= cur;
}
if(root === null) {
return respath;
}
travelTree(root, targetSum);
return resPath;
};
// 迭代
let pathSum = function(root, targetSum) {
if(root === null) return [];
let nodeArr = [root];
let resArr = []; // 记录符合目标和的返回路径
let tempArr = [[]]; // 对应路径
let countArr = [0]; //对应和
while(nodeArr.length) {
let curNode = nodeArr.shift();
let curVal = countArr.shift();
let curNodeArr = tempArr.shift();
curVal += curNode.val;
curNodeArr.push(curNode.val);
// 为叶子结点,且和等于目标数,将此次结果数组push进返回数组中
if (curNode.left === null && curNode.right === null && curVal === targetSum) {
resArr.push(curNodeArr);
}
// 左节点,将当前的和及对应路径也对应记录下来
if (curNode.left) {
nodeArr.push(curNode.left);
countArr.push(curVal);
tempArr.push([...curNodeArr]);
}
// 右节点,将当前的和及对应路径也对应记录下来
if (curNode.right) {
nodeArr.push(curNode.right);
countArr.push(curVal);
tempArr.push([...curNodeArr]);
}
}
return resArr;
};
- 从中序与后序遍历序列构造二叉树
-
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
-
思路
-
递归
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间
var buildTree = function(inorder, postorder) {
if (!inorder.length) return null;
const rootVal = postorder.pop(); // 从后序遍历的数组中获取中间节点的值, 即数组最后一个值
let rootIndex = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
const root = new TreeNode(rootVal); // 创建中间节点
root.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex)); // 创建左节点
root.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex)); // 创建右节点
return root;
};
var buildTree = function(preorder, inorder) {
if (!preorder.length) return null;
const rootVal = preorder.shift(); // 从前序遍历的数组中获取中间节点的值, 即数组第一个值
const index = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
const root = new TreeNode(rootVal); // 创建中间节点
root.left = buildTree(preorder.slice(0, index), inorder.slice(0, index)); // 创建左节点
root.right = buildTree(preorder.slice(index), inorder.slice(index + 1)); // 创建右节点
return root;
};