• 【LeetCode】二叉树OJ


    目录

    一、根据二叉树创建字符串

    二、二叉树的层序遍历

    三、二叉树的层序遍历 II

    四、二叉树的最近公共祖先

    五、二叉搜索树与双向链表

    六、从前序与中序遍历序列构造二叉树

    七、从中序与后序遍历序列构造二叉树


    一、根据二叉树创建字符串

    606. 根据二叉树创建字符串 - 力扣(LeetCode)

    解题思路:本题在递归前序遍历二叉树的同时要注意()的省略,这时就有了三种情况:

    • 如果当前节点没有孩子,我们不需要在节点后面加上任何括号

    • 如果当前节点只有左孩子,我们在递归时,只需要在左孩子的结果外加上一层括号,而不需要给右孩子加上任何括号
    • 如果当前节点只有右孩子,我们在递归时,需要先加上一层空的括号 ‘()’ 表示左孩子为空,再对右孩子进行递归,并在结果外加上一层括号

    解题代码: 

    1. class Solution {
    2. public:
    3. string tree2str(TreeNode* root) {
    4. if(root==nullptr)
    5. return "";
    6. string str=to_string(root->val);
    7. if(root->left||root->right)//有左孩子或者无左孩子有右孩子
    8. {
    9. str+="(";
    10. str+=tree2str(root->left);
    11. str+=")";
    12. }
    13. if(root->right)//有右孩子
    14. {
    15. str+="(";
    16. str+=tree2str(root->right);
    17. str+=")";
    18. }
    19. return str;
    20. }
    21. };

    二、二叉树的层序遍历

    102. 二叉树的层序遍历 - 力扣(LeetCode)

    解题思路:本题为了达到层序遍历,我们需要用到一个队列来保存每一层的节点,将将根节点入队列,每次出队列时都要将其孩子节点入队列:

    还要有一个变量来记录每一层节点的个数,当该变量不为0时需要将节点出队列,所出节点都属于同一层的元素,每出一个节点将其减1。当该变量为0时,队列中都是上一层节点的孩子节点,所以都属于同一层节点,这时将变量置为队列中元素个数继续将节点出队列即可,直到队列为空

    解题代码:

    1. class Solution {
    2. public:
    3. vectorint>> levelOrder(TreeNode* root) {
    4. queue q;
    5. int levelsize=0;
    6. if(root)//根节点入队列
    7. {
    8. q.push(root);
    9. levelsize=1;
    10. }
    11. vectorint>> vv;
    12. while(!q.empty())
    13. {
    14. vector<int> v;//记录每一层的数据
    15. while(levelsize--)//将上一层节点出队列的同时,将其孩子节点都入队列
    16. {
    17. TreeNode* front=q.front();
    18. q.pop();
    19. v.push_back(front->val);
    20. if(front->left)
    21. q.push(front->left);
    22. if(front->right)
    23. q.push(front->right);
    24. }
    25. vv.push_back(v);//将遍历完的一层数据存入
    26. levelsize=q.size();
    27. }
    28. return vv;
    29. }
    30. };

    三、二叉树的层序遍历 II

    107. 二叉树的层序遍历 II - 力扣(LeetCode)

    解题思路:该题是上题的变形题,很多同学容易在这题中想的过多,这一题想要的结果是上题的结果的逆置,所以按上题思路处理,最后将其结果逆置一下即可:

    解题代码:

    1. class Solution {
    2. public:
    3. vectorint>> levelOrderBottom(TreeNode* root) {
    4. queue q;
    5. int levelsize=0;
    6. if(root)//根节点入队列
    7. {
    8. q.push(root);
    9. levelsize=1;
    10. }
    11. vectorint>> vv;
    12. while(!q.empty())
    13. {
    14. vector<int> v;//记录每一层的数据
    15. while(levelsize--)//将上一层节点出队列的同时,将其孩子节点都入队列
    16. {
    17. TreeNode* front=q.front();
    18. q.pop();
    19. v.push_back(front->val);
    20. if(front->left)
    21. q.push(front->left);
    22. if(front->right)
    23. q.push(front->right);
    24. }
    25. vv.push_back(v);//将遍历完的一层数据存入
    26. levelsize=q.size();
    27. }
    28. reverse(vv.begin(),vv.end());//将结果逆置
    29. return vv;
    30. }
    31. };

    四、二叉树的最近公共祖先

    236. 二叉树的最近公共祖先 - 力扣(LeetCode)

    解题思路:对于该题我们需要了解两个节点最近公共祖先的特性,即这两个节点分别在最近公共祖先节点的左右子树;所以我们只要找到满足该条件的节点就找到了最近公共祖先节点

    解题代码:

    1. class Solution {
    2. public:
    3. bool IsIntree(TreeNode* root, TreeNode* x)
    4. {
    5. if(root==nullptr)
    6. return false;
    7. if(root==x)
    8. return true;
    9. return IsIntree(root->left,x)||IsIntree(root->right,x);
    10. }
    11. TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    12. if(root==p||root==q)
    13. return root;
    14. bool pInleft=IsIntree(root->left,p);
    15. bool pInright=!pInleft;
    16. bool qInleft=IsIntree(root->left,q);
    17. bool qInright=!qInleft;
    18. if((pInright&&qInleft)||(pInleft&&qInright))
    19. {
    20. return root;
    21. }
    22. else if(pInleft&&qInleft)
    23. {
    24. return lowestCommonAncestor(root->left,p,q);
    25. }
    26. else
    27. {
    28. return lowestCommonAncestor(root->right,p,q);
    29. }
    30. }
    31. };

    但是该方法最坏情况下的时间复杂度为O(N^2),运行时长并不理想:

    那下面我们换个思路来优化一下:我们可以记录从根节点到两个要查找节点的路径,从而将其转换为链表相交问题(对于链表的相交问题我们之前在LeetCode和牛客网经典链表题目合集 讲过),对于路径的存储我们可以用到栈来记录,最终找到共同的最近祖先节点:

    1. class Solution {
    2. public:
    3. bool GetPath(TreeNode* root, TreeNode* x,stack& s)
    4. {
    5. if(root==nullptr)
    6. return false;
    7. s.push(root);
    8. if(root==x)
    9. return true;
    10. if(GetPath(root->left,x,s)||GetPath(root->right,x,s))
    11. return true;
    12. s.pop();
    13. return false;
    14. }
    15. TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    16. stack pPath,qPath;
    17. GetPath(root,p,pPath);
    18. GetPath(root,q,qPath);
    19. while(pPath.size()!=qPath.size())
    20. {
    21. if(pPath.size()>qPath.size())
    22. pPath.pop();
    23. else
    24. qPath.pop();
    25. }
    26. while(pPath.top()!=qPath.top())
    27. {
    28. pPath.pop();
    29. qPath.pop();
    30. }
    31. return pPath.top();
    32. }
    33. };

    这样子时间复杂度就降到了O(N):

    五、二叉搜索树与双向链表

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

    解题思路:在该题中需要利用二叉搜索树中序遍历的结果是升序的,所以我们在中序遍历时修改所在节点与前驱节点的指针指向即可;用cur指针表示当前节点,用prev指针表示中序遍历时当前节点的上一个遍历节点,使cur指针的左孩子:

    下面是一个二叉搜索树中序遍历时,修改当前节点和前驱节点的指针指向的演示:

    解题代码:

    1. class Solution {
    2. public:
    3. void InOrder(TreeNode* cur,TreeNode*& prev)
    4. {
    5. if(cur==nullptr)
    6. return;
    7. InOrder(cur->left,prev);
    8. cur->left=prev;
    9. if(prev)
    10. prev->right=cur;//让前驱节点指针的右孩子指向当前节点
    11. prev=cur;//更新前驱节点指针位置
    12. InOrder(cur->right,prev);
    13. }
    14. TreeNode* Convert(TreeNode* pRootOfTree)
    15. {
    16. if(pRootOfTree==nullptr)
    17. return nullptr;
    18. TreeNode* prev=nullptr;
    19. InOrder(pRootOfTree,prev);//中序遍历的同时修改二叉树节点指针的指向
    20. TreeNode* head=pRootOfTree;//pRootOfTree并非链表的头节点,需要找到头节点后返回
    21. while(head->left)
    22. {
    23. head=head->left;
    24. }
    25. return head;
    26. }
    27. };

    六、从前序与中序遍历序列构造二叉树

    105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

    解题思路:

    对于任意一颗树而言,前序遍历的形式总是

    [ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

    即根节点总是前序遍历中的第一个节点。而中序遍历的形式总是

    [ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

    只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位

    这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置

    解题代码:

    1. class Solution {
    2. public:
    3. TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder,int begini,int endi,int& n)
    4. {
    5. if(begini>endi)
    6. return nullptr;
    7. TreeNode* newnode=new TreeNode(preorder[n]);//先利用先序遍历确定根节点
    8. int rooti=begini;
    9. while(rooti<=endi)//找到确定的根节点在中序遍历中的位置
    10. {
    11. if(inorder[rooti]==preorder[n])
    12. break;
    13. else
    14. ++rooti;
    15. }
    16. ++n;
    17. newnode->left=_buildTree(preorder,inorder,begini,rooti-1,n);//将剩下的左区间的节点连接到左子树上
    18. newnode->right=_buildTree(preorder,inorder,rooti+1,endi,n);//将剩下的右区间的节点连接到右子树上
    19. return newnode;
    20. }
    21. TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    22. int n=0;
    23. return _buildTree(preorder,inorder,0,preorder.size()-1,n);
    24. }
    25. };

    七、从中序与后序遍历序列构造二叉树

    106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

    解题思路:该题和上一题是同类型题,我们使用后序遍历的逆序来确定根节点,再在中序遍历中找到其对应的节点,再递归地对构造出右子树和左子树(后序遍历的逆序是相当于先遍历右子树的前序遍历,所以我们先要构建右子树再构建左子树)

    解题代码:

    1. class Solution {
    2. public:
    3. TreeNode* _buildTree(vector<int>& inorder,vector<int>& postorder,int begini,int endi,int& n)
    4. {
    5. if(begini>endi)
    6. return nullptr;
    7. TreeNode* newnode=new TreeNode(postorder[n]);//先利用后序遍历确定根节点
    8. int rooti=begini;
    9. while(rooti<=endi)//找到确定的根节点在中序遍历中的位置
    10. {
    11. if(inorder[rooti]==postorder[n])
    12. break;
    13. else
    14. ++rooti;
    15. }
    16. --n;
    17. newnode->right=_buildTree(inorder,postorder,rooti+1,endi,n);//将剩下的右区间的节点连接到右子树上
    18. newnode->left=_buildTree(inorder,postorder,begini,rooti-1,n);//将剩下的左区间的节点连接到左子树上
    19. return newnode;
    20. }
    21. TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    22. int n=inorder.size()-1;
    23. return _buildTree(inorder,postorder,0,inorder.size()-1,n);
    24. }
    25. };
  • 相关阅读:
    开源MyBatisGenerator组件源码分析
    VM虚拟机克隆
    Envoy熔断限流实践(一)基于Rainbond插件实现熔断
    SQL找出重复的记录只显示一条记录
    Data-free NAS
    vue3 调度执行-控制执行次数
    Java中set集合简介说明
    【小程序源码】笑话与趣图框架
    通信算法之七十八:无人机反制—精华总结
    史上最全大厂面试题复盘总结,全会进大厂拿35K没问题(Java岗)
  • 原文地址:https://blog.csdn.net/m0_70811813/article/details/134486097