C++刷题 -- 二叉树oj

8 篇文章 0 订阅

C++刷题 – 二叉树oj


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

https://leetcode.cn/problems/construct-string-from-binary-tree/
前序遍历二叉树,打印出每个节点,每个根节点的左右子树都要用括号括起来;
注意:需要省略不影响二叉树结构的空括号:
1.左右子树都为空,则空括号可以省略;
2.左子树不为空,右子树为空,右子树空括号可以省略;
3.左子树为空,右子树不为空,左子树空括号不能省略;

class Solution {
public:
    string tree2str(TreeNode* root) {
        if(root == nullptr)
        {
            return string();
        }

        string ret;
        ret += to_string(root->val);

        //左子树不为空或者右子树不为空,左子树都要加括号
        if(root->left || root->right)
        {
            ret += '(';
            ret += tree2str(root->left);
            ret += ')';
        }

        //右子树不为空,才需要加括号,右子树为空的情况,都要省略右子树的空括号
        if(root->right)
        {
            ret += '(';
            ret += tree2str(root->right);
            ret += ')';
        }

        return ret;
    }
};

2.二叉树的层序遍历

https://leetcode.cn/problems/binary-tree-level-order-traversal/
二叉树的层序遍历,可以借助队列来完成,先将第一层节点进队,然后出队,出队的节点的val放进vv的第一层,紧接着将第一层节点的左右子节点都进队,然后挨个出队,出队时将val放进vv的第二层,同时将左右子节点进队,依次向下,直到队列为空;
在这里插入图片描述

在这里插入图片描述
在遍历进行中,会出现不同层节点同时出现在队列中的情况,导致我们无法判断要出队多少个节点;我们可以通过添加一个变量levelSize来记录当前层一共需要出队多少个节点,就不会发生上面的情况了;

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> q;
        vector<vector<int>> vv;
        int levelSize = 0;//levelSize记录当前层的所有节点的个数
        if(root)
        {
            q.push(root);
            levelSize = 1;
        }
        
        while(!q.empty())
        {
            vector<int> v;
            //根据levelSize来遍历当前层的所有节点
            for(int i = 0; i < levelSize; i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);
                //入队top的左右子节点
                if(front->left)
                {
                    q.push(front->left);
                }
                if(front->right)
                {
                    q.push(front->right);
                }
            }
            vv.push_back(v);
            levelSize = q.size();//队列中剩下的就是下一层的所有节点,赋值给levelSize
        }

        return vv;
    }
};

3.二叉树的层序遍历 II

https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/
要求从底层向上层序遍历二叉树,只需将上面的代码,在后面将vv逆置一下就可以了;

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> vv;
        int levelSize = 0;
        queue<TreeNode*> q;
        if(root)
        {
            q.push(root);
            levelSize = 1;
        }

        while(!q.empty())
        {
            vector<int> v;
            for(int i = 0; i < levelSize; i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);

                if(front->left)
                {
                    q.push(front->left);
                }

                if(front->right)
                {
                    q.push(front->right);
                }
            }
            vv.push_back(v);
            levelSize = q.size();
        }
        reverse(vv.begin(), vv.end());
        return vv;
    }
};

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

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
找二叉树两个节点的最近公共祖先,规则如下:
在这里插入图片描述
若对于其中的一个根节点,两个节点分别分布在根节点的左右子树中,那么这个根节点就是其最近的公共节点;
若两个节点都在左子树,就递归到左子树中继续找,右子树同理;
最近公共祖先有可能是该节点本身;

class Solution {
public:
    //寻找sub为根的这颗子树中,是否有x节点
    bool Find(TreeNode* sub, TreeNode* x)
    {
        if(sub == nullptr)
        {
            return false;
        }

        return sub == x
        || Find(sub->left, x)
        || Find(sub->right, x);
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr)
        {
            return nullptr;
        }
        if(p == root || q == root)
        {
            return root;
        }

        bool pInLeft, pInRight, qInLeft, qInRight = 0;
        pInLeft = Find(root->left, p);
        pInRight = !pInLeft;
        qInLeft = Find(root->left, q);
        qInRight = !qInLeft;

        //如果p和q分别在root的左右子树,那么root就是最近公共祖先
        if((pInLeft && qInRight) || (pInRight && qInLeft))
        {
            return root;
        }
        //如果p和q都在root的左子树,就递归到左子树中找
        else if(pInLeft && qInLeft)
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        //如果p和q都在root的右子树,就递归到右子树中找
        else if(pInRight && qInRight)
        {
            return lowestCommonAncestor(root->right, p, q);
        }
        else
        {
            return nullptr;
        }
    }
};

这种方法的时间复杂度为:O(H * N) (H为树的高度),如果对时间复杂度进行优化,有如下的算法:
找出从根节点到目标节点的路径,然后转化为链表相交;
对二叉树进行前序遍历,对遍历过的节点进行入栈,直到入栈了叶子节点后还没有找到目标节点,就出栈该叶子节点,转到其父节点的右子树中寻找,递归进行,直到找出目标节点后,栈中的节点就是路径;

class Solution {
public:
//方法二:时间复杂度O(N)
    //找出路径,转换为链表相交
    bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
    {
        if(root == nullptr)
        {
            return false;
        }
        
        path.push(root);//root可能是路径节点,入栈

        if(root == x)
        {
            return true;
        }
        
        //在root的左子树中寻找x
        if(FindPath(root->left, x, path))
        {
            return true;
        }
        //左子树中没找见,去右子树中找
        if(FindPath(root->right, x, path))
        {
            return true;
        }
        //root的左右子树中都没有,则root不是路径,出栈root
        path.pop();
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pPath, qPath;
        //找出root到p和q的路径
        FindPath(root, p, pPath);
        FindPath(root, q, qPath);

        //较长的路径先走
        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();
    }
};

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

https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&ru=/exam/oj
将二叉搜索树转换为一个已排序的双向链表;
二叉搜索树经过中序遍历得到的就是有序的序列,所以我们可以从中序遍历入手,依次改变节点的指针连接方向;
使用两个指针进行节点的控制:cur和prev,prev指向cur的前一个节点,使用cur控制节点的左指针的指向,prev控制节点的右指针的指向;

class Solution {
public:
	void InOrderConvert(TreeNode* cur, TreeNode*& prev)
	{
		if(cur == nullptr)
		{
			return;
		}
		//中序遍历
		InOrderConvert(cur->left, prev);
		//cur控制左指针
		cur->left = prev;
		if(prev)
		{
			//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;
    }
};

prev使用的是引用类型,是为了在递归后,能将上一层的prev的改变传到下一层;
在这里插入图片描述
第二层改变了prev,在函数返回第一层递归时要能够看到prev的改变,就需要使用引用,让prev一直是第一层的prev的别名;

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

https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
题目给定二叉树前序和中序遍历的序列,从这两个序列中还原出二叉树的结构;
在这里插入图片描述
使用前序遍历来确定根节点,中序遍历来划分左右子树;
前序遍历第一个节点为3,就是整棵树的根节点,在中序序列中找到三,其左边就是3的左子树,右边就是3的右子树;左子树只有9一个节点,右子树有15、20、7三个;继续在前序中寻找,9为3的左子树的根节点,20为右子树的根节点,左子树已经走完,看有字数,在中序中找20为右子树的根节点,将15、20、7分割为左右子树,这样就分割完了;

class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend)
    {
        //prei的变化需要一直传递,所以传引用
        if(inbegin > inend)
        {
            return nullptr;
        }
        //创建根节点
        TreeNode* root = new TreeNode(preorder[prei++]);

        //分割中序
        int ini = inbegin;
        while(inorder[ini] != root->val)
        {
            ini++;
        }

        //[inbegin, ini - 1] ini [ini + 1, inend]
        root->left = _buildTree(preorder, inorder, prei, inbegin, ini - 1);
        root->right = _buildTree(preorder, inorder, prei, ini + 1, inend);
        return root;
    }

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

当中序区间分割到只剩一个元素时,比如上面的9,递归进去inbegin和inend都是0,其继续递归左子树时,参数inbegin为0,ini - 1为-1,传进去的inbegin > inend,直接返回nullptr;

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

https://leetcode.cn/problems/binary-tree-preorder-traversal/
使用非递归的方法解决二叉树的前序遍历;
请添加图片描述

我们可以将二叉树的分为如下部分:
1.左路节点;
2.左路节点的右子树;

前序遍历可以先将所有的左路节点入栈,直到遇到空指针,然后取栈顶指针,出栈(一个节点出栈,就表明其根节点和左子树已经访问完毕了,接下来该访问右子树了),然后访问它的右子树,右子树的访问也是分为两步,左路节点和左路节点的右子树,直到栈为空,访问完毕;

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        vector<int> v;
        //先将root的左路节点全部入栈
        while(cur || !st.empty())//cur不为空是为了进入循环,st为空时循环结束
        {
            //开始访问一棵树
            while(cur)
            {
                //前序遍历先遍历该树的根节点,此时就要先将根节点的val插入v了
                v.push_back(cur->val);
                st.push(cur);
                cur = cur->left;
            }

            //拿出栈顶的节点,栈顶节点及其左子树已经访问了,开始访问它的右子树
            TreeNode* top = st.top();
            st.pop();

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

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

https://leetcode.cn/problems/binary-tree-inorder-traversal/
二叉树的中序遍历非递归方法与前序遍历相似,只是存入节点数据的时机不同,在遍历完左路节点后,不能直接访问根节点的数据,应该等左路节点出栈后再访问其根节点和右子树;

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        vector<int> v;
        while(cur || !st.empty())
        {
            //先将所有的左路节点入栈,但是先不访问其节点的数据
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            //中序遍历是要先访问左子树的数据,所以在左路节点出栈时,才将val插入到v中
            TreeNode* top = st.top();
            v.push_back(top->val);
            st.pop();

            //访问top的右子树
            cur = top->right;
        }
        return v;
    }
};

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

https://leetcode.cn/problems/binary-tree-postorder-traversal/
在这里插入图片描述
二叉树的后序遍历非递归方法与前序和中序略有不同,在遍历完左路节点后,当节点的右子树不为空时,有两种情况:
1.右子树还没访问过,需要访问右子树;
2.右子树已经访问过了,需要访问根节点;

为了区分这两种情况,可以添加一个prev指针来区分,当prev指向的是该节点的右子树的根节点,则表明已经访问过右子树了,如果不是,则表明右子树还没有访问;
访问节点数据的时机也需要改变,在访问完该节点的左右子树后,才能访问根节点的数据。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* prev = nullptr;//prev记录上一个访问的节点
        vector<int> v;

        while(cur || !st.empty())
        {
            //先入栈左路节点,但是不访问数据
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            //当该节点在栈顶时,就表明该节点的左子树已经访问完成了
            TreeNode* top = st.top();

            //若top的右子树为空或者右子树已经访问过了,就访问top
            if(top->right == nullptr || top->right == prev)
            {
                v.push_back(top->val);
                prev = top;
                cur = nullptr;//cur置空是为了不重复入栈cur的左路节点,该子树的根节点已经访问了,该访问根节点的父节点了
                st.pop();//top已经访问过了,出栈
            }
            //否则,该节点的右子树还没访问,就去访问右子树
            else
            {
                cur = top->right;
            }
        }
        return v;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值