《代码随想录》二叉树专题算法笔记
《代码随想录》二叉树专题算法笔记
本文整理《代码随想录》中二叉树专题的经典算法题目,包含题目要点分析与完整代码实现,覆盖算法训练营 Day13 至 Day21 打卡内容,涵盖二叉树的遍历、构造、修改、查询等核心场景。
一、二叉树的遍历
1. 二叉树的递归遍历(Day13-01)
核心要点
- 二叉树定义:每个节点包含
val
(值)、left
(左子节点)、right
(右子节点)。 - 递归遍历本质:通过调整 “处理当前节点” 与 “递归左右子树” 的顺序,实现前序、中序、后序遍历:
- 前序遍历:处理当前节点 → 递归左子树 → 递归右子树(根左右)。
- 中序遍历:递归左子树 → 处理当前节点 → 递归右子树(左根右)。
- 后序遍历:递归左子树 → 递归右子树 → 处理当前节点(左右根)。
- 终止条件:当
root == null
时,直接返回(递归边界)。
核心代码(以中序遍历为例)
// 假设result为存储遍历结果的List<Integer>
public void inorder(TreeNode root, List<Integer> result) {if (root == null) {return;}// 中序遍历:左 → 根 → 右(调整顺序可切换为前/后序)inorder(root.left, result); // 递归左子树result.add(root.val); // 处理当前节点inorder(root.right, result); // 递归右子树
}
2. 二叉树的层序遍历(Day13-02)
核心要点
- 层序遍历:按 “从上到下、从左到右” 的顺序遍历每一层节点,需借助队列(先进先出特性)实现。
- 关键逻辑:
- 初始化队列,将根节点入队。
- 外层循环:队列不为空时,获取当前层的节点数量
size
(确保内层循环只处理当前层节点)。 - 内层循环:遍历当前层
size
个节点,将节点值存入临时列表,同时将其左右子节点(非空)入队。 - 将当前层的临时列表加入结果集,直至队列空。
代码实现
class Solution {// 存储最终层序遍历结果(二维列表:每层一个子列表)List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> levelOrder(TreeNode root) {checkFun(root);return res;}// 辅助方法:实现层序遍历逻辑public void checkFun(TreeNode root) {if (root == null) return; // 根为空,直接返回Queue<TreeNode> que = new LinkedList<>();que.offer(root); // 根节点入队while (!que.isEmpty()) {List<Integer> item = new ArrayList<>(); // 存储当前层节点值int size = que.size(); // 当前层节点数量(固定,避免后续入队影响)// 遍历当前层所有节点while (size-- > 0) {TreeNode node = que.poll(); // 出队当前节点item.add(node.val); // 记录节点值// 左右子节点非空则入队(保证下一层遍历)if (node.left != null) que.offer(node.left);if (node.right != null) que.offer(node.right);}res.add(item); // 当前层加入结果集}}
}
3. 二叉树的层序遍历 II(Day13-03)
核心要点
- 需求:层序遍历结果 “从下到上”(即逆序输出层序遍历结果)。
- 实现思路:在普通层序遍历基础上,最后对结果集进行反转(或遍历过程中从头部插入结果)。
代码实现
class Solution {List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> levelOrderBottom(TreeNode root) {checkFun(root);return res;}public void checkFun(TreeNode root) {List<List<Integer>> curRes = new ArrayList<>(); // 临时存储正序层序结果if (root == null) return;Queue<TreeNode> que = new LinkedList<>();que.offer(root);while (!que.isEmpty()) {List<Integer> item = new ArrayList<>();int size = que.size();while (size-- > 0) {TreeNode node = que.poll();item.add(node.val);if (node.left != null) que.offer(node.left);if (node.right != null) que.offer(node.right);}curRes.add(item); // 正序存储每层}// 反转正序结果,得到“从下到上”的层序遍历for (int i = curRes.size() - 1; i >= 0; i--) {res.add(curRes.get(i));}}
}
二、二叉树的修改与判断
1. 翻转二叉树(Day14-01)
核心要点
- 需求:交换每个节点的左右子节点(即 “镜像翻转”)。
- 实现思路:采用前序 / 后序遍历(中序遍历需注意顺序,可能导致重复交换),递归交换每个节点的左右子树。
- 关键逻辑:先交换当前节点的左右子节点,再递归交换左右子树(前序);或先递归交换左右子树,再交换当前节点(后序)。
代码实现
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) return root; // 空树直接返回// 前序遍历:先交换当前节点,再递归左右子树(后序需调整顺序)swap(root); // 交换当前节点的左右子节点invertTree(root.left); // 递归翻转左子树invertTree(root.right); // 递归翻转右子树return root;}// 辅助方法:交换节点的左右子节点public void swap(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}
2. 对称二叉树(Day14-02)
核心要点
- 需求:判断二叉树是否 “对称”(即左右子树互为镜像)。
- 递归思路:比较左子树的左节点与右子树的右节点(外侧)、左子树的右节点与右子树的左节点(内侧),需覆盖所有节点为空 / 非空的情况。
- 终止条件(节点对比逻辑):
- 左空、右非空 → 不对称(返回
false
)。 - 左非空、右空 → 不对称(返回
false
)。 - 左空、右空 → 对称(返回
true
)。 - 左值 ≠ 右值 → 不对称(返回
false
)。
- 左空、右非空 → 不对称(返回
代码实现
class Solution {public boolean isSymmetric(TreeNode root) {// 空树对称;非空树需比较左右子树是否对称return root == null ? true : compare(root.left, root.right);}// 辅助方法:比较两个节点是否对称(递归核心)public boolean compare(TreeNode left, TreeNode right) {// 1. 处理节点为空的情况if (left == null && right != null) return false;if (left != null && right == null) return false;if (left == null && right == null) return true;// 2. 节点非空,比较值是否相等if (left.val != right.val) return false;// 3. 递归比较外侧(左左 vs 右右)和内侧(左右 vs 右左)boolean outside = compare(left.left, right.right); // 外侧boolean inside = compare(left.right, right.left); // 内侧return outside && inside; // 两者都对称才返回true}
}
3. 二叉树的最大深度(Day14-03)
核心要点
- 定义:二叉树的最大深度是 “从根节点到最远叶子节点的最长路径上的节点数”。
- 递归思路:采用后序遍历,先求左子树深度和右子树深度,当前节点的深度 = 左右子树深度的最大值 + 1(+1 表示当前节点本身)。
- 终止条件:空节点的深度为 0。
代码实现
class Solution {public int maxDepth(TreeNode root) {if (root == null) return 0; // 空节点深度为0// 后序遍历:左 → 右 → 根(先求左右子树深度)int leftHeight = maxDepth(root.left); // 左子树深度int rightHeight = maxDepth(root.right); // 右子树深度// 当前节点深度 = 左右子树最大深度 + 1return Math.max(leftHeight, rightHeight) + 1;}
}
4. 二叉树的最小深度(Day14-04)
核心要点
- 定义:二叉树的最小深度是 “从根节点到最近叶子节点的最短路径上的节点数”。
- 与最大深度的区别:需排除 “单支子树” 的情况(如左子树为空、右子树非空时,最小深度是右子树深度 + 1,而非 1)。
- 关键逻辑:后序遍历中,若左子树为空则返回右子树深度 + 1,若右子树为空则返回左子树深度 + 1,否则返回左右子树最小深度 + 1。
代码实现
class Solution {public int minDepth(TreeNode root) {if (root == null) return 0; // 空节点深度为0// 后序遍历:先求左右子树深度int minLeft = minDepth(root.left);int minRight = minDepth(root.right);// 处理单支子树情况(排除非叶子节点的“空分支”)if (root.left == null && root.right != null) {return minRight + 1;}if (root.left != null && root.right == null) {return minLeft + 1;}// 左右子树都非空,返回最小深度 + 1return Math.min(minLeft, minRight) + 1;}
}
5. 平衡二叉树(Day15-01)
核心要点
-
定义:平衡二叉树(AVL 树)是 “所有节点的左右子树高度差的绝对值 ≤ 1” 的二叉树。
-
递归思路:采用
后序遍历
,在求子树高度的同时判断是否平衡:
- 若子树不平衡,返回
-1
(标记不平衡状态)。 - 若子树平衡,返回子树的高度(用于上层判断)。
- 若子树不平衡,返回
-
关键逻辑:若左右子树高度差 > 1,返回
-1
;否则返回当前子树高度(左右子树最大高度 + 1)。
代码实现
class Solution {public boolean isBalanced(TreeNode root) {// 若getHeight返回-1,说明树不平衡;否则平衡return getHeight(root) != -1;}// 辅助方法:求子树高度,若不平衡返回-1public int getHeight(TreeNode root) {if (root == null) return 0; // 空节点高度为0// 后序遍历:先求左右子树高度int leftHeight = getHeight(root.left);if (leftHeight == -1) return -1; // 左子树不平衡,直接返回-1int rightHeight = getHeight(root.right);if (rightHeight == -1) return -1; // 右子树不平衡,直接返回-1// 判断当前节点是否平衡if (Math.abs(leftHeight - rightHeight) > 1) {return -1; // 不平衡,返回-1}// 平衡,返回当前子树高度return Math.max(leftHeight, rightHeight) + 1;}
}
三、二叉树的路径与求和
1. 二叉树的所有路径(Day15-02)
核心要点
-
需求:输出从根节点到所有叶子节点的路径(格式如
1->2->3
)。 -
思路:
回溯算法
(前序遍历 + 路径回溯):
- 用列表
path
记录当前路径,用列表res
记录所有路径。 - 前序遍历:先将当前节点加入
path
,若为叶子节点则拼接路径并加入res
。 - 递归左右子树后,需 “回溯”(删除
path
最后一个元素),避免路径污染。
- 用列表
代码实现
class Solution {// 全局变量:存储所有路径(最终结果)List<String> res = new ArrayList<>();// 全局变量:存储当前路径(节点值)List<Integer> path = new ArrayList<>();public List<String> binaryTreePaths(TreeNode root) {if (root == null) return res;backtracking(root); // 回溯遍历所有路径return res;}// 回溯方法:遍历从当前节点到叶子节点的路径public void backtracking(TreeNode root) {path.add(root.val); // 前序:先加入当前节点// 终止条件:当前节点是叶子节点(左右子树都为空)if (root.left == null && root.right == null) {StringBuilder sb = new StringBuilder();// 拼接路径(除最后一个节点外,每个节点后加"->")for (int i = 0; i < path.size() - 1; i++) {sb.append(path.get(i)).append("->");}sb.append(path.get(path.size() - 1)); // 拼接最后一个节点res.add(sb.toString()); // 加入结果集return;}// 递归左子树(非空才处理)if (root.left != null) {backtracking(root.left);path.remove(path.size() - 1); // 回溯:删除左子树的节点}// 递归右子树(非空才处理)if (root.right != null) {backtracking(root.right);path.remove(path.size() - 1); // 回溯:删除右子树的节点}}
}
2. 左叶子之和(Day15-03)
核心要点
- 定义:左叶子是 “父节点的左子节点,且自身没有左右子节点” 的节点。
- 思路:后序遍历,先求左子树的左叶子和,再求右子树的左叶子和,最后相加。
- 关键逻辑:判断当前节点的左子节点是否为左叶子(左子节点非空,且左子节点的左右子树都为空),若是则累加其值。
代码实现
class Solution {public int sumOfLeftLeaves(TreeNode root) {if (root == null) return 0; // 空树和为0if (root.left == null && root.right == null) return 0; // 叶子节点无左叶子// 后序遍历:左 → 右 → 根int leftSum = sumOfLeftLeaves(root.left); // 左子树的左叶子和// 判断当前节点的左子节点是否为左叶子if (root.left != null && root.left.left == null && root.left.right == null) {leftSum = root.left.val; // 是左叶子,直接取其值}int rightSum = sumOfLeftLeaves(root.right); // 右子树的左叶子和return leftSum + rightSum; // 总左叶子和}
}
-
Day16
1. 找树左下角的值
方法一:递归(DFS + 回溯)
- 要点:
- 使用两个全局变量:
val
存储当前最左下角的值,Deep
存储其深度。 - 只有当当前节点是叶子节点且深度大于
Deep
时才更新。 - 使用回溯维护当前深度。
- 使用两个全局变量:
class Solution {int val = 0;int Deep = -1;public int findBottomLeftValue(TreeNode root) {val = root.val;backtracking(root, 0);return val;}public void backtracking(TreeNode root, int deep) {if (root == null) return;if (root.left == null && root.right == null) {if (deep > Deep) {Deep = deep;val = root.val;}}if (root.left != null) {deep += 1;backtracking(root.left, deep);deep -= 1;}if (root.right != null) {deep += 1;backtracking(root.right, deep);deep -= 1;}} }
方法二:层序遍历(BFS)
- 要点:
- 每层第一个节点即为该层最左边的节点。
- 最后一层的第一个节点即为答案。
class Solution {public int findBottomLeftValue(TreeNode root) {Queue<TreeNode> que = new LinkedList<>();que.offer(root);int res = 0;while (!que.isEmpty()) {int size = que.size();for (int i = 0; i < size; i++) {TreeNode temp = que.poll();if (i == 0) res = temp.val;if (temp.left != null) que.offer(temp.left);if (temp.right != null) que.offer(temp.right);}}return res;} }
2. 路径总和
- 要点:
- 递归判断是否存在从根到叶子节点的路径,使得路径上所有节点值之和等于
targetSum
。 - 使用回溯 + 剪枝(一旦找到就返回)。
- 递归判断是否存在从根到叶子节点的路径,使得路径上所有节点值之和等于
class Solution {public boolean hasPathSum(TreeNode root, int targetSum) {if (root == null) return false;if (root.left == null && root.right == null) {return targetSum == root.val;}if (root.left != null) {if (hasPathSum(root.left, targetSum - root.left.val)) return true;}if (root.right != null) {if (hasPathSum(root.right, targetSum - root.right.val)) return true;}return false;} }
3. 从中序与后序遍历序列构造二叉树
- 要点:
- 后序最后一个元素为根。
- 在中序中找到根的位置,分割左右子树。
- 递归构建左右子树(使用左闭右开区间)。
class Solution {public TreeNode buildTree(int[] inorder, int[] postorder) {if (postorder.length == 0) return null;return buildHelper(inorder, 0, inorder.length, postorder, 0, postorder.length);}public TreeNode buildHelper(int[] inorder, int inStart, int inEnd,int[] postorder, int postStart, int postEnd) {if (postStart == postEnd) return null;int rootVal = postorder[postEnd - 1];TreeNode root = new TreeNode(rootVal);int index;for (index = inStart; index < inEnd; index++) {if (inorder[index] == rootVal) break;}int leftInStart = inStart;int leftInEnd = index;int rightInStart = index + 1;int rightInEnd = inEnd;int leftPostStart = postStart;int leftPostEnd = postStart + (leftInEnd - leftInStart);int rightPostStart = leftPostEnd;int rightPostEnd = postEnd - 1;root.left = buildHelper(inorder, leftInStart, leftInEnd,postorder, leftPostStart, leftPostEnd);root.right = buildHelper(inorder, rightInStart, rightInEnd,postorder, rightPostStart, rightPostEnd);return root;} }
Day17
1. 最大二叉树
- 要点:
- 找到数组最大值作为根,递归构建左右子树。
- 使用左闭右开区间。
class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {return constructMaximumBinaryTree1(nums, 0, nums.length);}public TreeNode constructMaximumBinaryTree1(int[] nums, int left, int right) {if (left == right) return null;if (right - left == 1) return new TreeNode(nums[left]);int maxVal = nums[left], index = left;for (int i = left + 1; i < right; i++) {if (nums[i] > maxVal) {maxVal = nums[i];index = i;}}TreeNode root = new TreeNode(maxVal);root.left = constructMaximumBinaryTree1(nums, left, index);root.right = constructMaximumBinaryTree1(nums, index + 1, right);return root;} }
2. 合并二叉树
- 要点:
- 同时遍历两棵树,合并到
root1
上。 - 若某节点为空,直接返回另一棵树对应节点。
- 同时遍历两棵树,合并到
class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1 == null) return root2;if (root2 == null) return root1;root1.val += root2.val;root1.left = mergeTrees(root1.left, root2.left);root1.right = mergeTrees(root1.right, root2.right);return root1;} }
3. 二叉搜索树中的搜索
- 要点:
- 利用 BST 性质:左小右大。
- 可用递归或迭代。
class Solution {public TreeNode searchBST(TreeNode root, int val) {while (root != null) {if (root.val > val) root = root.left;else if (root.val < val) root = root.right;else return root;}return null;} }
4. 验证二叉搜索树
- 要点:
- 中序遍历应为严格递增序列。
- 使用
pre
指针记录前一个节点。
class Solution {TreeNode pre = null;public boolean isValidBST(TreeNode root) {if (root == null) return true;boolean left = isValidBST(root.left);if (pre != null && pre.val >= root.val) return false;pre = root;boolean right = isValidBST(root.right);return left && right;} }
Day18
1. 二叉搜索树的最小绝对差
- 要点:
- 中序遍历相邻节点差值最小。
- 全局变量
pre
和min
。
class Solution {int min = Integer.MAX_VALUE;TreeNode pre = null;public int getMinimumDifference(TreeNode root) {if (root == null) return 0;getMinimumDifference(root.left);if (pre != null) {min = Math.min(min, root.val - pre.val);}pre = root;getMinimumDifference(root.right);return min;} }
2. 二叉搜索树中的众数
- 要点:
- 中序遍历统计频率。
- 使用
count
、maxCount
、pre
、res
四个全局变量。
class Solution {TreeNode pre = null;int count = 0;int maxCount = 0;List<Integer> res = new ArrayList<>();public int[] findMode(TreeNode root) {findMode1(root);return res.stream().mapToInt(i -> i).toArray();}public void findMode1(TreeNode root) {if (root == null) return;findMode1(root.left);if (pre == null || pre.val != root.val) count = 1;else count++;if (count == maxCount) res.add(root.val);else if (count > maxCount) {res.clear();maxCount = count;res.add(root.val);}pre = root;findMode1(root.right);} }
3. 二叉树的最近公共祖先
- 要点:
- 后序遍历(自底向上)。
- 若左右子树分别包含
p
和q
,则当前节点为 LCA。
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if (root == null || root == p || root == q) return root;TreeNode left = lowestCommonAncestor(root.left, p, q);TreeNode right = lowestCommonAncestor(root.right, p, q);if (left == null && right == null) return null;if (left != null && right == null) return left;if (left == null && right != null) return right;return root;} }
Day20
1. 二叉搜索树的最近公共祖先
- 要点:
- 利用 BST 性质:若
root.val
在p
和q
之间,则为 LCA。 - 可用迭代法高效实现。
- 利用 BST 性质:若
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {while (root != null) {if (root.val > p.val && root.val > q.val) root = root.left;else if (root.val < p.val && root.val < q.val) root = root.right;else return root;}return root;} }
2. 二叉搜索树中的插入操作
- 要点:
- 插入位置一定是叶子节点。
- 递归到底后新建节点并返回。
class Solution {public TreeNode insertIntoBST(TreeNode root, int val) {if (root == null) return new TreeNode(val);if (root.val > val) root.left = insertIntoBST(root.left, val);if (root.val < val) root.right = insertIntoBST(root.right, val);return root;} }
- 要点:
3. 删除二叉搜索树中的节点
- 要点:
- 分四种情况处理:
- 节点无子节点 → 删除;
- 仅有左/右子树 → 返回子树;
- 有左右子树 → 将右子树最左节点接上左子树,返回右子树。
- 分四种情况处理:
class Solution {public TreeNode deleteNode(TreeNode root, int key) {if (root == null) return null;if (root.val == key) {if (root.left == null && root.right == null) return null;else if (root.left != null && root.right == null) return root.left;else if (root.left == null && root.right != null) return root.right;else {TreeNode cur = root.right;while (cur.left != null) cur = cur.left;cur.left = root.left;return root.right;}}if (root.val < key) root.right = deleteNode(root.right, key);if (root.val > key) root.left = deleteNode(root.left, key);return root;}
}
Day21
1. 修剪二叉搜索树
- 要点:
- 前序遍历。
- 若当前节点小于
low
,则其左子树全无效,递归右子树; - 若大于
high
,则右子树无效,递归左子树。
class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if (root == null) return null;if (root.val < low) return trimBST(root.right, low, high);if (root.val > high) return trimBST(root.left, low, high);root.left = trimBST(root.left, low, high);root.right = trimBST(root.right, low, high);return root;}
}
2. 将有序数组转换为二叉搜索树
- 要点:
- 选中点为根,递归构建左右子树(左闭右闭)。
- 保证树高度平衡。
class Solution {public TreeNode sortedArrayToBST(int[] nums) {return build(nums, 0, nums.length - 1);}public TreeNode build(int[] nums, int left, int right) {if (left > right) return null;int mid = (left + right) / 2;TreeNode root = new TreeNode(nums[mid]);root.left = build(nums, left, mid - 1);root.right = build(nums, mid + 1, right);return root;}
}
3. 把二叉搜索树转换为累加树
- 要点:
- 反向中序遍历(右 → 中 → 左)。
- 累加右侧所有更大值。
class Solution {int pre = 0;public TreeNode convertBST(TreeNode root) {convertBST1(root);return root;}public void convertBST1(TreeNode root) {if (root == null) return;convertBST1(root.right);root.val += pre;pre = root.val;convertBST1(root.left);}
}