代码随想录Day22-二叉树:力扣第701m、450、669m、108e、538m题

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

题目链接
代码随想录文章讲解链接

方法一:迭代法

用时:11m38s

思路

从根节点开始迭代,若要插入的元素大于(小于)当前节点,则要插入的位置在当前节点右边(左边),如果当前节点的右节点(左节点)不存在,则直接插入,如果存在,则当前节点迭代为右节点(左节点)。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        TreeNode* newNode = new TreeNode(val);
        if (root == nullptr) return newNode;
        TreeNode* cur = root;
        while (true) {
            if (cur->val < val) {
                if (cur->right) cur = cur->right;
                else {
                    cur->right = newNode;
                    break;
                }
            } else {
                if (cur->left) cur = cur->left;
                else {
                    cur->left = newNode;
                    break;
                }
            }
        }
        return root;
    }
};

方法二:递归法

用时:7m17s

思路

本质上与方法一一致,使用递归的方法实现。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == nullptr) return new TreeNode(val);
        if (root->val < val) root->right = insertIntoBST(root->right, val);
        else root->left = insertIntoBST(root->left, val);
        return root;
    }
};

看完讲解的思考

代码实现遇到的问题


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

题目链接
代码随想录文章讲解链接

方法一:递归法

用时:1h10m58s

思路

首先分析清楚我们要如何删除一个节点,假设当前节点是cur,当前节点的父节点是pre,分情况讨论:

  1. 如果cur是叶子节点,则让pre指向空节点,然后删除cur
  2. 如果cur只有一个子节点child,则让pre指向child,然后删除cur
  3. 如果cur有两个子节点leftright,假设右子树最小的节点为rightMin,让rightMin的左指针指向left,然后让pre指向right,最后删除cur

删除二叉树节点跟删除链表的节点一样,一定要先处理删除节点的前一个节点,然后再删除掉要删除的节点,如果直接删除节点而不处理pre,则pre的指针就会指向已经被释放的空间导致bug。

搞清楚删除的逻辑后再来处理代码实现的逻辑。第一种方法我们可以使用递归来实现,递归三部曲:

  • 确定递归函数参数以及返回值。传入根节点和要删除的元素,返回根节点。

  • 确定终止条件。遇到空返回,其实这也说明没找到删除的节点,递归到空节点直接返回了。

  • 确定单层递归的逻辑,每次递归只判断当前节点:

    • 如果当前节点的值不等于要删除的元素,则继续递归。
    • 如果当前节点的值等于要删除的元素,则根据上述1、2、3点的删除逻辑删除当前节点。在递归中,返回的节点赋值给上一层递归的节点的子节点,其实就隐式的实现了处理pre的逻辑。
  • 时间复杂度: O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n)。时间复杂度和空间复杂度都为 O ( d ) O(d) O(d),其中 d d d为递归的深度,即树的高度,最坏情况下树为链状,则复杂度为 O ( n ) O(n) O(n)

C++代码
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return nullptr;  // 情况1:当前节点为空
        if (root->val == key) {
            TreeNode* node = nullptr;  // 情况2:当前节点是叶子节点
            if (root->left && root->right) {  // 情况3:当前节点的左右节点都存在
                TreeNode* rightMinNode = root->right;
                while (rightMinNode->left) rightMinNode = rightMinNode->left;
                rightMinNode->left = root->left;
                node = root->right;
            } else if (!(root->left == nullptr && root->right == nullptr)) node = root->left ? root->left : root->right;  // 情况4:当前节点只存在一个子节点
            delete root;
            return node;
        } else if (root->val > key) root->left = deleteNode(root->left, key);
        else root->right = deleteNode(root->right, key);
        return root;
    }
};

方法二:迭代法

思路

迭代法实现的本质与递归法一致,主要是要理清楚删除节点的逻辑。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        TreeNode* cur = root;
        TreeNode* pre = nullptr;
        while (cur) {
            if (cur->val == key) {  // 如果当前节点的值等于要删除的值
                TreeNode* node = nullptr;  // 如果是叶子节点
                if (cur->left && cur->right) {  // 如果左右节点都存在
                    TreeNode* rightMin = cur->right;
                    while (rightMin->left) rightMin = rightMin->left;
                    rightMin->left = cur->left;
                    node = cur->right;
                } else if (!(cur->left == nullptr && cur->right == nullptr)) node = cur->left ? cur->left : cur->right;  // 如果只存在一个子节点

                // 更新父节点的指向;如果要删除的节点是根节点,则修改根节点
                if (pre) {
                    if (pre->left && pre->left->val == cur->val) pre->left = node;
                    else pre->right = node;
                } else root = node;

                // 删除当前节点并停止迭代
                delete cur;
                break;
            } else {  // 如果当前节点的值不等于要删除的值,则继续迭代
                pre = cur;
                cur = cur->val < key ? cur->right : cur->left;
            }
        }
        return root;
    }
};

看完讲解的思考

删除二叉树节点与删除链表节点的操作很类似,都是要先处理前一个指针的指向,再来删除要删除的节点。

代码实现遇到的问题

没有先处理父节点指针的指向,导致指针指向了被释放的空间。


669m. 修剪二叉搜索树

题目链接
代码随想录文章讲解链接

方法一:递归法

用时:42m38s

思路

如果当前节点的值小于下界,那么左子树肯定都小于下界,只用判断右子树;如果当前节点的值大于上界,那么右子树肯定都大于下界,只用判断左子树;如果当前节点的值位于范围内,则分别递归判断左右子树

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (root == nullptr) return nullptr;
        if (root->val < low) return trimBST(root->right, low, high);  // 如果当前节点的值小于下界,那么左子树肯定都小于下界,只用判断右子树
        else 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;
        }
    }
};

方法二:迭代法

用时:18m4s

思路

首先找到第一个满足条件的节点作为根节点,然后分别修剪左子树和右子树。
修剪左子树的逻辑:如果当前节点cur的左节点cur->left存在,并且左节点的值小于下界,那么让cur->left = cur->left->right,这样就相当于修剪掉了左节点以及左节点的左子树(左节点小于下界,那么左节点的左子树肯定也小于下界)。如果当前节点的左节点满足要求,则保留左节点,迭代当前节点,继续判断左节点。
修建右子树的逻辑与修建左子树的类似。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        // 找到满足条件的节点作为根节点
        while (root && (root->val < low || root->val > high)) {
            if (root->val < low) root = root->right;
            else root = root->left;
        }
        // 修剪左子树
        TreeNode* cur = root;
        while (cur) {
            if (cur->left && cur->left->val < low) cur->left = cur->left->right;
            else cur = cur->left;
        }
        // 修剪右子树
        cur = root;
        while (cur) {
            if (cur->right && cur->right->val > high) cur->right = cur->right->left;
            else cur = cur->right;
        }
        return root;
    }
};

看完讲解的思考

此题的修剪与上一题450m的删除不同,本题并没有真正的释放节点占用的内存空间,是不是内存泄漏了?

代码实现遇到的问题

无。


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

题目链接
代码随想录文章讲解链接

方法一:递归法

用时:5m20s

思路

每次取数组中间位置的元素作为根节点的值。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
private:
    TreeNode* bulidBST(vector<int>& nums, int begin, int end) {
        if (begin > end) return nullptr;
        int mid = begin + (end - begin) / 2;
        TreeNode* root = new TreeNode(nums[mid]);
        root->left = bulidBST(nums, begin, mid - 1);
        root->right = bulidBST(nums, mid + 1, end);
        return root;
    }
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return bulidBST(nums, 0, nums.size() - 1);
    }
};

方法二:迭代法

用时:

思路
  • 时间复杂度: O ( ) O() O()
  • 空间复杂度: O ( ) O() O()
C++代码

看完讲解的思考

代码实现遇到的问题


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

题目链接
代码随想录文章讲解链接

方法一:反向中序遍历递归法

用时:3m41s

思路

BST中序遍历数组是递增数组,中序遍历顺序是“左中右”,那么反向中序遍历数组“右中左”就是递减数组,利用这一点我们就能由大到小遍历每一个元素,并且在遍历的过程中,用一个变量sum记录遍历过的元素之和,当遍历到某一个节点cur时,sum记录的就是比它大的元素之和,此时cur的值更新为cur->val+sum,即原树中大于等于cur->val的元素之和,然后更新sum。反向中序遍历的实现与正常的中序遍历一样,就是处理左右节点的顺序反过来。可以用递归或者迭代实现。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
private:
    int sum;
public:
    TreeNode* convertBST(TreeNode* root) {
        if (root == nullptr) return nullptr;
        root->right = convertBST(root->right);  // 右
        root->val += sum;  // 中
        sum = root->val;
        root->left = convertBST(root->left);  // 左
        return root;
    }
};

方法二:迭代法

思路
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    TreeNode* convertBST(TreeNode* root) {
        int sum = 0;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur || !st.empty()) {
            if (cur) {
                st.push(cur);
                cur = cur->right;
            } else {
                cur = st.top();
                st.pop();
                cur->val += sum;
                sum = cur->val;
                cur = cur->left;
            }
        }
        return root;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

中序遍历的迭代法还是不够熟练啊,居然还写不出来,得多练练。


最后的碎碎念

终于结束了二叉树专题了!!!这一章的内容是真多啊,最主要还是递归和迭代法,基本二叉树题目都是这样。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值