C++:二叉树进阶OJ题

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

给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

class Solution {
public:
    string tree2str(TreeNode* root) {
        if(root==nullptr){//根节点为空,返回空字符串
            return "";
        }
        
        //1.左右子树为空时,省略括号
        if(root->left==nullptr&&root->right==nullptr){
            return to_string(root->val);
        }
        //2.右子树为空时,省略括号
        if(root->right==nullptr){
            return to_string(root->val)+"("+tree2str(root->left)+")";

        }
        //左子树为空,右子树不为空时与左右子树不为空情况一样都不能省略括号
        return to_string(root->val) + "(" + tree2str(root->left) + ")(" + tree2str(root->right) + ")";
    }
};

二叉树创建字符串:本质上就是在前序遍历二叉树的过程中添加括号,再对空节点省略括号的情况进行分析:1.左右子树为空时,省略括号;2.右子树为空时,省略括号。其他情况都要在递归左子树和右子树前添加括号。

二、二叉树的层序遍历

2.1 二叉树的层序遍历Ⅰ

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。(即逐层地,从左到右访问所有节点)

 

 

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> q;
        vector<vector<int>> ret;
        if(root!=nullptr){
            q.push(root);
        }
        while(!q.empty()){
            vector <int> tmp;\
            //q.size()为该层节点个数,控制出队列节点
            for(int i=q.size();i>0;i--){
                root=q.front();
                q.pop();
                tmp.push_back(root->val);
                //root的左右节点不为空,插入队列
                if(root->left!=nullptr){
                    q.push(root->left);
                }
                if(root->right!=nullptr){
                    q.push(root->right);
                }
                
            }
            ret.push_back(tmp);
        }
        return ret;


    }
};

二叉树的层序遍历Ⅰ:构造一个quque<TreeNode*>队列,将根节点root插入队列。根据队列先入先出的特点,再通过for循环控制出队列节点个数,让出队列节点个数等于每层节点个数。出队列时,先更新root节点,向数组tmp插入该节点的值。并将root的左右节点(左右节点不为空)插入队列。for循环结束,该层节点遍历结束(该层节点全部出队列,下一层节点全部入队列),将该层节点值的数组tmp插入返回数组ret。

2.2 二叉树的层序遍历Ⅱ

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        queue<TreeNode*> q;
        vector<vector<int>> ret;
        if(root!=nullptr){
            q.push(root);
        }
        while(!q.empty()){
            vector <int> tmp;
            for(int i=q.size();i>0;i--){
                root=q.front();
                q.pop();
                tmp.push_back(root->val);
                if(root->left!=nullptr){
                    q.push(root->left);
                }
                if(root->right!=nullptr){
                    q.push(root->right);
                }
                
            }
            ret.push_back(tmp);
        }
        //reverse函数逆置返回数组
        reverse(ret.begin(),ret.end());
        return ret;


    }
};

二叉树的层序遍历Ⅱ:与二叉树的层序遍历Ⅰ一样,只需要在最后用reverse函数逆置返回数组ret。

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

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

class Solution {
public:
    bool IsInTree(TreeNode* root, TreeNode* x) {
        if (root == nullptr) {
            return false;
        }
        if (root == x)
            return true;
        return IsInTree(root->left, x) || IsInTree(root->right, x);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) {
            return nullptr;
        }
        if (root == p || root == q) {
            return root;
        }
        bool pInLeft, pInRight, qInLeft, qInRight;
        pInLeft = IsInTree(root->left, p);
        pInRight = !pInLeft;
        qInLeft = IsInTree(root->left, q);
        qInRight = !qInLeft;
        // 一个在左子树,一个在右子树
        if ((pInLeft && qInRight) || (pInRight && qInLeft)) {
            return root;
        }
        // 都在左子树
        else if (pInLeft && qInLeft) {
            return lowestCommonAncestor(root->left, p, q);
        }
        //都在右子树
        else if (pInRight && qInRight) {
            return lowestCommonAncestor(root->right, p, q);
        }
        return NULL;
    }
};

二叉树的最近公共祖先:用IsInTree函数判断p,q节点在左子树还是右子树,在就返回true,不在就返回false。根据返回结果的情况:1.一个在左子树,一个在右子树,直接返回root节点;2.都在左子树(右子树),就递归左子树(右子树)继续向下寻找,再次判断p,q节点所在位置。如果在递归中发现p或q为root节点,直接返回root。

class Solution {
public:
    bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, stack<TreeNode*>& path) {
        if (nullptr == pRoot)
            return false;
        // 先将根节点放在路径中
        path.push(pRoot);
        // 如果根节点和pNode相等,路径找到
        if (pNode == pRoot)
            return true;
        // 如果根节点和pNode不相等,先递归在左子树中找,找到则退出
        if (GetNodePath(pRoot->left, pNode, path))
            return true;
        // 未找到时,再到根节点的右子树中找
        if (GetNodePath(pRoot->right, pNode, path))
            return true;
        // 如果左右子树中都没有找到,说明根节点不在路径中
        path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 如果是空树,不存在最近公共祖先节点
        if (nullptr == root)
            return nullptr;
        // 获取两个节点在二叉树中的路径,并保存在栈中
        stack<TreeNode*> pPath;
        stack<TreeNode*> qPath;
        GetNodePath(root, p, pPath);
        GetNodePath(root, q, qPath);
        // 找最近公共祖先
        size_t pSize = pPath.size();
        size_t qSize = qPath.size();
        TreeNode* pCommonAncestor = nullptr;
        while (pSize && qSize) {
            // 如果两个栈中元素不相等,长的先出栈,直到两个栈中元素相等
            if (pSize > qSize) {
                pPath.pop();
                pSize--;
            } else if (pSize < qSize) {
                qPath.pop();
                qSize--;
            } else {
                // 如果栈顶元素相等,即为最近公共祖先
                // 否则:两个栈同时出栈
                if (pPath.top() == qPath.top()) {
                    pCommonAncestor = pPath.top();
                    break;
                } else {
                    pPath.pop();
                    qPath.pop();
                    pSize--;
                    qSize--;
                }
            }
        }

        return pCommonAncestor;
    }
};

 二叉树的最近公共祖先:按照前序获取节点pNode的路径,因为公共祖先从下往上找,因此将路径中的节点保存在栈中。两个栈中元素不相等,长的先出栈,直到两个栈中size相等。最近公共祖先为两个队列中位置与值都相同的节点

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

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

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

 

 

class Solution {
  public:
    void Treevector(TreeNode* pRootOfTree, vector<TreeNode*>& vtn) {
        if (pRootOfTree == nullptr) {
            return;
        }
        Treevector(pRootOfTree->left, vtn);
        vtn.push_back(pRootOfTree);
        Treevector(pRootOfTree->right, vtn);
        return;
    }

    TreeNode* Convert(TreeNode* pRootOfTree) {
		if (pRootOfTree == nullptr) {
            return nullptr;
        }
        vector<TreeNode*> vtn;
        Treevector(pRootOfTree, vtn);
        for (int i = 0; i < vtn.size(); i++) {
            if (i == 0) {
                vtn[i]->left = nullptr;
                vtn[i]->right = vtn[i + 1];
                continue;;
            }
            vtn[i]->left = vtn[i - 1];
            vtn[i]->right = vtn[i + 1];
            if (i == vtn.size() - 1) {
                vtn[i]->left = vtn[i - 1];
                vtn[i]->right = nullptr;
                break;
            }
        }
		return vtn[0];
    }
};

二叉搜索树与双向链表:二叉搜索树按照中序遍历将节点插入vector<TreeNode*>,变为一个有序数组。遍历数组依次修改每个节点left与right,让left指向前驱节点,right指向后继节点,最后返回数组首元素。

class Solution {
  public:
    void InorderConvert(TreeNode* cur, TreeNode*& 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) {
        if (!pRootOfTree){
            return pRootOfTree;
        } 
        TreeNode* prev=nullptr;
        InorderConvert(pRootOfTree, prev);
        TreeNode* head=pRootOfTree;
        while(head&&head->left){
            head=head->left;
        }

        return head;
    }
};

二叉搜索树与双向链表:在二叉搜索树按照中序遍历的过程中直接修改left和right,并加入一个TreeNode* prev指向当前节点cur的前一个节点。调用InorderConvert函数参数为prev的引用(能做用到原指针变量),让当前节点的left,指向前一个节点(prev);让前一个节点(prev)的right指向当前节点 。最后循环head->left,找到头节点。

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

class Solution {
public:
    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 rooti=inbegin;
        while(rooti<=inend){
            if(inorder[rooti]==root->val){
                break;
            }
            else rooti++;

        }
        //[inbegin,rooti-1] rooti [rooti+1,inend]
        root->left=_buildTree(preorder,inorder,prei,inbegin,rooti-1);
        root->right=_buildTree(preorder,inorder,prei,rooti+1,inend);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //i遍历前序数组
        int i=0;
        TreeNode *root=_buildTree(preorder,inorder,i,0,inorder.size()-1);
        return root;
    }
};

从前序与中序遍历序列构造二叉树:按照前序数组的顺序构建节点,在中序数组中找当前节点元素的位置,分割出左子树区间,根,右子树区间。接着递归构建左子树区间(左子树继续分割出左子树区间,根,右子树区间),直到inbegin>inend说明左子树区间构建完毕。再递归构建右子树区间。

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

class Solution {
public:
    TreeNode*_buildTree(vector<int>& inorder, vector<int>& postorder,int &prei,int inbegin,int inend){
        if(inbegin>inend){
            return nullptr;
        }
        TreeNode*root=new TreeNode(postorder[prei--]);
        
        int rooti=inbegin;
        while(rooti<=inend){
            if(inorder[rooti]==root->val){
                break;
            }
            else rooti++;

        }
        root->right=_buildTree(inorder,postorder,prei,rooti+1,inend);
        root->left=_buildTree(inorder,postorder,prei,inbegin,rooti-1);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int i=postorder.size()-1;
        TreeNode*root=_buildTree(inorder,postorder,i,0,inorder.size()-1);
        return root;
    }
};

从中序与后序遍历序列构造二叉树:和从前序与中序遍历序列构造二叉树方法一样,不过按照后序数组的逆序来构建节点。根,右子树,左子树依次递归构造。

七、二叉树前序、中序、后序的非递归遍历

7.1 二叉树前序非递归遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;

        TreeNode*cur=root;
        //刚开始栈为空所以||cur,让根节点入栈
        while(cur||!s.empty()){
            while(cur){
                //先插入节点的值,再继续向左走
                v.push_back(cur->val);
                s.push(cur);
                cur=cur->left;
            }
            TreeNode* top=s.top();
            s.pop();
            cur=top->right;
        }
        return v;
    }
};

 二叉树前序非递归遍历:前序非递归遍历需要借助栈,将二叉树分为左路节点和左路节点的右子树。先访问左路节点,向v中插入结点的值,让左路节点依次入栈。当cur为空时,左路节点访问结束。然后根据栈先入后出的特性,去访问左路节点的右子树,将栈顶节点出栈。(让右子树也分为左路节点和左路节点的右子树,先访问左路节点,让左路节点依次入栈(伪递归))。当栈为空所有左路节点的右子树访问完毕,结束循环。

7.2 二叉树中序非递归遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        while (cur || !s.empty()) {
            while (cur) {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            s.pop();
            v.push_back(top->val);
            cur = top->right;
        }
        return v;
    }
};

二叉树中序非递归遍历:与前序的非递归相同,不过要等左路节点遍历结束后,用top获取栈顶结点,再向v中插入节点的值。

7.3 二叉树后序非递归遍历

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode*cur=root;
        TreeNode*prev=nullptr;
        while (cur||!s.empty()) {
            while (cur) {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode*top=s.top();
            if(top->right==nullptr||top->right==prev){
                v.push_back(top->val);

                s.pop();
                prev=top;
            }
            else{
                cur=top->right;
            }
        }
        return v;
    }
};

二叉树后序非递归遍历:需要prev记录前一个访问节点。如果top->right为空或者top->right==prev(右节点已经访问过),无需访问右子树,直接插入节点的值,弹出栈顶元素,将prev更新为当前节点。否则访问右子树。

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值