二叉树OJ

二叉树OJ

根据二叉树创建字符串

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

在这里插入图片描述

思路

观察题目, 得出规律:

  1. 节点左右都为空时, 两个括号均省略
  2. 节点左为空时, 不省略左括号
  3. 节点右为空时, 省略右括号

我们创建一个字符串, 先插入根节点(要将数字转为字符串插入) , 然后按照规律递归插入其他节点即可

示例代码

class Solution {
public:
    string tree2str(TreeNode* root) {

        //递归出口: 左右都为空,省略括号,返回空串
        if(root == nullptr)
        return "";

        string s=to_string(root->val);   //先将根节点插入字符串(需要转换成字符串格式)

        //两种情况: 1.左不为空, 不省略括号  2. 左为空, 右不为空, 不省略括号
        if(root->left || root->right)
        {
            s+='(';
            s+= tree2str(root->left);
            s+=')';
        }

        //右为不空, 不省略括号
        if(root->right)
        {
            s+='(';
            s+= tree2str(root->right);
            s+=')';
        }
        return s;
    }
};

二叉树的层序遍历

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

在这里插入图片描述

思路

定义一个保存节点指针的队列和一个记录当前层节点数量的变量levelSize;

利用队列进行层序遍历:

  1. 先将根节点入队列, levelSize更新为1
  2. 定义一个二维数组来存储整棵树层序遍历的情况, 当队列不为空时,通过levelSize控制一层一层地出
  3. 定义一个一维数组存储当前层的数据, levelSize>0的条件下,出当前队列的头节点后保存到一维数组中,带入下一层(带入当前节点的左右节点)
  4. 继续出当前层的其他节点,当前层节点出完了即levelSize=0时,把这一整层的数据插入到二维数组中
  5. levelSize=0时当前层节点出完了,下一层节点一定全部进队列了 ,更新levelSize,此时levelSize=q.size()[上一层数全部出完, 这一层全部进队列了,这一层节点个数就是队列中元素的个数]
  6. 重复上面的步骤, 出上一层带下一层,直至队列为空,最后返回二维数组即可

在这里插入图片描述

示例代码

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> q; 
        int levelSize=0;

        //根节点入队列
        if(root) 
        {
            q.push(root);
            levelSize=1;
        }

        vector<vector<int>> vv;
        while(!q.empty())
        {
            //通过levelSize控制一层一层地出
            vector<int> v;
            while(levelSize--)   //levelSize减到0当前层就出完了, 当前层出完了, 下一层一定进队列了
            {
                //出上一层
                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();
        }
        return vv;
    }
};

二叉树的层序遍历 II

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

在这里插入图片描述

思路

观察发现这题和上一题输出顺序相反,这题是自底向上的层序遍历,上一题是自顶向下的层序遍历;

所以只需按照上一题的步骤先自顶向下的层序遍历,在返回前逆置这个二维数组就是自底向上的层序遍历

示例代码

class Solution {
public:

    vector<vector<int>> levelOrderBottom(TreeNode* root) {

        //先自顶向下层序遍历后逆置即可

        queue<TreeNode*> q; 
        int levelSize=0;

        //根节点入队列
        if(root) 
        {
            q.push(root);
            levelSize=1;
        }

        vector<vector<int>> vv;
        while(!q.empty())
        {
            //通过levelSize控制一层一层地出
            vector<int> v;
            while(levelSize--)   //levelSize减到0当前层就出完了, 当前层出完了, 下一层一定进队列了
            {
                //出上一层
                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;
    }

};

二叉树的最近公共祖先

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

在这里插入图片描述

思路1

观察题目发现特征,分为两种情况:

  1. 两个节点中一个是根, 另一个是孩子, 那么根就是公共祖先
  2. 一个节点在我的左树,一个节点在我的右树,那么我就是公共祖先

所以找公共祖先的步骤:

  1. 满足情况1直接返回
  2. 一般情况下,要判断两个节点在左右哪个子树,写一个isinTree函数来判断
  3. 判断结果分3种: (1) 一个节点在我的左树,一个节点在我的右树,那么我就是公共祖先 (2) 两个节点都在左子树, 转化成子问题, 去左子树找 (3) 两个节点都在右子树, 转化成子问题, 去右子树找

但此思路效率较低,我们用思路2来改进

在这里插入图片描述

示例代码1

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(p==root || q==root)
            return root;

        //真正找公共祖先的过程

        //判断两个节点在左右哪个子树
        bool pInleft=isinTree(root->left,p);
        bool pInright=!pInleft;

        bool qInleft=isinTree(root->left,q);
        bool qInright=!qInleft;

 
        //1. 一个在我的左树, 一个在我的右树, 我就是公共祖先
        if((pInleft && qInright) || (qInleft && pInright))
        {
            return root;
        }
        else if(pInleft && qInleft)  //2. 都在左子树, 转化成子问题, 去左子树找
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        else    //3. 都在右子树, 转化成子问题, 去右子树找
        {
            return lowestCommonAncestor(root->right, p, q);
        }
    }
};

思路2

前提: 如果是三叉链(每个节点都有parent), 可以转化成链表相交的问题

此解法以这个前提为思路:DFS, 求出这两个节点的路径保存在容器中, 转换成路径相交问题(这里用栈保存)

求两个节点的路径:

  1. 先让节点入栈, 若此节点恰好是直接返回
  2. 递归到左子树去找, 左是直接返回 ; 递归到右子树去找, 右是直接返回
  3. 若左右都不是, 说明当前节点不在目标节点的路径上, 直接让当前节点出栈, 返回false到上一层继续找

转换成路径相交:

  1. 用栈来存储两个节点的路径上的各个节点
  2. 两个栈中元素个数不相等, 先出元素多的栈
  3. 两个栈中元素个数相等, 两个栈中元素都出栈
  4. 直至两个栈中栈顶元素都相等, 则此节点就是公共祖先

在这里插入图片描述

示例代码2

class Solution {
public:

    bool GetPath(TreeNode* root, TreeNode* x,   stack<TreeNode*>& path)
    {
        if(root==nullptr)
            return false;

        //先让节点入栈
        path.push(root);
        if(root==x)
            return true;

        //左是, 直接返回
        if(GetPath(root->left, x, path))
            return true;

        //右是, 直接返回
        if(GetPath(root->right, x, path))
            return true;

        //这个节点本身及左右都不是, 出栈
        path.pop();
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {

        //用栈来存储这一路径上的各个节点, 最后转换成链表相交的问题
        stack<TreeNode*> pPath, qPath;
        GetPath(root, p, pPath);
        GetPath(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();
    }
};

二叉搜索树与双向链表

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

在这里插入图片描述

思路1

思路1比较简单,首先将这棵二叉树进行中序遍历并将其节点存到数组中,后更改数组中的链接关系,返回数组的首元素即可

示例代码1

class Solution {
public:
	vector<TreeNode*> v;  //用来存放中序遍历的节点

	//中序遍历将结点放入数组中
	void Inorder(TreeNode* cur) 
	{
		if(cur==nullptr)
			return;

		Inorder(cur->left);
		v.push_back(cur);
		Inorder(cur->right);
    }
    TreeNode* Convert(TreeNode* pRootOfTree) {

		if(pRootOfTree==nullptr)
			return pRootOfTree;

		Inorder(pRootOfTree);

		//更改数组中的链接关系
		for(int i=0;i<v.size()-1;++i)
		{
			v[i]->right=v[i+1];
			v[i+1]->left=v[i];
		}
		return v[0];   //返回双向链表的头节点
    }
};

思路2

中序遍历,更改树的链接关系,定义一个prev节点和一个cur节点,让cur节点从根开始走一个中序遍历,让cur -> left = prev, prev->right=cur 递归更改链接关系即可;更改完后找一下这个双向链表的头节点返回头节点

在这里插入图片描述

示例代码2

class Solution {
public:

	void InorderConvert(TreeNode* cur, TreeNode*& prev) 
	{
		if(cur==nullptr)
			return;

		InorderConvert(cur->left, prev);

		//这里cur出现顺序就是中序
		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;   
    }
};

迭代实现二叉树的三种遍历

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

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

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

前序遍历

思路

先访问这棵树的左路节点, 将访问到的元素入栈同时插入到数组中,左路节点访问完后, 开始从栈中取左路节点(即栈顶元素), 这时表示左路节点的左子树已访问完了;后开始访问左路节点的右子树,当栈为空和当前节点为空时循环结束

总结: 访问左路节点 + 转化成子问题访问左路节点的右子树

在这里插入图片描述

示例代码
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

        stack<TreeNode*> st;
        vector<int> v;
        TreeNode*cur=root;

        while(!st.empty() || cur)
        {
            //访问一棵树: 1. 访问左路节点  2. 访问左路节点的右子树

            //访问左路节点
            while(cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur=cur->left;
            }

            //开始访问右子树
            TreeNode*top=st.top();
            st.pop();

            //转换成子问题
            cur=top->right;
        }
        return v;
    }
};

中序遍历

思路

思路与上面相似

改变的地方是开始访问的左路节点不插入数组中, 等左路节点访问完后再从栈里面取左路节点,插入数组中,这样遍历的结果就是中序

在这里插入图片描述

示例代码
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        TreeNode*cur=root;
        stack<TreeNode*> st;   //临时存储树中的节点
        vector<int> v;         //保存这棵树的遍历结果

        while(cur || !st.empty())
        {
            //开始访问左路节点
            while(cur)
            {
                st.push(cur);
                cur=cur->left;
            }

            //从栈里面取到左路节点, 表示左路节点的左子树访问完了
            TreeNode*top=st.top();
            st.pop();
            v.push_back(top->val);

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

后序遍历

思路1

思路与上面两种遍历相似

后序遍历要找到根节点可以访问的两种情况: 1. 当其右为空时 2. 当右子树已经访问过时

右为空直接判断;右子树是否已经访过了, 用一个前驱节点prev来记录访问, 直接判断根的右节点是否为前驱节点

在这里插入图片描述

示例代码1
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode*cur=root;
        stack<TreeNode*> st;   //临时存储树中的节点
        vector<int> v;         //保存这棵树的遍历结果

        TreeNode*prev=nullptr;
        while(cur || !st.empty())
        {
            //开始访问左路节点
            while(cur)
            {
                st.push(cur);
                cur=cur->left;
            }

            //从栈里面取到左路节点, 表示左路节点的左子树访问完了
            TreeNode*top=st.top();

            //可以访问根节点的两种情况: 1. 右为空  2. 右子树已经访问过了
            if( top->right==nullptr || top->right==prev)
            {
                st.pop();
                v.push_back(top->val);

                prev=top;
            }
            else
            {
                //访问左路节点的右子树
                cur=top->right;
            }
        }
        return v;
    }
};
思路2

还是前序遍历的思路, 只是这次是访问右路节点 + 转化成子问题访问右路节点的左子树

最后将这个数组逆置就由根-右-左 变成左-右-根

示例代码2
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode*cur=root;
        stack<TreeNode*> st;   //临时存储树中的节点
        vector<int> v;         //保存这棵树的遍历结果

        while(cur || !st.empty())
        {
            //开始访问右路节点
            while(cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur=cur->right;
            }

            //从栈里面取到右路节点, 表示右路节点的右子树访问完了
            TreeNode*top=st.top();
            st.pop();

            cur=top->left;
        }

        //由根-右-左 变成左-右-根
        reverse(v.begin(), v.end());

        return v;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值