Day 18 二叉树
修剪二叉搜索树
题目链接:669. 修剪二叉搜索树 - 力扣(LeetCode)
文章讲解: 代码随想录
视频讲解: 你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树
题目建议:这道题目比较难,比 添加增加和删除节点
难的多,建议先看视频理解。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
}
}
题目解析
关键思路
- 利用
BST
的有序特性
进行修剪 - 递归处理时:
当前节点值小于low时,保留右子树
当前节点值大于high时,保留左子树
当前节点在范围内时,递归修剪左右子树
递归解法
class Solution {
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;
}
}
迭代解法
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) return null;
// 1. 先处理根节点,使其落在[low, high]范围内
// 处理逻辑, 如果 root.val 不在该范围内, 并且 root != null, 就继续调整
// 如果 root 已经在范围内, 或者 root 为空, 则结束调整
// err: root != null 要放在前面
while (root != null && (root.val < low || root.val > high)) {
root = root.val < low ? root.right : root.left;
}
TreeNode curr = root;
// 2. 修剪左子树(移除小于low的节点)
while (curr != null) {
// err: 要使用循环处理左子树, 根节点符合题意, 左节点一定小于 high, 只需考虑左节点是否小于 low
while (curr.left != null && curr.left.val < low) {
curr.left = curr.left.right;
}
curr = curr.left;
}
curr = root;
// 3. 修剪右子树(移除大于high的节点)
while (curr != null) {
// err: 要使用循环处理右子树, 根节点符合题意, 右节点一定大于 low, 只需考虑右节点是否小于 high
while (curr.right != null && curr.right.val > high) {
curr.right = curr.right.left;
}
curr = curr.right;
}
return root;
}
}
关键点说明
-
递归解法特点:
- 代码简洁直观
通过返回值自动完成子树连接
- 时间复杂度
O(n)
-
迭代解法特点:
- 分三步处理:
根节点定位、左子树修剪、右子树修剪
- 不需要使用栈,
直接修改指针
- 时间复杂度
O(n)
- 分三步处理:
-
共同注意事项:
- 保持 BST 的
有序性
不需要实际删除节点,只需修改指针
- 处理边界条件(
root == null
)
- 保持 BST 的
将有序数组转换为二叉搜索树
题目链接:108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解:构造平衡二叉搜索树!| LeetCode:108.将有序数组转换为二叉搜索树
题目建议:本题就简单一些,可以尝试先自己做做。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
}
}
题目解析
做这道题目之前大家可以了解一下这几道:
关键思路
每次取数组中间元素作为根节点
那么为问题来了,
如果数组长度为偶数,中间节点有两个,取哪一个?
取哪一个都可以,只不过构成了不同的平衡二叉搜索树
。例如:输入:[-10,-3,0,5,9]
如下两棵树,都是这个数组的平衡二叉搜索树:
如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2。
这也是题目中强调答案不是唯一的原因。 理解这一点,这道题目算是理解到位了
。
-
递归构建
左子树和右子树 -
自然形成高度平衡的BST(左右子树高度差不超过1)
递归解法
class Solution {
int[] nums;
public TreeNode sortedArrayToBST(int[] nums1) {
nums = nums1;
return dfs(0, nums.length-1);
}
private TreeNode dfs(int start, int end){
if(start > end){
return null;
}
// 取数组中间元素作为根节点, 并防止整数溢出
int midIndex = start + (end - start)/2;
TreeNode root = new TreeNode(nums[midIndex]);
// 递归构建该节点的左右子树
root.left = dfs(start, midIndex-1);
root.right = dfs(midIndex+1, end);
// 把处理好的根节点向上返回
return root;
}
}
迭代解法
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if (nums.length == 0) return null;
TreeNode root = new TreeNode(0);
Queue<TreeNode> nodeQueue = new LinkedList<>();
Queue<Integer> leftQueue = new LinkedList<>();
Queue<Integer> rightQueue = new LinkedList<>();
nodeQueue.offer(root);
leftQueue.offer(0);
rightQueue.offer(nums.length - 1);
while (!nodeQueue.isEmpty()) {
TreeNode curr = nodeQueue.poll();
int left = leftQueue.poll();
int right = rightQueue.poll();
int mid = left + (right - left) / 2;
curr.val = nums[mid];
if (left <= mid - 1) {
curr.left = new TreeNode(0);
nodeQueue.offer(curr.left);
leftQueue.offer(left);
rightQueue.offer(mid - 1);
}
if (right >= mid + 1) {
curr.right = new TreeNode(0);
nodeQueue.offer(curr.right);
leftQueue.offer(mid + 1);
rightQueue.offer(right);
}
}
return root;
}
}
关键点说明
-
递归解法特点:
- 代码简洁直观
- 时间复杂度
O(n)
- 空间复杂度
O(logn)
(递归栈)
-
迭代解法特点:
- 使用
队列
模拟递归过程 - 避免递归栈溢出风险
同样保证平衡性
- 使用
-
共同注意事项:
中间位置计算防止整数溢出
- 处理空数组情况
保持 BST 的有序性和平衡性
把二叉搜索树转换为累加树
题目链接:538. 把二叉搜索树转换为累加树 - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解:普大喜奔!二叉树章节已全部更完啦!| LeetCode:538.把二叉搜索树转换为累加树
题目建议:本题也不难,在 求二叉搜索树的最小绝对差
和 众数
那两道题目 都讲过了 双指针法
,思路是一样的。
1038. 从二叉搜索树到更大和树 - 力扣(LeetCode)
题目解析
关键思路
关键点:将二叉搜索树转换为累加树,
可以类比为对有序数组从后向前累加
。因为二叉搜索树的中序遍历是有序的,所以需要进行反中序遍历(右-中-左)
,并在遍历过程中累加节点值
。
本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加。
利用 BST 的中序遍历特性(有序性)
反向中序遍历(右-中-左)
实现从大到小访问节点
累加前面所有节点的值
到当前节点
递归解法
class Solution {
int pre = 0;
public TreeNode convertBST(TreeNode root) {
return dfs(root);
}
private TreeNode dfs(TreeNode root){
if(root == null){
return null;
}
dfs(root.right);
root.val += pre;
pre = root.val;
dfs(root.left);
return root;
}
}
迭代解法
class Solution {
public TreeNode convertBST(TreeNode root) {
if(root == null){
return null;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
int pre = 0;
// stack.push(cur);
// err: 循环开始前将cur压入栈中,这会导致栈中多出一个不必要的节点。
while(cur != null || !stack.isEmpty()){
if(cur != null){
// 先遍历右子树
stack.push(cur);
cur = cur.right;
}else{
// 处理当前节点
cur = stack.pop();
cur.val += pre;
pre = cur.val;
// 遍历左子树
cur = cur.left;
}
}
return root;
}
}
关键点说明
反向中序遍历
:从最大的节点开始处理,依次累加
- 累加逻辑:
维护一个全局累加变量 pre
pre 和 root.val 类似于固定窗口, 处理方向中序遍历的相邻两个节点
每个节点的值更新为 pre += root.val
- 时间复杂度:
O(n)
,每个节点访问一次 - 空间复杂度:
- 递归:
O(h)
,h为树高 - 迭代:
O(n),最坏情况下栈空间
- 递归:
二叉树总结篇
好了,二叉树大家就这样刷完了,做一个总结吧:代码随想录