669.修剪二叉搜索树
- 误区:节点不在范围内不能直接return null,因为它的右子树是可能符合边界的。虽然要删除这个节点,但还要先继续遍历右子树。
递归套递归,删除修剪的同时去递归修剪左/右子树,然后在下面递归左右子树的时候用root.left right接住
- 确定递归函数 传入新的左右边界,返回新的根节点
TreeNode trimBST(TreeNode root, int low, int high)
- 终止条件
if(root == null) return null;
- 单层递归逻辑
如果一个节点在边界左侧,还要往右遍历。它的返回值是这棵右子树修剪完后的根节点。
不能直接return right!因为右子树不一定完全符合边界,要递归处理。
if(root.val < low) {right = traversal(root.right,low,high); //返回值要在上一层接住return right; //一会在上一层的左子树接住
}
if(root.val > high) { //在边界右侧的情况同理left = traversal(root.left,low,high);return left; //一会在上一层的右子树接住
}
root.left = traversal(root.left,low,high);
root.right = traversal(root.right,low,high);
return root;
- 完整代码
class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if(root == null) return null;if(root.val < low ) {TreeNode right = trimBST(root.right,low,high);return right;}if(root.val > high) {TreeNode left = trimBST(root.left,low,high);return left;}root.left = trimBST(root.left,low,high);root.right = trimBST(root.right,low,high);return root;}
}
这一题和上一题都是在递归的推出条件上做的文章, 遍历顺序倒无所谓
108.将有序数组转换为二叉搜索树
- 注意 要构造一棵平衡二叉搜索树
大致思路:每一次都选择中间的节点,将这个数组分为左区间和右区间,递归遍历左区间构造左子树,遍历右区间构造右子树。
长度为偶数怎么办呢?—取左侧或者右侧的都可以
每次进入递归的时候new出来节点赋值,把它return回去 递归的时候用root.left/right接住它
注意区间的定义:保持左闭右闭!
- 确定递归函数
TreeNode traversal(int[] nums, left, right)
-
确定终止条件(什么时候是非法的区间?停止构造)
当left = right的时候,区间是合法的。
if(left > right) return null; //此时才是非法区间
- 单层递归逻辑
int mid = (left + right)/2;
TreeNode root = new TreeNode(nums[mid]); //构造根节点
//接下来递归构造左右子树
root.left = traversal(nums,left,mid-1);
root.right = traversal(nums,mid+1,right);
return root;
- 调用递归函数
return traversal(nums,0,nums.length-1);
class Solution {public TreeNode traversal(int[] nums, int left, int right) {if(left > right) return null;int mid = (left + right) / 2;TreeNode root = new TreeNode(nums[mid]);root.left = traversal(nums, left, mid-1); //更新右边界root.right = traversal(nums, mid+1, right);return root;}public TreeNode sortedArrayToBST(int[] nums) {return traversal(nums,0,nums.length-1);}
}
538.把二叉搜索树转换为累加树
- 思路:如果给一个有序数组,变成累加数组!就是从后往前遍历,逐个累加前面节点的数值。
二叉搜索树的中序遍历(左中右)就是一个有序(升序)数组!
如何倒序遍历呢?右中左!数组的话用双指针实现,二叉树这里也可以用双指针!pre初始化为0
class Solution {TreeNode pre = null;//右中左 遍历public void traversal(TreeNode cur) {if(cur == null) return;traversal(cur.right);if(pre != null) { //pre不为null才相加cur.val = pre.val + cur.val;}pre = cur;traversal(cur.left);}public TreeNode convertBST(TreeNode root) {traversal(root);return root;}
}
二叉树总结篇
遍历方式
-
深度优先
- [二叉树:前中后序递归法:递归三部曲初次亮相
- [二叉树:前中后序迭代法(一) :通过栈模拟递归
- [二叉树:前中后序迭代法(二)统一风格]
-
广度优先
- [二叉树的层序遍历]:通过队列模拟
求二叉树的属性
- 二叉树:是否对称
- 递归:后序,比较的是根节点的左子树与右子树是不是相互翻转
- 迭代:使用队列/栈将两个节点顺序放入容器中进行比较
- 二叉树:求最大深度
- 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值做计算树的高度
- 迭代:层序遍历
- 二叉树:求最小深度
- 递归:后序,求根节点最小高度就是最小深度,注意最小深度的定义
- 迭代:层序遍历
- 二叉树:求有多少个节点
- 递归:后序,通过递归函数的返回值计算节点数量
- 迭代:层序遍历
- 二叉树:是否平衡
- 递归:后序,注意后序求高度和前序求深度,递归过程判断高度差
- 迭代:效率很低,不推荐
- 二叉树:找所有路径
- 递归:前序,方便让父节点指向子节点,涉及回溯处理根节点到叶子的所有路径
- 迭代:一个栈模拟递归,一个栈来存放对应的遍历路径
- 二叉树:递归中如何隐藏着回溯
- 详解[二叉树:找所有路径 中递归如何隐藏着回溯
- 二叉树:求左叶子之和
- 递归:后序,必须三层约束条件,才能判断是否是左叶子。
- 迭代:直接模拟后序遍历
- 二叉树:求左下角的值
- 递归:顺序无所谓,优先左孩子搜索,同时找深度最大的叶子节点。
- 迭代:层序遍历找最后一行最左边
- 二叉树:求路径总和
- 递归:顺序无所谓,递归函数返回值为bool类型是为了搜索一条边,没有返回值是搜索整棵树。
- 迭代:栈里元素不仅要记录节点指针,还要记录从头结点到该节点的路径数值总和
二叉树的修改与构造
- 翻转二叉树
- 递归:前序,交换左右孩子
- 迭代:直接模拟前序遍历
- 构造二叉树
- 递归:前序,重点在于找分割点,分左右区间构造
- 迭代:比较复杂,意义不大
- 构造最大的二叉树
- 递归:前序,分割点为数组最大值,分左右区间构造
- 迭代:比较复杂,意义不大
- 合并两个二叉树
- 递归:前序,同时操作两个树的节点,注意合并的规则
- 迭代:使用队列,类似层序遍历
求二叉搜索树的属性
- 二叉搜索树中的搜索
- 递归:二叉搜索树的递归是有方向的
- 迭代:因为有方向,所以迭代法很简单
- 是不是二叉搜索树
- 递归:中序,相当于变成了判断一个序列是不是递增的
- 迭代:模拟中序,逻辑相同
- 求二叉搜索树的最小绝对差
- 递归:中序,双指针操作
- 迭代:模拟中序,逻辑相同
- 求二叉搜索树的众数
- 递归:中序,清空结果集的技巧,遍历一遍便可求众数集合
- [二叉搜索树转成累加树]
- 递归:中序,双指针操作累加
- 迭代:模拟中序,逻辑相同
二叉树公共祖先问题
- 二叉树的公共祖先问题
- 递归:后序,回溯,找到左子树出现目标值,右子树节点目标值的节点。
- 迭代:不适合模拟回溯
- 二叉搜索树的公共祖先问题
- 递归:顺序无所谓,如果节点的数值在目标区间就是最近公共祖先
- 迭代:按序遍历
二叉搜索树的修改与构造
- 二叉搜索树中的插入操作
- 递归:顺序无所谓,通过递归函数返回值添加节点
- 迭代:按序遍历,需要记录插入父节点,这样才能做插入操作
- 二叉搜索树中的删除操作
- 递归:前序,想清楚删除非叶子节点的情况
- 迭代:有序遍历,较复杂
- 修剪二叉搜索树
- 递归:前序,通过递归函数返回值删除节点
- 迭代:有序遍历,较复杂
- 构造二叉搜索树
- 递归:前序,数组中间节点分割
- 迭代:较复杂,通过三个队列来模拟
总结
- 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
- 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
- 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了