数据结构之二叉树——LeetCode笔记
- 一、树的遍历
-
- 1、介绍
- 2、二叉树的前序遍历 ([LeetCode 144](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/))
- 3、二叉树的中序遍历 ([LeetCode 94](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/))
- 4、二叉树的后序遍历 ([LeetCode 145](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/))
- ==5、递归模板总结——六行代码解决前中后遍历==
- 二、层序遍历
- 三、递归解决树的问题
- 四、总结
-
- 1、从中序和后序遍历中构造二叉树 ([LeetCode 106](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/))
- 2、从前序与中序遍历构造二叉树 ([LeetCode 105](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/))
- 3、填充每个节点的下一个右侧节点指针 ([LeetCode 116](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/))
- 4、填充每个节点的下一个右侧节点Ⅱ (LeetCode 117)
- 5、二叉树的最近公共祖先 ([LeetCode 236](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/))
- 6、二叉树的序列化与反序列化 ([LeetCode 297](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/))
-
树的每个节点有一个值、包含所有子节点的列表
-
二叉树的每个节点最多有两个子树——左子树、右子树
一、树的遍历
1、介绍
- ① 前序遍历:先访问根节点,然后遍历左子树,最后遍历右子树
- ② 中序遍历:先遍历左子树,然后访问根节点,最后遍历右子树
- 后序遍历:先遍历左子树,然后遍历右子树,最后访问树的根节点
小结
- 前序:根左右;中序:左根右;后序:左右根;
- 通过中序遍历得到一个递增的有序序列
- 后序可用于数学中的后缀表示法,结合栈处理表达式:遇到一个操作符,就可以从栈中弹出栈顶的两个元素,计算并将结果返回到栈中
2、二叉树的前序遍历 (LeetCode 144)
方法1——递归
- 首先判断当前节点(默认其是根节点),是否存在
- 若当前节点存在,则加入列表;然后依次递归当前节点的左子树、右子树
- 若不存在则返回
class Solution {
List<Integer> res = new ArrayList<Integer>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root == null) return res;
res.add(root.val);
if(root.left != null) preorderTraversal(root.left);
if(root.right != null) preorderTraversal(root.right);
return res;
}
}
或
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
// 创建结果数组
List<Integer> res = new ArrayList<Integer>();
// 前序遍历方法
preOrder(root, res);
return res;
}
public void preOrder(TreeNode root, List<Integer> res){
// 若当前节点不存在,则返回
if(root == null) return ;
// 当前节点存在,将值存入列表
res.add(root.val);
// 先左、后右地递归
preOrder(root.left, res);
preOrder(root.right, res);
}
}
方法2——迭代:使用栈存储数据
- 按照根左右的顺序查询
- 若当前节点存在,则将该节点压入栈
- 若当前节点不存在,则将栈顶的一个弹出来
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
// 创建结果数组
List<Integer> res = new ArrayList<Integer>();
if(root == null) return res;
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode cur = root;
while(!stack.isEmpty() || cur != null){
while(cur != null){
res.add(cur.val);
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
cur = cur.right; // 因为根和左子树的值都已经加入列表了
}
return res;
}
}
方法3——Morris遍历
- 假设先将所有node.right打断(实际编程不用,图解示意而已)
- 对于某个节点cur,找到其左子树中最右侧末端的节点,将其 “插入” 连接原本cur.right指向的节点;若左子树无右侧节点,则将左子树(节点)插入;若无左子树,跳过,如:
- 对于B节点,A是左子树,但是A没有任何左子树(节点)或右子树(节点),直接将A插入B.right,原本指向的D节点前面
- 对于D节点,则是将C插入D.right,E节点前面
- 对于F节点,左子树的最右侧末端节点是E,则将E插入F.right,G节点前面
- 对于G节点,无左子树,则跳过
- 最后,根据连接线的顺序,遍历即为结果
- 【注】由于并不是真的“打断”,而最右侧末端节点连接cur节点时,在debug过程会出现成环错误
Error - Found cycle in the TreeNode
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null) return res;
TreeNode cur = root, temp = null;
while(cur != null){
temp = cur.left; // cur节点的左子树首个节点
if(temp != null){
while(temp.right != null && temp.right != cur){
temp = temp.right; // 一直达到右侧最末端节点
}
if(temp.right == null){
res.add(cur.val);
temp.right = cur; // Error - Found cycle in the TreeNode
cur = cur.left;
continue;
} else {
temp.right = null;
}
} else {
res.add(cur.val);
}
cur = cur.right;
}
return res;
}
}
3、二叉树的中序遍历 (LeetCode 94)
方法1——递归
class Solution {
List<Integer> res = new ArrayList<Integer>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null) return res;
if(root.left != null) inorderTraversal(root.left);
res.add(root.val);
if(root.right != null) inorderTraversal(root.right);
return res;
}
}
或
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return ;
if(root.left != null) inorder(root.left, res);
res.add(root.val);
if(root.right != null) inorder(root.right, res);
}
}
方法2——迭代
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stack = new LinkedList<TreeNode>();
while(!stack.isEmpty() || root != null){
while(root != null){
stack.push(root);
root = root.left;
}
root = stack.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
方法3——Morris遍历
- 优点:没有使用任何辅助空间
- 缺点:改变了整个树的结构,强行把二叉树改成链表
- 步骤:
- ① 判断当前节点cur的左节点cur.left,是否存在
- ② 若cur.left存在,则当前节点cur以及连带cur的全部右子树,挂在cur.left的最右侧节点下
- ③ 若cur.left为空了,则输出这个节点,向右遍历
- ④ 重复以上操作
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
TreeNode prev = null;
while(root != null){
if(root.left != null){
prev = root.left; // 判断当前节点的左子树是否存在
// 若存在,直接指向左子树最最右侧的节点
while(prev.right != null) prev = prev.right;
// 将当前节点及其左、右子树连接到左子树最最右侧节点的右侧
prev.right = root;
TreeNode temp = root;
// 将当前节点指向左子树
root = root.left;
// 将之前连接的一团中,其左子树“砍掉”
temp.left = null;
} else {
res.add(root.val);
root = root.right;
}
}
return res;
}
}
图解
4、二叉树的后序遍历 (LeetCode 145)
方法1——递归
class Solution {
List<Integer> res = new ArrayList<Integer>();
public List<Integer> postorderTraversal(TreeNode root) {
if(root == null) return res;
if(root.left != null) postorderTraversal(root.left);
if(root.right != null) postorderTraversal(root.right);
res.add(root.val);
return res;
}
}
或
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>(