Day20 二叉树:669.修剪二叉搜索树 108.将有序数组转换为二叉搜索树 538.把二叉搜索树转换为累加树

73 阅读8分钟

669.修剪二叉搜索树

题目链接:669.修剪二叉搜索树

难度指数:😀😐😕😫

这道题比删除节点还难。

如果不对递归有深刻的理解,本题有点难,单纯移除一个节点那还不够,要修剪!

本题可能删的不止一个节点,同时还要改变树的结构。

建议掌握递归法即可。

区间:[2, 7]

20.01.png

常见误区及代码:

代码思路:

确定递归函数的参数和返回值: 传入的参数是根节点,左边界和右边界;返回值是二叉搜索树按照给定边界修剪之后,新的根节点。

 TreeNode* traversal(root, low, high) {
     if (root == NULL) {   //遍历到NULL
         return NULL;
     }
     if (root->val < low || root->val > high) {  //如果当前遍历的节点小于左边界 或 大于右边界
         return NULL;  //将这个节点删除
     }
     root->left = traversal(root->left, low, high);  //节点7的左孩子接住NULL (那么7的左孩子就变成了NULL)
     root->right = traversal(root->right, low, high);
     
     retur root;
 }

经过一番修理,这棵二叉搜索树就变成了节点7

存在问题:我们要删除节点0,节点0的右孩子并不是我们想删的,但是上面的代码却把它删了。

虽然我们删除节点0,但是还要向右遍历,看看右子树是不是也符合这个区间。

正确思路:

区间:[2, 7]

20.01.png

递归函数的参数和返回值,终止条件不变,还是沿用上面的:

 TreeNode* traversal(root, low, high) {
     if (root == NULL) {
         return NULL;
     }
     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;
 }

把9的左子树作为一个独立的二叉树,传进来,

在这个函数中按照区间的规则进行修剪之后,新的根节点赋给left,然后将新的根节点返回给9

代码量不多,但是很考查对二叉树的理解。

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 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.将有序数组转换为二叉搜索树

题目链接:108.将有序数组转换为二叉搜索树

题目难度:😀😐

题目中为什么要强调“平衡”?

给我们任何一个有序数组,都可以把它构造成链式的二叉树,这棵二叉树符合二叉搜索树的特性:

20.02.png

给我们一个有序数组,要是都想这样构造二叉搜索树的话就没啥意义了,因此要加上平衡二叉搜索树的限制。

如何构造这棵二叉树? (在中后序构造二叉树 和 构造最大二叉树这两个视频有讲)

整体思路就是选取一个中间的节点,将数组分为左区间和右区间,递归遍历左区间,构成左子树;递归遍历右区间,构成右子树。

针对本题,如何构造平衡二叉搜索树?

在数组中选取中间节点一定要选取数组里中间位置的节点,因为只有把中间位置节点作为根节点,左区间节点的数量才能和右区间一样,才能保证二叉树是平衡的。

PS:当数组的长度是偶数的话,取左侧节点或右侧节点都可以,只不过最终二叉树的结构有所不同。

代码思路:

参数:传入的数组是引用;构造左区间、右区间

Q:为什么传入引用?

A:在C++中,如果不使用引用的话,每递归一层就会拷贝一次内存空间,造成程序的性能很差。

使用引用,每次根据数组去构造二叉树,递归遍历的时候,都是在同一个内存地址进行操作。

返回值:在数组中,在左、右区间下所构造的平衡二叉搜索树,它的根节点需要返回。

🦄确定左、右区间的定义:左闭右闭,还是左闭右开? 本题对区间的定义是左闭右闭

思考:if (left > right) 还是 if (left >= right)

答:if (left > right)

left = right,区间内只有一个元素,这是个合法的区间。

这个元素我们还要继续去构造,它可能是叶子节点

20.03.png

root->left = traversal(nums, left, mid - 1);

这个函数的含义是:这个数组中,它的下标 leftmid - 1 这个区间所构造的二叉树的根节点返回,返回给 root 的左子树。

 TreeNode* traversal(vector<int> &nums, left, right) {
     if (left > right) {  //此时是非法区间了
         return NULL;
     }
     //确定单层递归的逻辑
     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;
 }
 ​
 //主函数里面
 root = traversal(nums, 0, nums.size() - 1);

left + right 在有些题目可能会爆int,即相加可能会超过int的范围。

(不过本题无需考虑这个,因为本题的left和right数组里的下标,数组里的下标相加不可能爆int,为什么?)

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 class Solution {
 private:
     TreeNode* traversal(vector<int> &nums, int left, int right) {
         //终止条件
         if (left > right) {  //非法区间
             return NULL;
         }
         //确定单层递归的逻辑
         int mid = left + ((right - left) / 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(vector<int>& nums) {
         TreeNode* root = traversal(nums, 0, nums.size() - 1);
         return root;
     }
 };

538.把二叉搜索树转换为累加树

题目链接:538.把二叉搜索树转换为累加树

难度指数:😀😐😕

看着有点复杂,我们可以换个思路

一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后在遍历其他节点累加?怎么一想这么麻烦呢。

然后再发现这是一棵二叉搜索树,二叉搜索树啊,这是有序的啊。

那么有序的元素如果求累加呢?

其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。

为什么变成数组就是感觉简单了呢?

因为数组大家都知道怎么遍历啊,从后向前,挨个累加就完事了,这换成了二叉搜索树,看起来就别扭了一些。

那么知道如何遍历这个二叉树,也就迎刃而解了,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。


本题依然需要一个 pre 指针记录当前遍历节点 cur 的前一个节点,这样才方便做累加。

在二叉搜索树中遍历这种双指针的操作,(在求二叉搜索树的众数,以及求二叉搜索树中的最小绝对差这两个视频,都讲了这种双指针的操作)

递归解法:

代码思路:

20.04.png

pre 只是记录前一个结点的数值,因此给它定义的类型是int,(而不是TreeNode,不是让它去代表前一个结点)

递归函数的返回值:本题不需要返回值去返回什么,我们就是要遍历整个二叉树,在遍历过程中去更新结点数值,

参数:传入的是二叉搜索树的根结点

 int pre = 0;
 void traversal(TreeNode* cur) {
     if (cur == NULL) {
         return;  //什么也不需要返回
     }
     //右
     traversal(cur->right);
     //中
     cur->val += pre;
     pre = cur->val;  //把cur的数值赋给pre
     //左
     traversal(cur->left);
 }

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 class Solution {
 private:
     int pre = 0;
     void traversal(TreeNode* cur) {
         if (cur == NULL) {
             return;
         }
         //右
         traversal(cur->right);
         //中
         cur->val += pre;
         pre = cur->val;  //把cur的数值赋给pre
         //左
         traversal(cur->left);
     }
 public:
     TreeNode* convertBST(TreeNode* root) {
         pre = 0;
         traversal(root);
         return root;
     }
 };

迭代解法:

总结篇