Day24|二叉树 PART08

235. 二叉搜索树的最近公共祖先

与236类似但可以利用二叉搜索树的性质来做
二叉搜索树:左子 小于头 小于右子
(怪怪的 感觉是不是先记住比较好)(而且也没太理解二叉搜索树的规律)
大体思路:从上到下遍历(从头节点开始遍历,那应该是前序遍历)
公共祖先的节点的值应该在p和q的闭区间内,最近公共祖先是从上到下遍历中遇到的第一个符合条件的节点(至于为什么是第一个我也没捋明白)

写了一下没写出来发现我没搞清楚“沿一条边递归”和“沿整棵树递归”的区别:

在二叉树:公共祖先问题 (opens new window)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。

在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。

很明显这里的情况是“返回值不为空的时候立即返回”,所以是搜索一条边。

701.二叉搜索树中的插入操作

遍历二叉树,其实可以不考虑题目中提示所说的改变树的结构的插入方式,遇到空节点就插入就行。
递归插入,单层递归逻辑:
入参:
root,val
返回值可以为空(?)其实在这里感觉有没有返回值都行,但是没有的话可能无法知道节点到底有没有插入从而终止递归,有返回值的话返回的节点赋值给?

可以有,也可以没有,但递归函数如果没有返回值的话,实现是比较麻烦的,下面也会给出其具体实现代码。
有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作。

与当前节点比较,大于就往节点右侧插,小于就往节点左侧插

确定终止条件:
当前节点为null说明可以插入

因此可以根据终止条件得知,返回的当前节点由上一次接住:
用java写了一版:

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        if (root.val > val) {
            root.left = insertIntoBST(root.left, val);

        } else {
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }
}

450.删除二叉搜索树中的节点

也可以沿着边搜索,遇到删除的节点分三种情况判断:

1.叶子节点,直接返回null
2.左子右子只有一个存在:返回存在的子节点
3.左右子都存在:右子接到该节点的位置,左子接到右子树上最左的叶子节点下,返回右节点

既然有返回值,那么递归的过程中就需要接住这个返回值。

接住的逻辑:

这里相当于把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住,代码如下:

if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;

开写:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr)
            return root;
        if (root->val == key) {
            if (root->left == nullptr && root->right == nullptr) {
                return nullptr;
            } else if (root->left == nullptr || root->right == nullptr) {
                return root->left == nullptr ? root->right : root->left;
            } else {
                findLeaf(root->right)->left = root->left;
                return root->right;
            }
        }
        if (root->left)
            root->left = deleteNode(root->left, key);
        if (root->right)
            root->right = deleteNode(root->right, key);

        return root;
    }

    TreeNode* findLeaf(TreeNode* root) {
        while (root->left != nullptr) {
            root = root->left;
        }
        return root;
    }
};

AC了 上面黑体的部分算是解本题的关键点,可以记住。

669. 修剪二叉搜索树

如果找到不在范围内的节点,
小于最小值,说明该节点及其左子树都不要了
大于最大值,说明该节点及其右子树都不要了

递归逻辑:
记住递归是需要找到本层逻辑,不需要考虑整个递归的套路

本层逻辑:

先判断当前节点,
小于范围:对该节点进行一次删除操作,把节点当作没有左子树的情况返回右节点
大于范围:当作没有右子树的情况返回左节点
再执行删除节点的接住逻辑

开写的时候发现了一个忽略掉的逻辑:
就是把左或右子树接到当前节点上的时候,会忽略对新接入节点的判断:

比如
比如这种情况

比如这种情况,如果low=3, 删除了0后直接接上的2其实也不在范围内

又绕进去了。。用这个逻辑好像不太对

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

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

写了一下果然不对,卡在这个用例了:
跟预想的错误一样

看了一下题解,其实单层递归的逻辑跟这个很类似:

确定单层递归的逻辑
如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。

也就是说其实不能直接返回整个右子树,需要返回右子树中符合条件的头节点

那么如何找到右子树中符合条件的头节点?使用本递归函数进行修剪!
在单层递归中调用递归函数的时候,其实不用考虑递归的整个过程,直接把这个递归函数当作一个实现功能的工具即可!

就是说:

if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
return right;
}

开写!

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

其实只需要在第一版代码的基础上稍微修改“返回左右子树中符合条件的 节点” 即可。

108.将有序数组转换为二叉搜索树

做这道题目之前大家可以了解一下这几道:

106.从中序与后序遍历序列构造二叉树 (opens new window)
654.最大二叉树 (opens new window)中其实已经讲过了,如果根据数组构造一棵二叉树。
701.二叉搜索树中的插入操作 (opens new window)
450.删除二叉搜索树中的节点

一开始被卡住了,卡住的点在于看到题干给出的两个例子:
题干给出的例子

一直在想为什么不能说是0左右直接长出长长的两撇(这不算平衡二叉树吗,为什么非要调换-10和-3的位置把-3放在-10的右子)

(查了一下好像本来就可以产生很多答案,姑且把我的构建结果当作正确的一种)

单层递归思路:

入参感觉可以有数组,当前头节点,int left, int right

这里注意:

再来看参数,首先是传入数组,然后就是左下标left和右下标right,我们在二叉树:构造二叉树登场! (opens new window)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。

首先找到本层传入数组的中点
赋给一个构造出的节点,
该节点的左右节点,根据traversal函数获得

返回该节点

递归终止条件

这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。

开写

class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return traversal(nums, 0, nums.size()-1);
    }
    TreeNode* traversal(vector<int>& nums, int left, int right){
        if(left > right) return nullptr;
        int mid = left + ((right - left)>>1);
        TreeNode* root = new TreeNode(nums[mid]);
        root->left = traversal(nums, left,mid-1);
        root->right = traversal(nums, mid+1, right);
        return root;
    }
};

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

本题与1038题相同

需要写出一个求和的封装,然后遍历该二叉树根据求和替换每个节点的值。
求和就是求出右子树的和 再加 当前节点值

求和过程的递归就是正常遍历就行,给出当前节点的右子树的头节点,遍历一圈获取和(理论上是怎么遍历都可以)

写了一下发现不太对。。没法对应上这个图
不对

圈起来的部分全没法使用上述方法替换val(节点5甚至没有右子树)

感觉也可以将二叉树转换成数组,不过这样可以获取但替换不太方便

还是考虑在遍历过程中按顺序求值并替换,可以按右-〉头-〉左的顺序遍历并替换
每遍历一个节点就把当前值+=sum

class Solution {
public:
    int sum = 0;
    TreeNode* convertBST(TreeNode* root) {
        sum=0;
        traversal(root);
        return root;
    }
    void traversal(TreeNode* root) {
        if (root == nullptr)
            return;
        if (root->right != nullptr){
            traversal(root->right);
        }
        root->val += sum;
        sum = root->val;
        if (root->left != nullptr)
            traversal(root->left);
        return;
    }

};

改用java写一写。。:

class Solution {
    private int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        getSum(root);
        return root;
    }
    public void getSum(TreeNode root){
        if(root==null) return;
        getSum(root.right);
        root.val += sum;
        sum=root.val;
        getSum(root.left);


    }
}

最后部分是对二叉树模块总结:

二叉树模块挺全面的,卡哥每题都给出了递归和迭代的解法,但我这次一刷基本上只选择了递归的方法(除了有些层序遍历会用迭代)
而且二叉树这个模块我中间间隔了很久才重新开始刷。。中间经历了找实习+写毕设+回国实习。。实习从C++转到Java。。真的隔了很久。。前面一些二叉树的题(二叉树属性,修改与改造部分)急需二刷

就先这样,一刷二叉树over!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值