手把手刷数据结构-3.手把手刷二叉树算法2

归并排序

二叉搜索树(特性篇)

首先,BST 的特性大家应该都很熟悉了:

  1. 对于 BST 的每一个节点 node,左子树节点的值都比 node 的值要小,右子树节点的值都比 node 的值大。

  2. 对于 BST 的每一个节点 node,它的左侧子树和右侧子树都是 BST。

二叉搜索树并不算复杂,但我觉得它可以算是数据结构领域的半壁江山,直接基于 BST 的数据结构有 AVL 树,红黑树等等,拥有了自平衡性质,可以提供 logN 级别的增删查改效率;还有 B+ 树,线段树等结构都是基于 BST 的思想来设计的。

从做算法题的角度来看 BST,除了它的定义,还有一个重要的性质:BST 的中序遍历结果是有序的(升序)。

寻找第 K 小的元素

链接: 寻找第 K 小的元素.

这个 ↓ 解法并不是最高效的解法,而是仅仅适用于这道题。

class Solution {
public:
    int kthSmallest(TreeNode* root, int k) {
        // 利用 BST 的中序遍历特性
        traverse(root, k);
        return res;
    }

    // 记录结果
    int res = 0;
    // 记录当前元素排名
    int rank = 0;

    void traverse(TreeNode* root, int k) {
        if (root == nullptr) {
            return;
        }

        traverse(root->left, k);
        /* 中序遍历代码位置 */
        rank++;
        if (k == rank) {
            res = root->val;
            return;
        }

        traverse(root->right, k);
    }
};

BST 转化累加树

链接: BST 转化累加树.

  • 维护一个外部累加变量 sum,在遍历 BST 的过程中增加 sum,同时把 sum 赋值给 BST 中的每一个节点,就将 BST 转化成累加树了。

  • 但是注意顺序,正常的中序遍历顺序是先左子树后右子树,这里需要反过来,先右子树后左子树。

class Solution {
public:
    TreeNode* convertBST(TreeNode* root) {
        traverse(root);
        return root;
    }
    // 记录累加和
    int sum = 0;
    void traverse(TreeNode* root) {
        if (root == nullptr) {
            return;
        }

        traverse(root->right);

        // 维护累加和
        sum += root->val;
        //将BST转化成累加树
        root->val = sum;
        traverse(root->left);
    }
};

二叉搜索树(基操篇)

在 BST 中搜索元素

链接: 在 BST 中搜索元素.

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if (root == nullptr) {
            return nullptr;
        }

        // 去左子树搜索
        if (root->val > val) {
            return searchBST(root->left, val);
        }

        // 去右子树搜索
        if(root->val < val) {
            return searchBST(root->right, val);
        }
        return root;
    }
};

在 BST 中插入一个数

链接: 在 BST 中插入一个数.

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

        if (root->val > val) {
            root->left = insertIntoBST(root->left, val);
        }
        return root;
    }
};

在 BST 中删除一个数

链接: 在 BST 中删除一个数.

删除比插入和搜索都要复杂一些,分三种情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) {
            return nullptr;
        }

        if (root->val == key) {
            // 这两个 if 把情况 1 和 2 都正确处理了
            if (root->left == nullptr) {
                return root->right;
            }
            if (root->right == nullptr) {
                return root->left;
            }

            // 处理情况 3
            // 获得右子树最小的节点
            TreeNode* minNode = getMin(root->right);
            // 删除右子树最小的节点
            root->right = deleteNode(root->right, minNode->val);
            // 用右子树最小的节点替换 root 节点
            minNode->left = root->left;
            minNode->right = root->right;
            root = minNode;
        } else if (root->val > key) {
            root->left = deleteNode(root->left, key);
        } else if (root->val < key) {
            root->right = deleteNode(root->right, key);
        }
        return root;
    }

    TreeNode* getMin(TreeNode* node) {
        // BST 最左边的就是最小的
        while (node->left != nullptr) {
            node = node->left;
        }
        return node;
    }
};

二叉搜索树(构造篇)

不同的二叉搜索树

链接: 不同的二叉搜索树.

解法:官方解法.

class Solution {
public:
    int numTrees(int n) {
        vector<int> G(n + 1, 0);
        G[0] = 1;
        G[1] = 1;

        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= i; ++j) {
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    }
};

不同的二叉搜索树II

链接: 不同的二叉搜索树II.

想要构造出所有合法 BST,分以下三步:

  1. 穷举 root 节点的所有可能。

  2. 递归构造出左右子树的所有合法 BST。

  3. 给 root 节点穷举所有左右子树的组合。

class Solution {
public:
    /* 主函数 */
    vector<TreeNode*> generateTrees(int n) {
        if (n == 0) {
            return {};
        }
        // 构造闭区间 [1, n] 组成的 BST
        return build(1, n);
    }
    /* 构造闭区间 [lo, hi] 组成的 BST */
    vector<TreeNode*> build(int start, int end) {
        // base case
        if (start > end) {
            return { nullptr };
        }
        vector<TreeNode*> allTrees;

        // 枚举可行根节点
        for (int i = start; i <= end; i++) {
            // 获得所有可行的左子树集合
            vector<TreeNode*> leftTrees = build(start, i - 1);

            // 获得所有可行的右子树集合
            vector<TreeNode*> rightTrees = build(i + 1, end);

            // 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
            for (auto& left : leftTrees) {
                for (auto& right : rightTrees) {
                    TreeNode* currTree = new TreeNode(i);
                    currTree->left = left;
                    currTree->right = right;
                    allTrees.emplace_back(currTree);
                }
            }
        }
        return allTrees;
    }
};

快速排序

美团面试官:你对后序遍历一无所知

二叉搜索子树的最大键值和

链接: 二叉搜索子树的最大键值和.

class Solution {
private:
    // 记录全局的最大键值和
    int maxRes = 0;
    // 返回值
    // 0: 1是搜索树,0不是搜索树
    // 1: 最小值
    // 2: 最大值
    // 3: 总和: 对于非搜索树,直接返回0
    vector<int> dfs(TreeNode* curr)
    {
        if (curr == nullptr)
        {
            return {1, INT_MAX, INT_MIN, 0};
        }
        auto l = dfs(curr->left);
        auto r = dfs(curr->right);

        vector<int> res(4, 0);
        // 只有满足二叉搜索树,才返回结果
        // cout << l[0] << " " << r[0] << " " << l[2] << " " << r[1] << " : " << curr->val << endl;
        if (l[0] == 1 && r[0] == 1 && l[2] < curr->val && r[1] > curr->val)
        {
            res[0] = 1;
            res[1] = min(l[1], curr->val);
            res[2] = max(r[2], curr->val);
            res[3] = l[3] + r[3] + curr->val;
            maxRes = max(maxRes, res[3]);
        }

        return res;
    }
public:
    int maxSumBST(TreeNode* root) {
        dfs(root);

        return maxRes;
    }
};

扁平化嵌套列表迭代器

链接: 扁平化嵌套列表迭代器.

链接: 题解.


Git原理之最近公共祖先

链接: 题解.

二叉树的最近公共祖先

链接: 二叉树的最近公共祖先.
在这里插入图片描述

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // base case
        if (root == nullptr) return nullptr;
        if (root == p || root == q) return root;

        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);

        //情况1
        if (left != nullptr && right != nullptr) return root;

        //情况2
        if (left == nullptr && right == nullptr) return nullptr;

        //情况3
        return left == nullptr ? right : left;
    }
};

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

链接: 二叉搜索树的最近公共祖先.

但对于 BST 来说,根本不需要老老实实去遍历子树,由于 BST 左小右大的性质,将当前节点的值与val1和val2作对比即可判断当前节点是不是LCA:
在这里插入图片描述

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return nullptr;
        if (p->val > q->val) {
            // 保证 p.val <= q.val,便于后续情况讨论
            return lowestCommonAncestor(root, q, p);
        }

        if (root->val >= p->val && root->val <= q->val) {
            // p <= root <= q
            // 即 p 和 q 分别在 root 的左右子树,那么 root 就是 LCA
            return root;
        }

        if (root->val > q->val) {
            return lowestCommonAncestor(root->left, p, q);
        } else {
            // p 和 q 都在 root 的右子树,那么 LCA 在右子树
            return lowestCommonAncestor(root->right, p, q);
        }
    }
};

如何计算完全二叉树的节点数

完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。

完全二叉树的节点个数

链接: 完全二叉树的节点个数.
在这里插入图片描述
关键点在于,这两个递归只有一个会真的递归下去,另一个一定会触发 hl == hr 而立即返回,不会递归下去。

class Solution {
public:
    int countNodes(TreeNode* root) {
        TreeNode* l = root, *r = root;
        int hl = 0, hr = 0;
        while (l != nullptr) {
            l = l->left;
            hl++;
        }
        while (r != nullptr) {
            r = r->right;
            hr++;
        }
        // 如果左右子树的高度相同,则是一棵满二叉树
         if (hl == hr) {
            return (int)pow(2, hl) - 1;
        }
        // 如果左右高度不同,则按照普通二叉树的逻辑计算
        return 1 + countNodes(root->left) + countNodes(root->right);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一记绝尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值