二叉树oj练习

(牛客,力扣题目)

题目1:根据二叉树创建字符串

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

题解:

第一步:

采用前序遍历的方式来遍历这棵树,之后在遍历的过程中进行操作使得括号出现;

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

        string str=to_string(root->val);
        str.push_back('(');
        str+=tree2str(root->left);
        str.push_back(')');

        str.push_back('(');
        str+=tree2str(root->right);
        str.push_back(')');

        return str;
    }

};

这样我们就获得了包含空的二叉树的字符串模式;为了符合题意,我们需要把空给去掉,但是呢,我们不能影响映射关系,因为我们的子节点左为空和右为空通过字符串来表答应该是不同的;

所以我们的代码应该加上一些条件:

这样就欧克了;

题目2:二叉树的层序遍历

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

这个题目有两种思路,但其实做法都差不多;

思路1

我们可以建立两个队列,让我们的节点和我们的节点所在的层数分别存储在两个队列中;这样我们就可以通过入队和出队的方式将我们的节点一层一层的输出;

入队方式:

那我们应该怎么让我们的数据入队呢 ?我们首先会让根节点入队,每个节点在出队的时候会载入我们的vector表中,然后在这个时候再把我们当前节点的子节点入队,这样就完成了一次入队出队的过程,每个节点都这么操作,使得所有节点都被放入vector表中;

这就是出入队列产生的操作,就这样所有的节点都会载入我们的vector中;

下面是实现的代码:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;

        queue<TreeNode*> qnode;
        queue<int> qlevel;
        qnode.push(root);
        qlevel.push(0);
        vv.push_back(vector<int>());//先插入一个空的vector<int>确保vv有空间
        while (!qnode.empty())
        {
            TreeNode* pop = qnode.front();
            int level = qlevel.front();
            qnode.pop();// 节点出队列
            qlevel.pop();// 节点层数出队列
            if ((level + 1) > vv.size())
                vv.push_back(vector<int>());
            vv[level].push_back(pop->val);
            if (pop->left)
            {
                qnode.push(pop->left);
                qlevel.push(level + 1);
            }
            if (pop->right)
            {
                qnode.push(pop->right);
                qlevel.push(level + 1);
            }
        }
        return vv;
    }
};

思路2

采用一个队列存储我们的节点,用一个int类型的levelsize来计数,第一次手动让根节点入队,levelsize通过队列的大小来获得(queue.size()),这个时候levelsize就等于我们的每层的节点数了;而想要更新下一层的节点树,就要先让我们队列开头的那层数据全部出队列;因为每次出队列,levelsize会-1,当levelsize减为0的时候就代表这一层的节点出完了;之后再把这一层的节点全部压入vector中;依此反复使得全部节点进入我们的vector中;

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        int levelsize = 0;
        if (root)
        {
            q.push(root);
            levelsize = 1;
        }
        vector<int> level;//存储每层的数据
        while (!q.empty())
        {
            TreeNode* pop = q.front();
            q.pop();//出队列入我们的数组
            level.push_back(pop->val);//将数据放入每层
            levelsize--;
            if (pop->left)//出了之后开始入队列
                q.push(pop->left);
            if (pop->right)
                q.push(pop->right);
            if (levelsize == 0)//一层出完了开始放入level中
            {
                levelsize = q.size();
                vv.push_back(level);
                level.clear();
            }
        }
        return vv;
    }
};

上面是代码实现;

题目3:二叉树的层序遍历 II

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

思路:本题思路很简单,复用上题的层序遍历的代码,最后反转我们的vector顺序表就好了;

//最后反转一下vector

reverse(vv.begin(),vv.end());

题目4: 二叉树的最近公共祖先

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

本题有两种思路;

思路1:

 当两个节点分别是某个节点的左右子树中的节点时,那么这个节点一定是这两个节点的根节点;

这个思路又又两个实现的方法:

方法1:

class Solution {
public:
    bool search(TreeNode* root, TreeNode* node)
    {
        if (root == nullptr)
            return false;

        return (root == node || search(root->left, node) || search(root->right, node));
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //这样开头是先序遍历查找的
        if (root == p || root == q)//先看根
            return root;
判断p和q在我们的哪边的子树上
        bool pinleft = search(root->left, p);
        bool pinright = !pinleft;

        bool qinright = search(root->right, q);
        bool qinleft = !qinright;

        if ((pinleft && qinright) || (pinright && qinleft))
            return root;
        else if (pinleft && qinleft)
            return lowestCommonAncestor(root->left, p, q);
        else
            return lowestCommonAncestor(root->right, p, q);
    }
};

这个方法是使用先序遍历的方式每次向下寻找,每次寻找都会去判断题目给出的两个节点是不是分别在当前节点的左右子树上;如果分别在当前的左右子树上,那此节点就是我们要找的节点;

时间复杂度是O(n^2):

当此树为歪脖子树的时候:

方法2:

class Solution {
public:
    bool search(TreeNode* root, TreeNode* p, TreeNode* q, TreeNode** parent)
    {
        if (root == nullptr)
            return false;
        if ((nil && nir) || ((root->val == q->val) && (nil || nir)) || ((root->val == p->val) && (nil || nir)))
            *parent = root;
        bool nil = search(root->left, p, q, parent);//nil全称是nodeinleft
        bool nir = search(root->right, p, q, parent);//nir->nodeinright
        return ((root->val == q->val) || (root->val == p->val) || nil || nir);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //后续遍历查看(深度优先dfs)
        TreeNode* parent = nullptr;
        search(root, p, q, &parent);
        return parent;
    }

代码实现上,是先通过后续遍历到达最深处,通过返回值来判断自己的子树下是否有左右节点(自己是左右节点也满足)子树有题目给出的左右节点的时候,就继续上寻找,当寻找到第一个把题目给出的左右节点当作子节点的时候,这个节点就是我们要找的最小祖先节点了;

我现在见过的递归函数的返回值有:void,int,bool; 

时间复杂度:

思路2: 

我们使用三叉链的方法转化为链表相交类型的问题,我们首先创建出两个栈,这两个栈将要存储的是从根节点到我们题目给出的两个节点的两条路径;我们前序遍历树,首先将当前节点压入栈中,只要不为空就先压入栈中,之后我们接收遍历的返回值,由于我们到树的底部的时候一定会有空返回,当有空返回的时候就会返回false值给上一节点,当节点接收子节点返回值全部为false时就会继续往下运行,当运行到pop,会弹出前面压入栈的数据;当我们节点遇到题目给出的节点的时候就会返回true跳出调用,不会pop数据,从而我们可以获得一条根在栈底,而我们的题目给出节点在栈顶的路径;

 之后就是通过出栈(栈的个数大的路径出栈)使得两条路径长度相同(栈的数据个数相同);再比较栈顶元素是否相同,不同则两栈同时出栈,相同时,这个栈顶元素就是我们需要的答案了;

class Solution {
public:
    bool getlist(TreeNode* root, TreeNode* node, stack<TreeNode*>& s)
    {
        if (root == nullptr)
            return false;
        s.push(root);
        if (root == node)
            return true;
        if (getlist(root->left, node, s))
            return true;
        if (getlist(root->right, node, s))
            return true;
        s.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //这是转换为三叉链方法
        stack<TreeNode*> sp;
        stack<TreeNode*> sq;
        //创建两个栈来存放我们的路径,这样出栈就可以还原成链表相交问题了
        getlist(root, p, sp);
        getlist(root, q, sq);

        while (sp.size() != sq.size())
        {
            sp.size() > sq.size() ? sp.pop() : sq.pop();
        }
        while (sp.top()!=sq.top())
        {
            sp.pop();
            sq.pop();
        }
        return sp.top();
    }
};

第5题: 二叉搜索树与双向链表

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

 本题思路:

本题就是把一个二叉树的左右指针指向改变,使得我们的左指针指向上一节点,右指针指向下一节点;我们可以再看看上面的图,我们发现形成的链表是中序遍历的,所以这样的链表,我们一定可以配合中序遍历来形成链表;那么这样我们就是在中序的过程中对我们的左右指针进行处理,改变它的指向;那么左右指针的改变究竟在中序的什么时候改变呢?我们通过下面的图例来辅助理解:

所以在中序的当前节点的操作过程中可以改变自己的左指针和上一节点的右指针;因为当我们从左节点回到当前节点的时候,我们左节点指向的指针就不需要了,而左节点右指针也是已经进行完才回到我们当前指针的,所以此时我们是可以更改左节点的右指针指向的; 

                                      将树转换为中序双向链表(二叉搜索树与双向链表)2024.3.15
class Solution {
public:
	void connect(TreeNode* cur, TreeNode*& prev)//中序遍历
	{
		if (cur == nullptr)
			return;
		connect(cur->left, prev);//调用操作左节点
		cur->left = prev;//更改当前节点的左指针
		if (prev)//改变上一节点prev的右节点
			prev->right = cur;
		prev = cur;//操作完成之后,当前节点也是返回到上一节点的prev(因为我们也是被调用的节点之一)
		connect(cur->right, prev);//调用操作右节点
	}
	TreeNode* Convert(TreeNode* pRootOfTree) {
		TreeNode* prev = nullptr;
		connect(pRootOfTree, prev);//调用函数进行中序遍历
		TreeNode* head = pRootOfTree;
		while (head && head->left)//使得指针指向最左节点(也是链表的起始节点)
		{
			head = head->left;
		}
		return head;
	}
};

第6题: 从前序与中序遍历序列构造二叉树

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

本题思路:

我们通过前序和中序,或者前序和后序可以知道树的结构;如何操作呢?首先,我们需要知道树前序是 根-左-右 这样排列的,我们的第一节点一定是根,而中序是 左-根-右 排列的,所以中序的最开始一定是最左节点,最后面一定是最右节点,知道了些我们可以把这整个大问题,看成一个个找根划分左右区间的小问题,每次递归时只需要处理相同的小问题即可,那如何操作呢?看下图:

你看因为我们的前序总是根左右排列的,所以我们只需要从左向右遍历即可,而从左向右遍历时我们遍历的当前节点一定是某棵树的根,而这棵树的根的左右树(也是左右区间)需要我们从中序遍历中获得,在中序遍历中,我们找到当前节点,在中序中不断划分出小区域给我们的子节点;并让父节点连接它们从而获得树的结构;

class Solution {
public:
    TreeNode*create(vector<int>& preorder,vector<int>& inorder,int &proot,int begin,int end)
    {
        if(proot>preorder.size()-1||begin>end)//当前序遍历完了,或者是自己的左右区间再无法划分了
            return nullptr;
        int rooti=begin;
        TreeNode*root=new TreeNode(preorder[proot]);
        proot++;
        while(rooti<=end)//在中序中寻找子节点
        {
            if(inorder[rooti]==root->val)
            {
                break;
            }
            rooti++;
        }
        //划分区间[begin,rooti-1]rooti[rooti+1,end]
        root->left=create(preorder,inorder,proot,begin,rooti-1);
        root->right=create(preorder,inorder,proot,rooti+1,end);
        return root;
    }

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

第6题:从中序与后序遍历序列构造二叉树

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

本题和上一题类似,我们只需要从后向前遍历,并先操作右树再操作左树即可;

//                                                  从中序与后序遍历序列构造二叉树
class Solution {
public:
    TreeNode* create(vector<int>& preorder, vector<int>& inorder, int& proot, int begin, int end)
    {
        if (proot<0 || begin>end)
            return nullptr;
        int rooti = begin;
        TreeNode* root = new TreeNode(preorder[proot]);
        proot--;
        while (rooti <= end)
        {
            if (inorder[rooti] == root->val)
            {
                break;
            }
            rooti++;
        }
        //划分区间[begin,rooti-1]rooti[rooti+1,end]
        root->right = create(preorder, inorder, proot, rooti + 1, end);
        root->left = create(preorder, inorder, proot, begin, rooti - 1);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int tmp = postorder.size() - 1;
        return create(postorder, inorder, tmp, 0, postorder.size() - 1);
    }
};

 第7题:​​​​​​​ 二叉树的前序遍历(非递归方式)

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

​​​​​​​

 思路:本题采用非递归的方式来前序遍历我们的树;首先前序遍历是先遍历我们的根再向左遍历的;我们先创建一个栈,每次向左遍历就将数据压入栈和vector中,当左节点遍历到空时,就开始弹出栈顶元素,并跳转到右子树,我们先查看右子树是否为空,为空则跳转到栈顶元素的右子树,如果右子树不为空则继续向左遍历,依此重复;直到栈中没有数据并且当前节点也为空时这棵树也就被遍历完了;

                                  二叉树的前序遍历(非递归)
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int>v;
        TreeNode* cur = root;
        while (cur || !s.empty())
        {
            while (cur)
            {
                s.push(cur);
                v.push_back(cur->val);
                cur = cur->left;
            }

            TreeNode* top = s.top();
            s.pop();
            cur = top->right;
        }
        return v;
    }
};

所以我们只需要掌握左为空时需要进行的操作就可以很好的处理此问题;

第8题:二叉树的中序遍历(非递归)

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

这题题目和上题一样,只是换成了中序遍历;

思路:

本题其实处理和上题一样,但是我们需要更改的地方就是将数据压入vector的时间,这次我们需要在数据弹出栈的才能将数据压入vector中,其他的操作不变;

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;
    }
};

第9题:二叉树的后序遍历(非递归)

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

题目同上,修改要求为后序;

思路:本题思路是我们因为需要最后才将根节点压入vector中,所以我们在左节点为空时,不能直接弹出栈顶元素,我们需要判断栈顶元素的右节点是否也为空,如果此时为空才能弹出栈顶元素,并将栈顶元素压入vector中;如果栈顶元素右节点不为空则跳转至右节点,继续进行处理,压入左节点入栈;

但是我们需要注意的是如果我们的数据已经在栈中弹出过一次了那么我们这个右节点就不能再跳转了,否则会形成死循环;

实现: 

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;
    }
};

  • 31
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值