669. 修剪二叉搜索树
这道题目比较难,比 添加增加和删除节点难的多,建议先看视频理解。题目链接/文章讲解: 代码随想录
视频讲解: 你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树_哔哩哔哩_bilibili
重点:
1. 二叉搜索树,根据当前节点的值可以知道把哪边子树当作根节点
思路:
递归法:
1. 确定参数及返回值
public TreeNode trimBST(TreeNode root, int low, int high)
2. 确定终止条件
if (root == null) { return null; }
3. 确定单层递归的逻辑
根据值的大小,递归的修剪左/右子树,然后回溯给上一层赋值即可
// 当前值小,修剪右子树 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);
迭代法:
1. 不需要栈来模拟前中后序
2. 在剪枝的时候,可以分为三步:
- 将root移动到[L, R] 范围内,注意是左闭右闭区间
- 剪枝左子树
- 剪枝右子树
public TreeNode trimBST(TreeNode root, int low, int high) {
// 终止条件
if (root == null) {
return null;
}
// 单层递归的逻辑
// 当前值小于low值,需要向右子树移动,但右子树可能也需要修剪
if (root.val < low) {
return trimBST(root.right, low, high);
}
// 当前值大于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;
}
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) {
return null;
}
// 先找到在区间的位置
while (root != null && (root.val < low || root.val > high)) {
if (root.val < low) {
root = root.right;
} else {
root = root.left;
}
}
TreeNode cur = root;
// 开始修剪左子树
while (cur != null) {
while (cur.left != null && cur.left.val < low) {
cur.left = cur.left.right;
}
cur = cur.left;
}
cur = root;
// 开始修剪右子树
while (cur != null) {
while (cur.right != null && cur.right.val > high) {
cur.right = cur.right.left;
}
cur = cur.right;
}
return root;
}
108. 将有序数组转换为二叉搜索树
本题就简单一些,可以尝试先自己做做。
题目链接/文章讲解:代码随想录
视频讲解:构造平衡二叉搜索树!| LeetCode:108.将有序数组转换为二叉搜索树_哔哩哔哩_bilibili
重点;
1. 循环不变量原则,左闭右闭 / 左闭右开
思路:
递归法:
1. 确定参数及返回值
private TreeNode traversal(int[] nums, int leftIndex, int rightIndex)
2. 确定终止条件
当前这部分数组已经构成好子树了,所以才会index超标
if (leftIndex > rightIndex) { return null; }
3. 确定单层递归的逻辑
把数组分成前后两部分,递归的组成子树
int midIndex = (leftIndex + rightIndex) / 2; TreeNode curRootNode = new TreeNode(nums[midIndex]); // 左闭右闭 curRootNode.left = traversal(nums, leftIndex, midIndex - 1); curRootNode.right = traversal(nums, midIndex + 1, rightIndex); return curRootNode;
public TreeNode sortedArrayToBST(int[] nums) {
if (nums.length == 0) {
return null;
}
// 左闭右闭
TreeNode root = traversal(nums, 0, nums.length - 1);
return root;
}
private TreeNode traversal(int[] nums, int leftIndex, int rightIndex) {
if (leftIndex > rightIndex) {
return null;
}
int midIndex = (leftIndex + rightIndex) / 2;
TreeNode curRootNode = new TreeNode(nums[midIndex]);
// 左闭右闭
curRootNode.left = traversal(nums, leftIndex, midIndex - 1);
curRootNode.right = traversal(nums, midIndex + 1, rightIndex);
return curRootNode;
}
538. 把二叉搜索树转换为累加树
本题也不难,在 求二叉搜索树的最小绝对差 和 众数 那两道题目 都讲过了 双指针法,思路是一样的。
题目链接/文章讲解:代码随想录
视频讲解:普大喜奔!二叉树章节已全部更完啦!| LeetCode:538.把二叉搜索树转换为累加树_哔哩哔哩_bilibili
重点:
1. 倒叙遍历有序数组可实现累加+双指针记录前一个节点即可
思路:
1. 确定参数及返回值
其实可以void,因为只是单纯的遍历全部节点,也并没有需要处理递归返回值
public TreeNode convertBST(TreeNode root)
2. 确定终止条件
if (root == null) { return null; }
3. 确定单层递归的逻辑
把中序倒过来遍历,就可以实现累加了,还要一个双指针来记录前一个节点
// 右 convertBST(root.right); // 中 root.val += pre; pre = root.val; // 左 convertBST(root.left);
迭代法:
1. 用栈来记录遍历的顺序,但是因为我们要累加,所以是右中左来遍历
int pre = 0;
public TreeNode convertBST(TreeNode root) {
if (root == null) {
return null;
}
// 右
convertBST(root.right);
// 中
root.val += pre;
pre = root.val;
// 左
convertBST(root.left);
return root;
}
int pre = 0;
public TreeNode convertBST(TreeNode root) {
Deque<TreeNode> stack = new LinkedList<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.offerLast(cur);
// 右
cur = cur.right;
} else {
// 中
cur = stack.removeLast();
cur.val += pre;
pre = cur.val;
// 左
cur = cur.left;
}
}
return root;
}
二叉树总结
二叉树的理论基础
- 关于二叉树,你该了解这些! (opens new window):二叉树的种类、存储方式、遍历方式、定义方式
二叉树的遍历方式
- 深度优先遍历
- 广度优先遍历
- 二叉树的层序遍历 (opens new window):通过队列模拟
求二叉树的属性
- 二叉树:是否对称(opens new window)
- 递归:后序,比较的是根节点的左子树与右子树是不是相互翻转
- 迭代:使用队列/栈将两个节点顺序放入容器中进行比较
- 二叉树:求最大深度(opens new window)
- 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值做计算树的高度
- 迭代:层序遍历
- 二叉树:求最小深度(opens new window)
- 递归:后序,求根节点最小高度就是最小深度,注意最小深度的定义
- 迭代:层序遍历
- 二叉树:求有多少个节点(opens new window)
- 递归:后序,通过递归函数的返回值计算节点数量
- 迭代:层序遍历
- 二叉树:是否平衡(opens new window)
- 递归:后序,注意后序求高度和前序求深度,递归过程判断高度差
- 迭代:效率很低,不推荐
- 二叉树:找所有路径(opens new window)
- 递归:前序,方便让父节点指向子节点,涉及回溯处理根节点到叶子的所有路径
- 迭代:一个栈模拟递归,一个栈来存放对应的遍历路径
- 二叉树:递归中如何隐藏着回溯(opens new window)
- 详解二叉树:找所有路径 (opens new window)中递归如何隐藏着回溯
- 二叉树:求左叶子之和(opens new window)
- 递归:后序,必须三层约束条件,才能判断是否是左叶子。
- 迭代:直接模拟后序遍历
- 二叉树:求左下角的值(opens new window)
- 递归:顺序无所谓,优先左孩子搜索,同时找深度最大的叶子节点。
- 迭代:层序遍历找最后一行最左边
- 二叉树:求路径总和(opens new window)
- 递归:顺序无所谓,递归函数返回值为bool类型是为了搜索一条边,没有返回值是搜索整棵树。
- 迭代:栈里元素不仅要记录节点指针,还要记录从头结点到该节点的路径数值总和
二叉树的修改与构造
- 翻转二叉树(opens new window)
- 递归:前序,交换左右孩子
- 迭代:直接模拟前序遍历
- 构造二叉树(opens new window)
- 递归:前序,重点在于找分割点,分左右区间构造
- 迭代:比较复杂,意义不大
- 构造最大的二叉树(opens new window)
- 递归:前序,分割点为数组最大值,分左右区间构造
- 迭代:比较复杂,意义不大
- 合并两个二叉树(opens new window)
- 递归:前序,同时操作两个树的节点,注意合并的规则
- 迭代:使用队列,类似层序遍历
求二叉搜索树的属性
- 递归:二叉搜索树的递归是有方向的
- 迭代:因为有方向,所以迭代法很简单
- 递归:中序,相当于变成了判断一个序列是不是递增的
- 迭代:模拟中序,逻辑相同
求二叉搜索树的最小绝对差(opens new window)
- 递归:中序,双指针操作
- 迭代:模拟中序,逻辑相同
递归:中序,清空结果集的技巧,遍历一遍便可求众数集合
递归:中序,双指针操作累加
迭代:模拟中序,逻辑相同
二叉树的公共祖先问题
- 二叉树的公共祖先问题(opens new window)
- 递归:后序,回溯,找到左子树出现目标值,右子树节点目标值的节点。
- 迭代:不适合模拟回溯
- 二叉搜索树的公共祖先问题(opens new window)
- 递归:顺序无所谓,如果节点的数值在目标区间就是最近公共祖先
- 迭代:按序遍历
二叉搜索树的修改与构造
- 二叉搜索树中的插入操作(opens new window)
- 递归:顺序无所谓,通过递归函数返回值添加节点
- 迭代:按序遍历,需要记录插入父节点,这样才能做插入操作
- 二叉搜索树中的删除操作(opens new window)
- 递归:前序,想清楚删除非叶子节点的情况
- 迭代:有序遍历,较复杂
- 修剪二叉搜索树(opens new window)
- 递归:前序,通过递归函数返回值删除节点
- 迭代:有序遍历,较复杂
- 构造二叉搜索树(opens new window)
- 递归:前序,数组中间节点分割
- 迭代:较复杂,通过三个队列来模拟
阶段总结
大家以上题目都做过了,也一定要看如下阶段小结。
每周小结都会对大家的疑问做统一解答,并且对每周的内容进行拓展和补充,所以一定要看,将细碎知识点一网打尽!
- 本周小结!(二叉树系列一)(opens new window)
- 本周小结!(二叉树系列二)(opens new window)
- 本周小结!(二叉树系列三)(opens new window)
- 本周小结!(二叉树系列四)
最后总结
在二叉树题目选择什么遍历顺序是不少同学头疼的事情,我们做了这么多二叉树的题目了,Carl给大家大体分分类。
涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。
注意在普通二叉树的属性中,我用的是一般为后序,例如单纯求深度就用前序,二叉树:找所有路径 (opens new window)也用了前序,这是为了方便让父节点指向子节点。
所以求普通二叉树的属性还是要具体问题具体分析。
二叉树专题汇聚为一张图: