力扣669:修剪二叉搜索树,如何正确的使用递归?

递归的基本流程

《代码随想录》里,我们了解了递归的三要素:

  1. 确定参数和返回值。
  2. 确定终止条件。
  3. 确定单层递归逻辑。

关于二叉树这一分类,解题思路基本都在围绕着二叉树的深度遍历和广度遍历,而二叉搜索树则几乎全部都是二叉树的深度遍历,只是将对结点的访问换成了其它的操作。
以力扣669为例,分析二叉搜索树如何使用递归。

669:修剪二叉搜索树

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。

所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

初次尝试

沿着三要素的方向出发:

  1. 参数显然不需要改变,对于每一个节点,我们都需要判断这个节点的值是否在区间内。而对于返回值,在对每一个节点进行操作使其满足区间范围之后,我们将返回上一层的函数调用,返回值要作为上一层的参数,直到最顶层。到这里,我们已经很清晰了,因为在最顶层我们需要将root返回,因此返回值是TreeNode*
  2. 终止条件,二叉搜索树的本质还是对树进行dfs,因此终止条件和dfs的一致,遇到叶子节点停止递归,按照中序遍历或者前后序遍历方式访问父节点或父节点的右子树。
  3. 单层递归逻辑是核心,如果结果不正确,卡在某一个例子,那么先检查自己的代码逻辑,返回值处理的是否正确?进入下一层递归的条件是否正确?需要注意的是,先处理不满足条件的情况,再处理满足条件的情况

而在这个题中,我们要做的就是判断当前节点的值是否在区间内,不在区间内怎么处理?(以下的分析有误区,但是是写这个题的第一反应,后面会纠正)
root->val只有三种情况,第一中小于low,第二种大于high,第三种在区间内。

  1. root->val < low时,它的左子树上所有的值都一定小于low,因此应该将当前的root更新为root->right,但是root->right也可能小于low,所以应该不断地寻找,直到右子树的值在区间内,代码如下:
        if(root->val < low) {
            TreeNode* cur = root;
            while(cur->right && cur->right->val < low) cur = cur->right;
            return cur->val < low ? cur->right : cur;
        }
  1. root->val >high时,同样的有它的右子树全部大于区间上限,因此更新当前的root,代码如下:
        if(root->val > high) {
            TreeNode* cur = root;
            while(cur->left && cur->left->val > high) cur = cur->left;
            return cur->val > low ? cur->left : cur;
        }
  1. 而在区间范围内的节点,我们就对左子树和右子树进行剪枝,代码如下:
        root->left = trimBST(root->left, low, high);
        root->right = trimBST(root->right, low, high);

最终完整代码:

  class Solution {
  public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root) return nullptr;
        if(root->val < low) {
            TreeNode* cur = root;
            while(cur->right && cur->right->val < low) cur = cur->right;
            return cur->val < low ? cur->right : cur;
        }
        if(root->val > high) {
            TreeNode* cur = root;
            while(cur->left && cur->left->val > high) cur = cur->left;
            return cur->val > low ? cur->left : cur;
        }

        root->left = trimBST(root->left, low, high);
        root->right = trimBST(root->right, low, high);

        return root;
    }
};

修改三层递归逻辑

上面的代码在测试用例:

报错,其实再回看代码,有一个很显然的错误就是,如果根节点不在区间内,那么就直接返回其左右子树,而不会对左右子树进行剪枝,因此对于root->val < lowroot->val > high的处理,不应该是直接返回在区间内的左子树节点或者右子树节点,应该修改为,继续进入左子树或者右子树进行剪枝
1.和2.的代码逻辑更改为:

        if(root->val < low) {
            return trimBST(root->right, low, high);
        }
        if(root->val > high) {
            return trimBST(root->left, low, high);
        }

更改后的全部代码是:

  class Solution {
  public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root) return nullptr;

        if(root->val < low) {
            return trimBST(root->right, low, high);
        }
        if(root->val > high) {
            return trimBST(root->left, low, high);
        }else{
            root->left = trimBST(root->left, low, high);
            root->right = trimBST(root->right, low, high);
            return root;
        }
    }
};

二叉搜索树几乎都可以用递归解决,想要练习递归建议把这部分的题归类总结一下。一个个人的疑问就是,递归需要频繁的调用函数,在用栈可以解决的情况下,是否优先用栈,而不是递归。

posted @ 2024-12-16 14:24  ZCry  阅读(90)  评论(0)    收藏  举报