669. 修剪二叉搜索树
文档讲解:代码随想录 (programmercarl.com)
视频讲解:你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树_哔哩哔哩_bilibili
状态:做不出来,没抓到题目的本质。递归超级不好理解,超级难,很考验对递归本质的理解。掌握递归法即可。
思路
按照题450“删除二叉搜索树中值为key的节点”的思路,写出如下代码,但下面这些代码是错误的,忽略了该题的本质,即本质中加粗的部分。
TreeNode* traversal(TreeNode* root, int low, int high) {
if(root == NULL) return NULL;
// 删除不符合的节点
if(root->val < low || row->val > high) return NULL;
// 指针接住下一层递归的结果
root->left = traversal(root->left, low, high);
root->right = traversal(root->right, low, high);
return root;
}
递归法
!!!!本质:
由于是二叉搜索树,若某个节点的值小于给定区间,那么该结点及其左子树都小于给定区间,都不符合情况,但是其右孩子有可能符合情况(因为右孩子大于该结点的值),所以让其右孩子继承它的位置; 如下图给定区间[4, 8],该结点为3。
若某个节点的值大于给定区间,那么该节点及其右子树都大于给定区间,都不符合情况,但是其左孩子有可能符合情况(因为左孩子小于该节点的值),所以让其左孩子继承它的位置。 如下图给定区间[4, 8],该结点为9。
整体代码
class Solution {
public:
//这些注释才是核心。
//递归的判断对象是当前节点
//看代码按下面标的顺序看
//前两个递归只是中结点的处理方式,也就是起过渡作用,直接过渡到有可能出现符合条件的子树上,与处理中结点的普通代码无异。
//后两个递归是中左右的左右递归,属于是框架中的递归。
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr ) return nullptr;
//!!1、不符合条件,去掉当前节点与左子树(或右子树),也就是返回处理完右子树(或左子树)的递归结果
//去掉当前节点与左子树
if (root->val < low) { //1、若不符合条件,则一直往有可能出现的方向(变大)递归
//将处理完(使各节点值均值[low,high]内)的右子树返回给上一层
//!!!这里用return而不是root->right = ,是因为要删除当前层的节点,将右子树的结果直接返回给上一层
return trimBST(root->right, low, high); //!下层的返回值,会通过这里,直接返回给上层,相当于过渡
// !!为什么不直接return root->right,因为右子树的左子树部分有的节点可能也小于low,也要处理,所以就递归进行修剪。
// !!递归如何修剪:若删除的节点不是叶结点,就返回处理完右(左)子树的结果;若删除节点是叶结点,就根据递归出口“if(root==nullptr) return nullptr;”返回空指针。
}
//去掉当前节点与右子树
if (root->val > high) { //1、若不符合条件,则一直往有可能出现的方向(变小)递归
//将处理完(使各节点值均值[low,high]内)的左子树返回给上一层
//!!!这里用return而不是root->left = ,是因为要删除当前层的节点,将左子树的结果直接返回给上一层
return trimBST(root->left, low, high); //!下层的返回值,会通过这里,直接返回给上层,相当于过渡
}
//2、找到符合条件的节点root,但先不返回。继续往下走,同样方式处理,直到空
// !!!!这里是用指针指向下一层递归的结果
// !!!! 这里用root->left,是因为不删除当前结点,而是将左(右)子树的结果返回给当前节点的左(右)指针
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
//3、返回当前节点给上层符合条件的节点,让其用上两行接住。
return root;
}
};
举例
如下代码相当于把节点0的右孩子(节点2)返回给上一层,
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
return right;
}
然后如下代码相当于用节点3的左孩子 把下一层返回的 节点0的右孩子(节点2) 接住。
root->left = trimBST(root->left, low, high);
此时节点3的左孩子就变成了节点2,将节点0从二叉树中移除了。
迭代法
步骤:
-
将根节点root移动到[low, high]内
若根节点root不在[low, high]内
若root->val < low,说明root及其左子树都小于low,则让右孩子继承位置,即root=root->right;
若root->val > high,说明root及其右孩子有大于high,则让左孩子继承位置,即root=root->left;
不断判断”根节点root是否在[low, high]内“,直到根节点为空或根节点在[low, high]内,故要用while。
由于题目要返回root,所以新建指针cur=root代替root进行遍历。
-
处理左子树
此时,根节点cur已经在[low, high]内,其左子树所有节点均小于cur,故其左子树结点值不可能大于high,只可能小于low(1)。
若根节点cur的左孩子小于low,那么,根节点左孩子的左子树必定都小于low,故让根节点左孩子的右孩子继承位置,即cur->left = cur->left->right。由于根节点左孩子的右孩子也有可能小于low,所以这里要用while,直到找到一个节点符合条件。此时,cur指向的节点符合条件,故cur往左子树遍历,即cur=cur->left。如下图,[low, high]为[3, 8],cur为节点3。
以上操作需要不断循环,直到cur为空为止。
左子树处理完,令cur=root,方便下一步处理右子树。
-
处理右子树
处理右子树的思路和左子树一样。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == nullptr) return nullptr;
//将根节点root移动到[low, high]内
while(root != nullptr && ((root->val < low)||(root->val > high))){
if(root->val < low) root = root->right;
else root = root->left;
}
TreeNode* cur = root;
//处理左子树
while(cur != nullptr){
while(cur->left && cur->left->val < low){
cur->left = cur->left->right;
}
cur = cur->left;
}
cur = root;
//处理右子树
while(cur != nullptr){
while(cur->right && cur->right->val > high){
cur->right = cur->right->left;
}
cur = cur->right;
}uu
return root;
}
};
108.将有序数组转换为二叉搜索树
文档讲解:代码随想录 (programmercarl.com)
视频讲解:构造平衡二叉搜索树!| LeetCode:108.将有序数组转换为二叉搜索树_哔哩哔哩_bilibili
状态:能直接做出来。
思路
数组中间节点作为根节点,然后递归连接左右即可。
代码
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if(nums.size() == 0) return nullptr;
int mid = nums.size() / 2;
TreeNode* root = new TreeNode(nums[mid]);
vector<int> left_nums(nums.begin(), nums.begin() + mid);
vector<int> right_nums(nums.begin() + mid + 1, nums.end());
root->left = sortedArrayToBST(left_nums);
root->right = sortedArrayToBST(right_nums);
return root;
}
};
优化版
class Solution {
public:
TreeNode* preorderTraversal(vector<int>& nums, int left, int right){
if(left > right) return nullptr;
//int mid = (left + right) / 2; //有可能数值越界,例如left和right都是最大int,这么操作就越界了
int mid = left + (right - left) / 2; //用这种写法就不会越界
TreeNode* root = new TreeNode(nums[mid]);
root->left = preorderTraversal(nums, left, mid - 1);
root->right = preorderTraversal(nums, mid + 1, right);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return preorderTraversal(nums, 0, nums.size() - 1);
}
};
538.把二叉搜索树转换为累加树
文档讲解:代码随想录 (programmercarl.com)
视频讲解:普大喜奔!二叉树章节已全部更完啦!| LeetCode:538.把二叉搜索树转换为累加树_哔哩哔哩_bilibili
状态:一开始没想到反中序遍历。自己思路多了一遍求和,用的是中序遍历。故本题不算做出来。
思路
二叉搜索树求累加,要利用其中序遍历时有序,转为数组的思路来做。
中序遍历的方向是数组从前往后,而本题需要从后往前累加,故变成反中序,也就是右中左即可。
要记录前一个节点的值,故要用前驱指针,所以本题也是双指针题目。
代码
class Solution {
//反中序遍历:右中左。用前驱指针
public:
TreeNode* pre = nullptr;
void Traversal(TreeNode* cur){ //反中序遍历:右中左。
if(cur == nullptr) return; //递归出口
Traversal(cur->right); //右
//中
if(pre != nullptr){
cur->val += pre->val;
}
pre = cur;
Traversal(cur->left); //左
}
TreeNode* convertBST(TreeNode* root) {
Traversal(root);
return root;
}
};