104. 二叉树的最大深度
思路:在深度优先遍历过程中,记录每个节点所在层级,找出最大层级即可
1)新建一个变量,记录最大深度
2)深度优先遍历整颗树,并记录每个节点的层级,同时不断刷新最大深度这个变量
3)遍历结束返回最大深度这个变量
/** 时间复杂度:O(n) n是树的结点
空间复杂度:函数调用函数会形成堆栈 O(logN)
*/
var maxDepth = function(root) {
let res = 0
const dfs = (n, l) =>{
if(!n) return
if(!n.left&&!n.right){ // 叶子结点再去刷新,不用每个结点都去刷新
res = Math.max(res, l) // 不断刷新最大深度这个变量
}
dfs(n.left, l+1)
dfs(n.right, l+1)
}
dfs(root, 1)
return res
};
111. 二叉树的最小深度
思路:在广度优先遍历中,遇到叶子节点,停止遍历,返回节点层级
/**时间复杂度:O(n) n是树的结点
空间复杂度:O(n) 有队列,最坏情况下队列会装满树的结点
*/
var minDepth = function(root) {
if(!root) return 0
const q = [[root, 1]] // 改成嵌套数组,第二个放层级
while(q.length){
const [n, l] = q.shift()
console.log(n.val, l)
if(!n.left && !n.right) return l // 左右节点为空,即叶子节点,返回层级
if(n.left) q.push([n.left, l+1])
if(n.right) q.push([n.right, l+1])
}
};
102. 二叉树的层序遍历
const levelOrder = function(root) {
const res = []
// 处理边界条件
if(!root) return res
// 初始化队列, 第一个元素是根结点
const queue = [root]
// 当队列不为空时,反复执行以下逻辑
while(queue.length) {
const level = [] // level用来存储当前层的结点
// 缓存刚进入循环时的队列长度,这一步很关键,因为队列长度后面会发生改变
const len = queue.length
// 循环遍历当前层级的结点
for(let i=0;i<len;i++) {
const top = queue.shift() // 取出队列的头部元素
level.push(top.val) // 将头部元素的值推入level数组
// 如果当前结点有左或右孩子,则推入下一层级
if(top.left) queue.push(top.left)
if(top.right) queue.push(top.right)
}
res.push(level) // 将level推入结果数组
}
return res
}
优化:不用额外开辟空间存当前层
/**时间复杂度:O(n) n是树的结点
空间复杂度:O(n) 有队列,最坏情况下队列会装满树的结点
*/
var levelOrder = function(root) {
if(!root) return []
const q = [root]
const res = []
while(q.length){
let len = q.length
res.push([])
while(len--){
const n = q.shift()
res[res.length-1].push(n.val)
if(n.left) q.push(n.left)
if(n.right) q.push(n.right)
}
}
return res
};
94. 二叉树的中序遍历
// 递归
var inorderTraversal = function(root) {
const res=[]
const rec = (n)=>{
if(!n) return
rec(n.left)
res.push(n.val)
rec(n.right)
}
rec(root)
return res
};
// 迭代
var inorderTraversal = function(root) {
const res=[]
const stack=[]
let p = root
while(stack.length || p){
while(p){
stack.push(p)
p = p.left
}
const n = stack.pop()
res.push(n.val)
p = n.right
}
return res
};
112. 路径总和
var hasPathSum = function(root, sum) {
if(!root) return false
let res=false
const dfs=(n, s)=>{
if(!n.left && !n.right && s===sum) res = true
if(n.left) dfs(n.left, n.left.val+s)
if(n.right) dfs(n.right, n.right.val+s)
}
dfs(root, root.val)
return res
};
226.翻转二叉树
思路:以递归的方式,遍历树中的每一个结点,并将每一个结点的左右孩子进行交换
const invertTree = function(root) {
// 定义递归边界
if(!root) return root
// 不断交换左右孩子节点
root.left = invertTree(root.right)
root.right = invertTree(root.left)
return root
};
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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
// 二叉树结点
class Node {
constructor(val) {
this.val = val
this.left = this.right = undefined
}
}
class Tree {
// 创建二叉树
// 思路:把node关联到父结点的左结点或右结点,所以要找父结点。
constructor(data) {
// 临时存储所有节点,方便寻找父子节点
let nodeList = []
// 顶节点
let root
// 拿到数组的每个元素,插入到树里
for (let i = 0, len = data.length; i < len; i++) {
let node = new Node(data[i])
nodeList.push(node)
// i=0为顶点
if (i > 0) {
// 计算当前结点属于哪一层 拿5来测试
let n = Math.floor(Math.sqrt(i + 1))
// 记录当前层的起始点 比如3
let q = Math.pow(2, n) - 1
// 记录上一层的起始点 比如1
let p = Math.pow(2, n - 1) - 1
// 找到当前节点的父节点 (5-3)/2 = 1 1+1
let parent = nodeList[p + Math.floor((i - q) / 2)]
// 当前结点和上一层的父节点做关联
if (parent.left) {
parent.right = node
} else {
parent.left = node
}
}
}
root = nodeList.shift()
nodeList.length = 0
return root
}
// 检测是否对称结点
// 思路:不断递归结点,判断父结点的左子树的左结点和右子树的右结点相同,左子树的右结点和右子树的左结点相同
static isSymmetry(root) {
// 没有顶点,就是空的二叉树,直接返回true
if (!root) return true
let walk = (left, right) => {
// 一起遍历完,没有了,说明通过,返回true
if (!left && !right) {
return true
}
if ((left && !right) || (!left && right) || (left.val !== right.val)) {
return false
}
return walk(left.left, right.right) && walk(left.right, right.left)
}
return walk(root.left, root.right)
}
}
const root = new Tree([1,2,2,3,4,4,3])
console.log(Tree.isSymmetry(root))
重建二叉树
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
/*
1)前序的第一个是树的顶点,用这个顶点在中序找出对应的左边和右边
2)找到中序的对应左边右边后,递归构建左边树和右边树
3)返回整棵树
*/
var buildTree = function (preorder, inorder) {
if(!preorder.length || !inorder.length) return null
const rootVal = preorder[0];
const node = new TreeNode(rootVal);
let i = 0; // i有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
for (; i < inorder.length; ++i) {
if (inorder[i] === rootVal) { // 说明找到中序了
break;
}
}
// slice(从什么开始,到什么结束且不包括结束位置) 前序的1位置(下标0已经是根节点) , 中序的左侧位置
node.left = buildTree(preorder.slice(1, i + 1), inorder.slice(0, i));
node.right = buildTree(preorder.slice(i + 1), inorder.slice(i + 1));
return node;
};
let preorder = [3, 9, 20, 15, 7]
let inorder = [9, 3, 15, 20, 7]
console.log(buildTree(preorder, inorder))
从上到下打印二叉树 (广度搜索)
层次遍历,用广度优先搜索,可以用队列实现
var levelOrder = function(root) {
let res=[]
let queue=[]
if(root!==null) queue.push(root) // root {val:xx,left:xx,right:xx}
while(queue.length){
let node=queue.shift()
res.push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
return res
};
从上到下打印二叉树 II (广度搜索)
var levelOrder = function(root) {
let res=[]
let queue=[]
if(root!==null) queue.push(root)
let level=0
while(queue.length){
res[level]=[]
let leveNum = queue.length
while(leveNum--){
let node = queue.shift()
res[level].push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
level++
}
return res
};
从上到下打印二叉树 III (广度搜索)
判断是否奇偶数,用reverse反转二维数组中level是偶数的那层
var levelOrder = function(root) {
let res=[]
let queue=[]
if(root!=null) queue.push(root)
let level=0
while(queue.length){
res[level]=[]
let levelNum = queue.length
while(levelNum--){
let node = queue.shift()
res[level].push(node.val)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
// 行号是偶数时,翻转当前层的遍历结果
if (level % 2) {
res[level].reverse();
}
level++
}
return res
};
二叉搜索树的后序遍历序列
- 二叉搜索树特点:左节点 < 根节点 < 右节点 ;可为空树
- 后序遍历:最后一个元素是根元素
function verifyPostorder(nums) {
const len = nums.length
if (len < 2) return true // 空树也可以二叉搜索树
const root = nums[len - 1] // 最后一个元素是根节点
// nums[0, root) nums[root, len - 1)是右子树
let cut = 0 // 左子树节点的数量
for (; cut < len - 1 && nums[cut] < root; ++cut) {}
console.log(cut) // 输出3,即下标3开始为右节点
// 检查右子树中的值是否满足条件,如果根节点比右节点大,则返回false
for (let i = cut; i < len - 1; ++i) {
if (nums[i] < root) return false
}
// 再分别递归检查左右子树是否满足后序遍历顺序
return (
verifyPostorder(nums.slice(0, cut)) &&
verifyPostorder(nums.slice(cut, len - 1))
);
}