理论知识
树是一种非线性的数据结构,它由节点(或顶点)和边组成。树具有以下特点:
- 有一个特殊的节点称为根节点
- 除根节点外,每个节点都有且只有一个父节点
- 每个节点可以有零个或多个子节点
- 没有子节点的节点称为叶子节点
相关术语:
- 根节点:树的顶端节点。
- 子节点:由一个节点直接连接的下一级节点。
- 父节点:直接连接其子节点的上一级节点。
- 叶节点:没有子节点的节点。
- 深度:从根节点到某节点的路径长度。
- 高度:从某节点到叶节点的最长路径长度。
- 层次:树中节点的层次关系,从根节点开始计数,根节点为第0层。
树的种类:
- 二叉树:每个节点最多有两个子节点。
- 平衡树:任意节点的两个子树的高度差不超过1。
- 搜索树:每个节点的左子节点值小于该节点值,右子节点值大于该节点值。
- 红黑树:一种自平衡二叉搜索树,确保树的高度维持在O(log n)。
树的遍历方式:
- 前序遍历:根节点 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根节点 -> 右子树
- 后序遍历:左子树 -> 右子树 -> 根节点
- 层次遍历:按层次逐层遍历,从上到下,从左到右
144. 二叉树的前序遍历
题目
解题思路
递归方法:
- 访问根节点
- 递归遍历左子树
- 递归遍历右子树
迭代方法(使用栈):
- 将根节点压入栈
- 当栈不为空时,弹出栈顶节点并访问
- 将右子节点压入栈(如果存在)
- 将左子节点压入栈(如果存在)
- 重复步骤2-4直到栈为空
代码实现
// 递归法
function preorderTraversal(root: TreeNode | null): number[] {
function traverse(node: TreeNode | null, result: number[]) {
if (node === null) return;
result.push(node.val);
traverse(node.left, result);
traverse(node.right, result);
}
const result: number[] = [];
traverse(root, result)
return result;
};
// 迭代法
function preorderTraversal(root: TreeNode | null): number[] {
if (root === null) return [];
const result: number[] = [];
const stack: TreeNode[] = [root];
while (stack.length > 0) {
const node = stack.pop()!; // 非空断言,因为我们知道栈不为空
result.push(node.val);
if (node.right) stack.push(node.right); // 先压入右子节点
if (node.left) stack.push(node.left); // 后压入左子节点
}
return result;
};
145. 二叉树的后序遍历
题目
给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
示例 1:
输入: root = [1,null,2,3]
输出: [3,2,1]
示例 2:
输入: root = []
输出: []
示例 3:
输入: root = [1]
输出: [1]
提示:
- 树中节点的数目在范围
[0, 100]内 -100 <= Node.val <= 100
解题思路
递归方法:
- 递归遍历左子树
- 递归遍历右子树
- 访问根节点
迭代方法(使用栈):
- 使用两个栈,一个用于遍历,另一个用于存储结果
- 将根节点压入第一个栈
- 当第一个栈不为空时,弹出栈顶节点并压入第二个栈
- 将左子节点压入第一个栈(如果存在)
- 将右子节点压入第一个栈(如果存在)
- 重复步骤3-5直到第一个栈为空
- 依次弹出第二个栈中的元素,即为后序遍历结果
代码实现
// 递归法
function postorderTraversal(root: TreeNode | null): number[] {
const result: number[] = [];
function traverse(node: TreeNode | null) {
if (node === null) return;
traverse(node.left);
traverse(node.right);
result.push(node.val);
}
traverse(root);
return result;
};
// 迭代法
function postorderTraversal(root: TreeNode | null): number[] {
if (root === null) return [];
const result: number[] = [];
const stack1: TreeNode[] = [root];
const stack2: TreeNode[] = [];
while (stack1.length > 0) {
const node = stack1.pop()!;
stack2.push(node);
if (node.left) stack1.push(node.left);
if (node.right) stack1.push(node.right);
}
while (stack2.length > 0) {
result.push(stack2.pop()!.val);
}
return result;
};
94. 二叉树的中序遍历
题目
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入: root = [1,null,2,3]
输出: [1,3,2]
示例 2:
输入: root = []
输出: []
示例 3:
输入: root = [1]
输出: [1]
提示:
- 树中节点数目在范围
[0, 100]内 -100 <= Node.val <= 100
解题思路
递归方法:
- 递归遍历左子树
- 访问根节点
- 递归遍历右子树
迭代方法(使用栈):
- 创建一个栈和一个指针(初始指向根节点)
- 当指针非空或栈非空时,进行以下操作:
- 如果指针非空,将指针指向的节点入栈,指针移动到左子节点
- 如果指针为空,弹出栈顶节点并访问,指针移动到右子节点
代码实现
// 递归法
function inorderTraversal(root: TreeNode | null): number[] {
const result: number[] = [];
function traverse(node: TreeNode | null) {
if (node === null) return;
traverse(node.left);
result.push(node.val);
traverse(node.right);
}
traverse(root);
return result;
};
// 迭代法
function inorderTraversal(root: TreeNode | null): number[] {
const result: number[] = [];
const stack: TreeNode[] = [];
let current: TreeNode | null = root;
while (current !== null || stack.length > 0) {
while (current !== null) {
stack.push(current);
current = current.left;
}
current = stack.pop()!;
result.push(current.val);
current = current.right;
}
return result;
};
102. 二叉树的层序遍历
题目
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
解题思路
- 层序遍历是指按照树的层级,从上到下,从左到右依次访问树的所有节点。每一层的节点值要单独存储在一个数组中。可以使用队列来实现广度优先搜索
- 初始时将根节点入队,每次处理当前队列中的所有节点(即当前层的节点),对于每个出队的节点,将其左右子节点入队(如果存在)
- 重复步骤以上,直到队列为空,返回结果列表
代码实现
function levelOrder(root: TreeNode | null): number[][] {
if(root === null) return []
const result: number[][] = [];
const queue: TreeNode[] = [root];
while(queue.length > 0){
const levelSize = queue.length;
const currentLevel: number[] = [];
for(let i = 0; i < levelSize; i++){
const currentNode = queue.shift();
if(currentNode){
currentLevel.push(currentNode.val);
currentNode.left && queue.push(currentNode.left);
currentNode.right && queue.push(currentNode.right);
}
}
result.push(currentLevel)
}
return result
};