代码随想录算法训练营第二十三天 | 修剪二叉搜索树很难,考验递归本质

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从二叉树中移除了。

迭代法

步骤:

  1. 将根节点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进行遍历。

  2. 处理左子树

    此时,根节点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,方便下一步处理右子树。

  3. 处理右子树

    处理右子树的思路和左子树一样。

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;
    }
};

总结

代码随想录 (programmercarl.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值