669.修剪二叉搜索树
题目链接:669.修剪二叉搜索树
难度指数:😀😐😕😫
这道题比删除节点还难。
如果不对递归有深刻的理解,本题有点难,单纯移除一个节点那还不够,要修剪!
本题可能删的不止一个节点,同时还要改变树的结构。
建议掌握递归法即可。
区间:[2, 7]
常见误区及代码:
代码思路:
确定递归函数的参数和返回值: 传入的参数是根节点,左边界和右边界;返回值是二叉搜索树按照给定边界修剪之后,新的根节点。
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]
递归函数的参数和返回值,终止条件不变,还是沿用上面的:
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.将有序数组转换为二叉搜索树
题目难度:😀😐
题目中为什么要强调“平衡”?
给我们任何一个有序数组,都可以把它构造成链式的二叉树,这棵二叉树符合二叉搜索树的特性:
给我们一个有序数组,要是都想这样构造二叉搜索树的话就没啥意义了,因此要加上平衡二叉搜索树的限制。
如何构造这棵二叉树? (在中后序构造二叉树 和 构造最大二叉树这两个视频有讲)
整体思路就是选取一个中间的节点,将数组分为左区间和右区间,递归遍历左区间,构成左子树;递归遍历右区间,构成右子树。
针对本题,如何构造平衡二叉搜索树?
在数组中选取中间节点一定要选取数组里中间位置的节点,因为只有把中间位置节点作为根节点,左区间节点的数量才能和右区间一样,才能保证二叉树是平衡的。
PS:当数组的长度是偶数的话,取左侧节点或右侧节点都可以,只不过最终二叉树的结构有所不同。
代码思路:
参数:传入的数组是引用;构造左区间、右区间
Q:为什么传入引用?
A:在C++中,如果不使用引用的话,每递归一层就会拷贝一次内存空间,造成程序的性能很差。
使用引用,每次根据数组去构造二叉树,递归遍历的时候,都是在同一个内存地址进行操作。
返回值:在数组中,在左、右区间下所构造的平衡二叉搜索树,它的根节点需要返回。
🦄确定左、右区间的定义:左闭右闭,还是左闭右开? 本题对区间的定义是左闭右闭
思考:
if (left > right)还是if (left >= right)?答:
if (left > right)
当 left = right,区间内只有一个元素,这是个合法的区间。
这个元素我们还要继续去构造,它可能是叶子节点。
root->left = traversal(nums, left, mid - 1);
这个函数的含义是:这个数组中,它的下标 left 和 mid - 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 的前一个节点,这样才方便做累加。
在二叉搜索树中遍历这种双指针的操作,(在求二叉搜索树的众数,以及求二叉搜索树中的最小绝对差这两个视频,都讲了这种双指针的操作)
递归解法:
代码思路:
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;
}
};
迭代解法: