【刷题记录】关于二叉树的OJ题(中)

简介: 【刷题记录】关于二叉树的OJ题(中)

3.二叉树的最近公共祖先


题目链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode)

题干:

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

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

示例1

70d3ab4beb5ccdde09eb719037c8160d.png

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvrEncu5-1683507657217)(null)]

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例3

输入:root = [1,2], p = 1, q = 2
输出:1


题目分析:


通过分析题目可知,需要找的p、q节点在树种的存在情况只有:1.pq都在当前节点所在树的左子树中,在左子树中找pq的公共祖先即可;2.pq都在右子树中,在右子树中找pq的公共祖先即可;3pq分别在左右子树中,当前节点即是所求公共祖先节点;4.pq中有一个是当前节点,返回当前节点即可。


代码实现:

class Solution {
public:
    bool isInTree(TreeNode* root, TreeNode* node)//判断node在不在树root中
    {
        if(root == nullptr)
            return false;
        //这里需要比较地址,而不是比较值
        return root == node || isInTree(root->left, node) || isInTree(root->right, node);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //由于pq在树中,所以这里不需要考虑root为空的情况
        if(p == root || q == root)//pq有一个是当前节点
        {
            return root;
        }
        //判断pq的位置
        bool pInLeft = isInTree(root->left, p);
        bool pInRight = ! pInLeft;
        bool qInLeft = isInTree(root->left, q);
        bool qInRight = ! qInLeft;
        if((pInLeft && qInRight) || (pInRight && qInLeft))//pq在两侧
        {
            return root;
        }
        if(pInLeft && qInLeft)//都在左子树
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        else//都在右子树
        {
            return lowestCommonAncestor(root->right, p, q);
        }
    }
};

fac56866c63358a88eabe15b2297a123.png

看一下结果,执行用时572ms,效率有点低啊,能不能优化一下嘞?分析一下我们的代码,我们的代码花费了太多时间在判断pq的位置上了,这里是一个普通二叉树,没办法很快的找到pq的位置,所以只能想想怎么优化掉这个过程了。


我们知道,pq节点肯定是在二叉树中的,那么他们在二叉树中就存在从根到节点的唯一路径,而且在这个路径中,绝对存在相同的部分,如果按照自低向上的看法,那么最终第一个相交的地方就是我们要找的公共祖先。那么做法思路就出来了:首先找到自低向上的节点路径,然后就可以类比链表相交的做法找到第一个相交的节点


代码实现:

class Solution {
public:
    bool GetPath(TreeNode* root, TreeNode* node, stack<TreeNode*>& path)
    {
        if(root == nullptr)
            return false;
        path.push(root);//首先当前节点入栈
        if(root == node)//当前节点就是所求节点时
            return true;
        //接下来需要在左右子树分别找,如果在左树中找到,就不需要去右树,否则还要去右树中找,所以这里需要判断有没有找到,所以这里把函数的返回值设置成bool
        if(GetPath(root->left, node, path))
        {
            return true;
        }
        //左树中没有,去右树种找
        if(GetPath(root->right, node, path))
        {
            return true;
        }
        //都没有时,代表当前根节点下边没有,所以pop掉
        path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    //在找pq的路径的时候,只能自顶向下找,所以找到之后需要逆置,而且后续操作中需要头删,所以这里干脆使用stack,就不需要中间的这些操作
        stack<TreeNode*> pPath;
        stack<TreeNode*> qPath;
        //这里不需要判断是否找到,因为题中已经确定pq在树种
        GetPath(root, p, pPath);
        GetPath(root, q, qPath);
        //让两个路径种长的先走,直到一样长的时候
        while(pPath.size() != qPath.size())
        {
            if(pPath.size() > qPath.size())
                pPath.pop();
            else
                qPath.pop();
        }
        //同时走,当节点相同时pop,返回相同节点即可
        while(pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }
        return pPath.top();
    }
};

2bb8a67be30f2f97e1677a6b2da44697.png

可以看到,效率提高显著。


4.二叉搜索树与双向链表


题目链接:二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

题干:

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

78fe190a3a3b5d06f46a98e6bfbbdd9d.png

数据范围:输入二叉树的节点数 0≤n≤1000,二叉树中每个节点的值 0≤val≤1000

要求:空间复杂度O(1)(即在原树上操作),时间复杂度 O*(*n)


注意:


1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继

2.返回链表中的第一个节点的指针

3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

4.你不用输出双向链表,程序会根据你的返回值自动打印输出

输入描述:

二叉树的根节点

返回值描述:

双向链表的其中一个头节点。

示例1

输入:{10,6,14,4,8,12,16}
返回值:From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
说明:输入题面图中二叉树,输出的时候将双向链表的头节点返回即可。     

示例2

输入:{5,4,#,3,#,2,#,1}
返回值:From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;
说明:               5
                  /
                4
              /
            3
          /
        2
      /
    1
树的形状如上图       


题目分析:

由于这是一个二叉搜索树,所以走中序遍历久是有序的结果,所以这里可以按照中序遍历的方式连接节点


代码实现:

class Solution {
public:
  void InOrderConvert(TreeNode* cur, TreeNode*& prev)//注意这里的参数类型设置,prev的参数类型要是引用,需要把prev的值带到其他栈帧中
  {
    if(cur == nullptr)//空树直接返回
      return;
    InOrderConvert(cur->left, prev);
    //中序操作
    cur->left = prev;//连接左结点
    if(prev)//连接右节点
      prev->right = cur;
    prev = cur;//交替向后走遍历所有节点
    InOrderConvert(cur->right, prev)
  }
    TreeNode* Convert(TreeNode* pRootOfTree) {
    TreeNode* prev = nullptr;
        InOrderConvert(pRootOfTree, prev);//重新连接节点
    TreeNode* head = pRootOfTree;
    while(head && head->left)//中序遍历的流程拿到最左节点
    {
      head = head->left;
    }
    return head;
    }
};


截屏2023-05-07 19.16.30.png


5. 从前序与中序遍历序列构造二叉树


题目链接: 105. 从前序与中序遍历序列构造二叉树

题干:

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

示例1:

tree.jpg

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例2

输入: preorder = [-1], inorder = [-1]
输出: [-1]


题目分析:

已知的条件是二叉树的前序和中续遍历的序列,那么通过前序序列可知根节点,在中序序列中,根节点把左右子树分开,就可以分别再构造左右子树,然后最后构造出整个二叉树


代码实现:

class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int begin, int end)
    {
        if(begin > end)//当区间不存在时,直接return
        {
            return nullptr;
        }
        int i = 0;
        for(; i < inorder.size(); ++i)//找到当前的[begin,end]的中序序列里面根的位置
        {
            if(inorder[i] == preorder[prei])
                break;
        }
        TreeNode* root = new TreeNode(preorder[prei++]);//构造一个当前序列的根节点
        //分别构建左右子树
        // [begin,i-1] i [i+1, end]
        root->left = _buildTree(preorder, inorder, prei, begin, i-1);
        root->right = _buildTree(preorder, inorder, prei, i+1, end);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int i = 0;//创建先序序列中根的下标
        return _buildTree(preorder, inorder, i, 0, preorder.size()-1);
    }
};


截屏2023-05-07 21.13.31.png

拓展与补充: 106. 从中序与后序遍历序列构造二叉树 。

与上题类似,这里通过后续遍历序列来确定根节点,然后中序遍历序列中的根节点的位置来分别构造左右子树

相关文章
|
2月前
二叉树oj题集(LeetCode)
二叉树oj题集(LeetCode)
|
4月前
|
算法 索引
leetcode654最大二叉树刷题打卡
leetcode654最大二叉树刷题打卡
19 0
|
3月前
二叉树OJ题目(2)
二叉树OJ题目(2)
13 0
|
4月前
leetcode617合并二叉树刷题打卡
leetcode617合并二叉树刷题打卡
18 0
【刷题记录】关于二叉树的OJ题(下)
【刷题记录】关于二叉树的OJ题(下)
【刷题记录】关于二叉树的OJ题(上)
【刷题记录】关于二叉树的OJ题(上)
|
10月前
|
算法
二叉树常见OJ题(笔记)
本篇总结了常见二叉树算法题,每个标题都有超链接,并给出AC代码。记得以后再刷几遍,容易忘记。
100 0
力扣---二叉树OJ题(多种题型二叉树)
第十三弹——力扣LeetCode每日一题
力扣---二叉树OJ题(多种题型二叉树)
leetcode刷题(10)二叉树(4)
leetcode刷题(10)二叉树(4)
leetcode刷题(8)二叉树(2)
各位朋友们,大家好!今天我为大家分享的是关于二叉树leetcode刷题的第二篇,我们一起来看看吧。