【C++】二叉树进阶OJ题(下)

简介: 【C++】二叉树进阶OJ题(下)

👉二叉树的最近公共祖先👈


给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。


百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”


b3ea81bfef4948ee8d91e0f9b71877b0.png


dc70e54997154f479cfa1f636a85abfd.png

思路一:

da45ecff0a064a8b8211d2c14802e0f9.png

class Solution 
{
private:
    // 查找节点x是否在以sub为根节点的树中
    bool Find(TreeNode* sub, TreeNode* x)
    {
        if(sub == nullptr)
            return false;
        return sub == x
            || Find(sub->left, x)
            || Find(sub->right, x);
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        if(root == nullptr)
            return nullptr;
        // 其中一个节点为根节点,则最近公共祖先为根节点
        if(root == p || root == q)
            return root;
        // p、q在根节点的左右子树中
        bool pInLeft, pInRight, qInLeft, qInRight;
        pInLeft = Find(root->left, p);
        pInRight = !pInLeft;
        qInLeft = Find(root->left, q);
        qInRight = !qInLeft;
        if(pInLeft && qInLeft)  // p、q都在根节点的左子树中,转化成子问题
            return lowestCommonAncestor(root->left, p, q);
        else if(pInRight && qInRight)   // p、q都在根节点的右子树中,转化成子问题
            return lowestCommonAncestor(root->right, p, q);
        else    // p、q一个在左,另一个在右,最近公共祖先为根节点
            return root;
    }
};

6535e177dd594dffaa208efcd5ee2e3b.png


注:因为 p 和 q 一定在树中,所以 p 在左子树,那么它一定不会在右子树中,q 同理。这种解法的时间复杂度为O(h*N),h为树的高度,N为节点的个数。


思路二:

这种思路是求出从根节点root到p和q的路径,路径用栈来存储。因为路径长短不一样,所以先让长的弹出长度差个节点,然后两个栈一起弹出节点直至栈顶节点相同,那么栈顶节点就是p和q的最近公共祖先。这种解法的思路类似于链表相交的思路,时间复杂度为O(N)。



35f05143faa840c0a43c158196a41d11.png

class Solution 
{
private:
    bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
    {
        if(root == nullptr)
            return false;
        path.push(root);
        if(root == x)   // 根节点就是p、q
            return true;
        if(FindPath(root->left, x, path))   // p、q在root的左子树中
            return true;
        if(FindPath(root->right, x, path))  // p、q在root的右子树中
            return true;
        path.pop();     // 经过root无法到达p、q,所以要将root弹出
        return false;
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        stack<TreeNode*> pPath, qPath;
        FindPath(root, p, pPath);   // pPath中储存的是root到p的路径
        FindPath(root, q, qPath);   // qPath中储存的是root到q的路径
        // 路径长的弹出长度差个节点
        while(pPath.size() != qPath.size())
        {
            if(pPath.size() > qPath.size())
                pPath.pop();
            else
                qPath.pop();
        }
        // 栈顶节点相等时,栈顶节点就是最近公共祖先
        while(pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }
        return pPath.top();
    }
};

c2d1d472d7b644f984805d35b473589a.png


思路三:

思路三是思路一的精简版本,时间复杂度为O(N)


4a1ecb7439894f0a8801f99e5fc47bde.png

class Solution 
{
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        // root为空或者是p、q中的一样都要返回自己
        if(root == nullptr || root == p || root == q)
            return root;
        TreeNode* InLeft = lowestCommonAncestor(root->left, p, q);
        TreeNode* InRight = lowestCommonAncestor(root->right, p, q);
        // InLeft和InRight都不为空,则根节点为最近公共祖先
        if(InLeft && InRight)
            return root;
        // InLeft和InRight谁不为空,谁就是最近公共祖先
        return InLeft ? InLeft : InRight;
    }
};

9a681293a08c4e58b43f6a21f72b2059.png


👉二叉搜索树与双向链表👈


输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。


链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。


当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

9a09a093130445d881cba64bdfd73c00.png


1de9b5f94b684c1d943ef65038d68197.png

思路:本道题要求左指针 left 指向中序的前一个节点,右指针 right 指向中序的后一个节点。我们可以采用prev来记录当前节点cur的前一个节点,那么prev->right = cur, cur->left = prev,这样就可以将二叉搜索树转化成双向链表了。注意:prev是Node*的引用,这样才能链接起来。


class Solution 
{
private:
    void InordertreeToDoublyList(Node* cur, Node*& prev)
    {
        if(cur == nullptr)
            return;
        InordertreeToDoublyList(cur->left, prev);
        cur->left = prev;
        if(prev != nullptr)
            prev->right = cur;
        prev = cur;
        InordertreeToDoublyList(cur->right, prev);
    }
public:
    Node* treeToDoublyList(Node* root) 
    {
      // 根节点为空,直接返回nullptr即可
        if(root == nullptr)
            return nullptr;
        Node* prev = nullptr;
        InordertreeToDoublyList(root, prev);
        // 找到中序第一个节点
        Node* first = root;
        while(first->left)
        {
            first = first->left;
        }
        // 找到中序最后一个节点
        Node* last = root;
        while(last->right)
        {
            last = last->right;
        }
        // 第一个节点与最后一个节点链接起来形成双向循环链表
        first->left = last;
        last->right = first;
        return first;
    }
};


1e4fcee7689446768c5932adb734e97a.png


👉从前序与中序遍历序列构造二叉树👈


给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

d17090cf91014d418233c13ae364863a.png


0f843b65b25a47c582e6cb11ef464c48.png

思路:前序结果创建树,中序分割左右子树。子树区间确认是否继续递归创建子树,区间不存在则是空树。


class Solution 
{
private:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& preI, int inBegin, int inEnd)
    {
        if(inBegin > inEnd)
            return nullptr;
        // 构建根
        TreeNode* root = new TreeNode(preorder[preI]);
        // 用中序结果来分割左右子树
        int inI = inBegin;
        while(inI <= inEnd)
        {
            if(inorder[inI] == root->val)
                break;
            else
                ++inI;
        }
        // [inBegin, inI - 1] inI [inI + 1, inEnd]
        // 先构建左子树再构建右子树
        ++preI;    // 找出左右子树的根
        root->left = _buildTree(preorder, inorder, preI, inBegin, inI - 1);
        root->right = _buildTree(preorder, inorder, preI, inI + 1, inEnd);
        return root;
    }
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        int i = 0;
        return _buildTree(preorder, inorder, i, 0, inorder.size() - 1);
    }
};

1d9f0ed8db484f1b849e5780f174e539.png


注:因为preI是遍历前序数组preinorde的下标,整个递归遍历中都要使用,所以需要传引用。如果不是传引用而是传值的话,左子树构建好返回。因为preI不是引用,只是形参,无法将上一次递归的结果保留下来,那么这样就无法找到右子树的根了,也就无构建右子树了。


👉从后序与中序遍历序列构造二叉树👈


给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

72450a96252846af82108813679b1fbf.png

08a1ab7641fb4b29969bd36da7a5ef50.png


思路:后序结果创建树,中序分割左右子树。子树区间确认是否继续递归创建子树,区间不存在则是空树。因为后序遍历序列是左子树、右子树、根,所以后序遍历序列的最后一个元素是根节点,位于它前面的是右子树的根节点。所以先要构建右子树,再来构建左子树。最后把根和左右子树链接在一起,二叉树就构建完成了。


class Solution 
{
private:
    TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder, int& posI, int inBegin, int inEnd)
    {
        if(inBegin > inEnd)
            return nullptr;
        TreeNode* root = new TreeNode(postorder[posI]);
        // 中序分割左右子树
        int inI = inBegin;
        while(inI <= inEnd)
        {
            if(inorder[inI] == root->val)
                break;
            else
                ++inI;
        }
        // 先构建右子树再构建左子树
        // [inBegin, inI - 1] inI [inI + 1, inEnd]
        --posI;
        root->right = _buildTree(inorder, postorder, posI, inI + 1, inEnd);;
        root->left = _buildTree(inorder, postorder, posI, inBegin, inI - 1);
        return root;
    }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) 
    {
        int i = postorder.size() - 1;
        return _buildTree(inorder, postorder, i, 0, inorder.size() - 1);
    }
};

8fdddadc980a4923b7d700f12cc55b2b.png


注:中序与后序遍历序列是无法确定唯一的一块树的,使用两个遍历结果确定树的结构, 其中有一个遍历结果必须要是中序遍历结果。


👉总结👈


本篇博客主要讲解了二叉树进阶的 OJ 题,每道题都是非常经典的面试题。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️








相关文章
|
20小时前
|
C语言 C++
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
4 0
|
6天前
|
编译器 C++
【C++进阶】引用 & 函数提高
【C++进阶】引用 & 函数提高
|
6天前
|
存储 C++
【C++进阶(九)】C++多态深度剖析
【C++进阶(九)】C++多态深度剖析
|
6天前
|
编译器 C++
【C++进阶(八)】C++继承深度剖析
【C++进阶(八)】C++继承深度剖析
|
6天前
|
编译器 C语言 C++
【C++进阶(七)】仿函数深度剖析&模板进阶讲解
【C++进阶(七)】仿函数深度剖析&模板进阶讲解
|
6天前
|
设计模式 C语言 C++
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
|
6天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
6天前
|
算法 C++ 容器
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
|
6天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
|
6天前
|
存储 C语言 C++
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现