「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
二叉树的作用
理解高级数据结构的基础
graph TD
二叉树 --> 完全二叉树
二叉树 --> 多叉树/森林
二叉树 --> 二叉排序树
完全二叉树 --> 堆
完全二叉树 --> 优先队列
多叉树/森林 --> 字典树
多叉树/森林 --> AC自动机
多叉树/森林 --> 并查集
二叉排序树 --> AVL树
二叉排序树 --> 2-3树
二叉排序树 --> 红黑树
多叉排序树 --> B-树/B+树
堆、优先队列是维护集合最值的神兵利器
字典树、AC自动机是字符串及其相关转换问题的神兵利器
并查集是连通性问题的神兵利器
AVL树、2-3树、红黑树是语言标准库中重要的数据检索容器的底层实现
B-树、B+树是文件系统、数据库底层重要数据结构
练习递归技巧的最佳选择
设计/理解递归程序
- 数学归纳法 -> 结构归纳法
- 赋予递归函数一个明确的意义
- 思考边界条件
- 实现递归过程
- k0是正确的
- 假设ki -> ki+1
- k0 -> k1 -> ... kn-1 -> kn
function fib(n) { //fib(n)代表第N项斐波那契数列的值
if (n <= 2) return n; //k0正确
return fib(n-1) + f(n-2); //假设ki正确
}
左孩子右兄弟表示法节省空间
二叉树的算法(递归)
二叉树的前序遍历-144
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
输入:root = [1,null,2,3] 输出:[1,2,3] 示例 2:
输入:root = [] 输出:[]
示例 3:
输入:root = [1] 输出:[1]
示例 4:
输入:root = [1,2] 输出:[1,2]
示例 5:
输入:root = [1,null,2] 输出:[1,2]
const preorder = function(root, arr) {
if (!root) return;
arr.push(root.val)
preorder(root.left, arr)
preorder(root.right, arr)
}
const preorderTraversal = function(root) {
const arr = []
preorder(root, arr);
return arr;
};
N叉树的前序遍历-589
/**
* @param {Node|null} root
* @return {number[]}
*/
const preorder = function(root) {
let arr = [];
preorderFunc(root, arr)
return arr
};
const preorderFunc = function(root, arr) {
if (!root) return;
arr.push(root.val);
if (root.children) {
root.children.forEach(item => {
preorderFunc(item, arr)
})
}
}
翻转二叉树-226
- 函数意义:翻转以root为根节点的二叉树
- 边界条件:root为空时不需要翻转
- 递归过程:翻转root的左子树,翻转root的右子树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(!root) return null;
let tempNode = invertTree(root.left);
root.left = invertTree(root.right);
root.right = tempNode;
return root;
};
剑指 Offer 32 - II. 从上到下打印二叉树 II
- 函数意义:将每个节点的数值放入应该的层数(需记录层数)
- 边界条件:root为空时不需要放值
- 递归过程:先放当前节点的值,再将左右节点放到下一层的数组
递归解法:
const levelOrder = function(root) {
let arr = []
_levelOrder(root, arr, 0)
return arr
};
const _levelOrder = function(root, arr, i) {
if (!root) return;
arr[i] ? arr[i].push(root.val) : arr[i] = [root.val];
_levelOrder(root.left, arr, i+1)
_levelOrder(root.right, arr, i+1)
}
队列解法:
const levelOrder = function (root) {
if (!root) return []
const queue = [root];
const arr = [];
while (queue.length > 0) {
const temp = [];
let i = queue.length
for (;i > 0; i--) {
const node = queue.shift();
temp.push(node.val)
if (node.left) {
queue.push(node.left)
}
if (node.right) {
queue.push(node.right)
}
}
arr.push(temp)
}
return arr
};
107. 二叉树的层序遍历 II
与剑指 Offer 32 - II. 从上到下打印二叉树 II类似,只要翻转数组就行。
const levelOrderBottom = function(root) {
const result = [];
_levelOrderBottom(root, result, 0);
// 小技巧
for(let i=0, j = result.length - 1; i < j; i++, j--) {
const temp = result[j];
result[j] = result[i];
result[i] = temp;
}
return result;
//return result.reverse()
};
const _levelOrderBottom = function(root, arr, idx) {
if (!root) return;
arr[idx] ? arr[idx].push(root.val) : arr[idx] = [root.val];
root.left && _levelOrderBottom(root.left, arr, idx + 1);
root.right && _levelOrderBottom(root.right, arr, idx + 1);
}
103. 二叉树的锯齿形层序遍历
同上,只要奇数层翻转就行
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
const zigzagLevelOrder = function (root) {
const result = [];
_zigzagLevelOrder(root, result, 0);
for (let i = 1; i < result.length; i += 2) {
result[i] = result[i].reverse()
}
return result
};
const _zigzagLevelOrder = function (root, arr, idx) {
if (!root) return;
arr[idx] ? arr[idx].push(root.val) : arr[idx] = [root.val];
root.left && _zigzagLevelOrder(root.left, arr, idx + 1);
root.right && _zigzagLevelOrder(root.right, arr, idx + 1);
}
110. 平衡二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
const isBalanced = function(root) {
return getHeight(root) >= 0
};
const getHeight = (root) => {
if (!root) return 0;
const left = getHeight(root.left);
const right = getHeight(root.right);
// 如果左右绝对值大于1,不平衡
if (Math.abs(left - right) > 1) return -2;
// 如果左子树或右子树小于0,说明有不平衡
if (left < 0 || right < 0) return -2;
// 返回左右子树更高值+1=当前节点高度
return Math.max(left, right) + 1;
}
112. 路径总和
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {boolean}
*/
const hasPathSum = function(root, targetSum) {
if (!root) return false;
// 如果是叶子节点,判断当前值是否是目标值
if (!root.left && !root.right) return root.val === targetSum;
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
};
105. 从前序与中序遍历序列构造二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
const buildTree = function (preorder, inorder) {
if (!preorder.length) return null;
const result = new TreeNode();
const root = preorder.shift();
result.val = root;
let inIdx = 0;
while(inorder[inIdx] !== root) inIdx++;
const leftPre = preorder.slice(0, inIdx);
const rightPre = preorder.slice(inIdx);
const leftIn = inorder.slice(0, inIdx);
const rightIn = inorder.slice(inIdx + 1);
result.left = buildTree(leftPre, leftIn);
result.right = buildTree(rightPre, rightIn);
return result;
};
222. 完全二叉树的节点个数
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var countNodes = function(root) {
if (!root) return 0;
return 1 + countNodes(root.left) + countNodes(root.right)
};
剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
注意:二叉搜索树,左边的节点一定小于右边节点。即二叉搜索树的中序遍历一定是个有序数组。
思路:先计算右节点有多少个数,判断第K大节点在哪个位置。去
/**
* 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}
*/
const kthLargest = function(root, k) {
const r_count = countTree(root.right);
if (k <= r_count) return kthLargest(root.right, k);
if (k === r_count + 1) return root.val;
return kthLargest(root.left, k - r_count - 1);
};
const countTree = (root) => {
if (!root) return 0;
return countTree(root.left) + countTree(root.right) + 1;
}
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} A
* @param {TreeNode} B
* @return {boolean}
*/
// 求是否是子树
const isSubStructure = function(A, B) {
if (!B || !A) return false;
if (B.val === A.val && is_Match(A,B)) return true;
return isSubStructure(A.left, B) || isSubStructure(A.right, B)
};
// 求是否节点匹配
const is_Match = (A, B) => {
if (!B) return true;
if (!A) return false;
if (B.val !== A.val) return false;
return is_Match(A.left, B.left) && is_Match(A.right, B.right);
}