二叉树OJ题

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

606. 根据二叉树创建字符串

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

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

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

106. 从中序与后序遍历序列构造二叉树

144. 二叉树的前序遍历(非递归)

94. 二叉树的中序遍历(非递归)

145. 二叉树的后序遍历(非递归)


606. 根据二叉树创建字符串

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

题目解析:

第一步先来个前序遍历

第二步加上扩号 

最后一步判断特殊情况

第一种情况是左右孩子都为空则需要忽略括号,第二种情况是左孩子为空,右孩子不为空的时候不能忽略括号~

那么我们可以这样去划分~

对于左子树而言,若根的左子树存在那就一定得加括号,若根左子树不在,但根右子树在,那也要加括号。

对于右子树而言,前边已经处理好根左子树了,右子树只需要存在就加括号即可~

代码:

class Solution {
public:
    string tree2str(TreeNode* root) {
        if(root==nullptr)
        {
            return "";
        }
        string s = to_string(root->val);
        if(root->left||root->right)
        {
             s+='(';
             s+= tree2str(root->left);
             s+=')';
        }
        if(root->right)
        {
             s+='(';
             s+= tree2str(root->right);
             s+=')';
        }
            
       

        return s;
    }
};

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

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

题目解析:

法一:我们先来了解什么叫最近公共祖先~

在这种情况下,公共祖先的特点就是祖先在左右子树中都能找到两个目标节点~

这种虽然在根节点角度上只在子树中的一侧,但是在公共祖先眼中仍是看左右子树。另外如果刚好出现5,6这种情况,那默认5就是公共祖先~

所以关键点分为两个阶段:

一是判断q与p是在当前节点的左子树中还是右子树中

二是通过判断得到p与q在当前节点相对位置后再进行划分

  • p与q可能就在当前节点的左子树与右子树里——该节点为公共祖先
  • p与q可能全在当前节点的左子树里——那就继续深入左子树中找,直到能把p与q划分到左右子树两边
  • p与q可能全在当前节点的右子树里——那就继续深入右子树中找,直到能把p与q划分到左右子树两边

法二:

本题还能用另外一种写法,利用栈记录节点路径,然后让长度一致对比top值即可~

流程就是先入栈,然后再判断其左右节点,若左右节点都为空且不是目标节点则出栈(表明不在路径上)~

代码:

class Solution {
public:
    //判断目标节点是否存在当前节点的左右子树中
    bool isintree(TreeNode* root, TreeNode* target)
    {
        if (root == nullptr)
        {
            return false;
        }
        if (root == target)
        {
            return true;
        }
        else
        {
            return isintree(root->left, target) || isintree(root->right, target);
        }
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //返回条件
        if (root == nullptr)
        {
            return NULL;
        }
        //特殊结果
        if (root == p || root == q)
        {
            return root;
        }
        //我们假设出四种结果:分别是p在当前节点左子树或右子树 q在当前节点左子树或右子树
        bool pleft, pright, qleft, qright;
        //判断p是否存在root的左子树中
        pleft = isintree(root->left, p);
        pright = !pleft;
        //判断q是否存在root的左子树中
        qleft = isintree(root->left, q);
        qright = !qleft;
        //公共祖先:当前节点中左子树有孩子,右子树也有孩子
        if ((pleft && qright) || (qleft && pright))
        {
            return root;
        }
        //两个目标节点都在当前节点的左子树中
        else if (pleft && qleft)
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        //两个目标节点都在当前节点的右子树中
        else
        {
            return lowestCommonAncestor(root->right, p, q);
        }


    }
};

法二:

class Solution {
public:
    bool getstack(TreeNode* root, TreeNode* target, stack<TreeNode*>& path)
    {
        if (root == nullptr)
        {
            return false;
        }
        path.push(root);
        if (root == target)
        {
            return true;
        }
        //如果当前节点不是目标节点,那就去该节点的左子树找
        if (getstack(root->left, target, path))
        {
            return true;
        }
        //如果当前节点不是目标节点,那就去该节点的右子树找
        if (getstack(root->right, target, path))
        {
            return true;
        }
        //如果前面的左右子树仍没有找到目标节点,那就把该节点排出
        path.pop();
        return false;

    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pathp, pathq;
        getstack(root, q, pathq);
        getstack(root, p, pathp);
        //让长度一致
        while (pathp.size() != pathq.size())
        {
            if (pathp.size() > pathq.size())
            {
                pathp.pop();
            }
            else
            {
                pathq.pop();
            }
        }
        //开始找共同交点
        while (pathp.top() != pathq.top())
        {
            pathp.pop();
            pathq.pop();
        }
        return pathp.top();

    }
};

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

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

题目解析:

题目并不是要我们生成一个双向链表放入数据,而是在原来的二叉树中通过改变left与right的指向变成双向链表~

核心:left改为前驱指针,right改为后继指针~

我们采用中序遍历的模板,然后在中间阶段进行指针的修改~

我们通过让cur的前驱指针指向prev,再让prev更新为cur的方式可以把所有节点的前驱指针都布置完毕,那么后继指向要怎么做呢?

例如我们cur位于节点4的时候是无法用后继指针指向节点6的,因为还没有获取到节点,但是如果我们到达节点6后prev就会到达节点4,而这时候反而可以建立4->6的后继指针联系~

代码:

class Solution {
public:
    void inorder(TreeNode* cur, TreeNode*& prev)
    {
        if (cur == nullptr)
        {
            return;
        }
        inorder(cur->left, prev);
        //前驱指针
        cur->left = prev;
        if (prev)
        {
            //后继指针
            prev->right = cur;
        }
        prev = cur;


        inorder(cur->right, prev);

    }
    TreeNode* Convert(TreeNode* pRootOfTree) {
        TreeNode* prev = nullptr;
        inorder(pRootOfTree, prev);
        TreeNode* root = pRootOfTree;
        while (root && root->left)
        {
            root = root->left;
        }
        return root;
    }
};

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

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

题目描述:

本道题的核心就是通过前序遍历优先确认根节点,然后再通过中序遍历确认根节点的左右子树并划分出范围~

所以我们只需要先把当前问题划分好,然后再分割成子问题去解决就行了~

就比如当前我们的问题就是前序遍历指向了节点3,那么我们需要做的就是去中序遍历那里划分出其左右子树的范围就好了~

  • 离开条件:无法构成左右子树范围
  • 当前问题:前序选定,中序查其位置进行左右子树的划分
  • 子问题:通过前序的选定,再对其左子树的左右子树进行划分与右子树的左右子树进行划分

这种情况就像是当我们把节点3范围划分好后,其左子树的链接那就靠它自己了,右子树的链接也靠它自己了,因为解决问题的方法都是一样的~

代码:

class Solution {
public:
    TreeNode* creatTree(vector<int>& preorder, vector<int>& inorder,int& cur,int begin,int end)
    {
        //左右子树范围失败条件
        if(begin>end)
        {
            return nullptr;
        }
        //先获取到前序遍历组内的节点
        TreeNode* root = new TreeNode(preorder[cur]);
        //再通过该节点值去找对应中序遍历的位置当作范围界限
        int inorderi = 0;
        while(preorder[cur]!=inorder[inorderi])
        {
            inorderi++;
        }
        //获取到界限范围,开始划分
        //[begin,inorderi-1] inorderi [inorderi+1,end]
        //链接左右子树 cur++
        cur++;
        root->left = creatTree(preorder, inorder,cur,begin,inorderi-1);
        root->right =creatTree(preorder, inorder,cur,inorderi+1,end);

        return root;

    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int cur = 0;
        return creatTree(preorder, inorder,cur,0,preorder.size()-1);


    }
};

106. 从中序与后序遍历序列构造二叉树

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

题目解析:

后序加中序与前序加中序是差不多一样的,唯一不同的就是后序遍历的根得从最后面找起,而且构建的顺序也是先构建其右子树,因为根据左子树 右子树 根的规律节点20是为右子树的~ 剩下的就和上一题没什么区别了~

代码:

class Solution {
public:
    TreeNode* creatTree(vector<int>& inorder, vector<int>& postorder,int& cur,int begin,int end)
    {
        //左右子树范围失败条件
        if(begin>end)
        {
            return nullptr;
        }

        //先获取到后序遍历组内的根节点
        TreeNode* root = new TreeNode(postorder[cur]);
        //再通过该节点值去找对应中序遍历的位置当作范围界限
        int inorderi = 0;
        while(postorder[cur]!=inorder[inorderi])
        {
            inorderi++;
        }
        //获取到界限范围,开始划分
        //[begin,inorderi-1] inorderi [inorderi+1,end]
        //链接左右子树 cur--
        cur--;
        root->right =creatTree(inorder, postorder,cur,inorderi+1,end);
        root->left = creatTree(inorder, postorder,cur,begin,inorderi-1);

        return root;

    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int cur = postorder.size()-1;
        return creatTree(inorder, postorder,cur,0,postorder.size()-1);
    }
};



144. 二叉树的前序遍历(非递归)

144. 二叉树的前序遍历 - 力扣(LeetCode)

题目描述:

在非递归写法中,我们不再是把整棵树以左子树 根 右子树这样的遍历方式看待~

而是被划分为左路节点与左路节点的右子树~ 

然后我们再利用栈的特性来帮助我们完成二叉树的前序遍历~

最后我们再来考虑外面的循环条件,什么样的情况才需要继续循环呢?

我们针对最关键一步来推测出外循环条件~

  • 若cur为空,则说明该节点并无右子树,那是否意味着循环结束了呢?——得根据栈是否为空来判断,因为我们不仅仅要检查该节点有无右子树,其他的节点也需要检查~
  • 若cur不为空,则说明该节点有右子树,那还得继续重复操作:左路节点录入栈,检查栈内节点的右子树~

代码:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        while(cur||s.size())
        {
            //让左路节点入栈
            while(cur)
            {
                v.push_back(cur->val);
                s.push(cur);
                cur = cur->left;
            }

            //检查左路节点中是否有右路节点
            TreeNode* node = s.top();
            s.pop();
            cur = node->right;
        }
        return v;
    }
    
};

94. 二叉树的中序遍历(非递归)

94. 二叉树的中序遍历 - 力扣(LeetCode)

题目解析:

基本思路不变,唯一不同的就是我们不要在左路节点入栈的时候去记录,而是在出栈的时候去记录 ~比如节点6出栈则意味着左路节点已经遍历完,准备访问右子树,那这时候也符合中序遍历~

代码:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        while (cur || s.size())
        {
            //让左路节点入栈
            while (cur)
            {
                s.push(cur);
                cur = cur->left;
            }

            //检查左路节点中是否有右路节点
            TreeNode* node = s.top();
            s.pop();
            v.push_back(node->val);

            cur = node->right;
        }
        return v;
    }

};

145. 二叉树的后序遍历(非递归)

145. 二叉树的后序遍历 - 力扣(LeetCode)

题目描述:

后序遍历的情况就不同于前两道了~

我们可以设置一个指针prev用来记录上一个访问的节点~

那么如何辨别是第二次访问呢?

只需要看上一次访问的节点是否为右子树的根,如果是则代表已经访问过了,那么就可以访问当前的节点。如果不是,则说明右子树还没有访问,则暂且不访问当前节点,转而访问其右子树~

所以我们对出栈作出限制:只有当其右子树为空或者上一个节点为右子树根(代表已访问)的时候才可以出栈,顺便标记该节点作为prev。

代码:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        TreeNode* prev = nullptr;
        while (cur || s.size()) {
            // 让左路节点入栈
            while (cur) 
            {
                s.push(cur);
                cur = cur->left;
            }
            // 检查左路节点中是否有右路节点
            TreeNode* node = s.top();
            if(node->right==nullptr||prev ==node->right)
            {
                v.push_back(node->val);
                s.pop();
                prev = node;
            }
            else
            {
                cur = node->right;
            }
        }
        return v;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值