秃头篇——二叉树进阶算法题

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

题目:

思路:这个题很明显需要我们采用二叉树的递归实现(前序遍历),但有一个注意的点:空括号能不能省略的问题,其实我们发现只要左为空,右不为空不能省略括号,剩下情况均可省略。

代码如下:

class Solution {
public:
    string tree2str(TreeNode* root) {
       string str;
       if(root==nullptr)
       {
        return str;
       }
       str+=to_string(root->val);//转换字符串函数
       //左不为空不能省略括号
       //左空右不空同样也不能省略(反映树的关系)
       //只有左右都空可省略 
       if(root->left||root->right) 
       {
          str+="(";
          str+=tree2str(root->left);
          str+=")";

       }
       //只要右为空即省略
       if(root->right)
       {
        str+="(";
        str+=tree2str(root->right);
        str+=")";
       }
       return str;
    }
};

二、二叉树的分层遍历1

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

这个与我们前面的二叉树初阶部分的层序遍历相似,但不同的是,它要把每层数据分别存储成一个二维数组,我们之前的思路是创建两个队列,分别存储该节点及其孩子,等该节点pop掉又会把他们的孩子带进来。

本题思路:创建一个vector<vector<int>>,再创建一个队列以及变量levelsize(记录每层多少个数据),队列存储的是该节点的指针(并不是该节点数据)每次pop掉以后插入到vector变量中,最后把每次的vector 插入到vector<vector<int>>即可。

代码如下:

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;
        }
        while(levelsize>0)
        {
           //一层一层出
            vector<int>v;
            while(levelsize--)
            {
                
                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;
    }
};

三、二叉树的分层遍历2

题目,与分层遍历1相同,只不过是从底向上遍历。

思路:在二的代码的基础上逆置一下即可,即在return vv前加一个reverse(vv.begin(),vv.end());代码略

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

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

比如:5和4的公共祖先是5(自己也可以是祖先),6和4的公共祖先是5,2和8的公共祖先是3,我们能够看出一个规律:除了自己是自己祖先的情况下,剩下的情况一个在祖先的左子树,一个在右子树(只有最近祖先满足这个规律!)

代码如下:

class Solution {
public:
    bool isintree(TreeNode* t, TreeNode* x)//判断是否在子树中
    {
            if(t==nullptr)
            return false;
            return t==x||isintree(t->left,x)||isintree(t->right,x);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)
        {
          return nullptr;
        }
        if(root==p||root==q)//如果自己就是祖先直接返回即可
        {
            return root;
        }
        bool pinleft=isintree(root->left,p);

        bool pinright = !pinleft;

        bool qinleft=isintree(root->left,q);

        bool qinright = !qinleft;
        if((pinleft&&qinright)||(qinleft&&pinright))//孩子分别在祖先的两侧就是祖先
        {
            return root;
        }
        else if(pinleft&&qinleft)//右子树没有了直接去左子树找形成新的树
            return lowestCommonAncestor(root->left,p,q);
        else
             return lowestCommonAncestor(root->right,p,q);
    }
};

虽然运行时结果都正确,但时间复杂度太高了,不太满足实际。

我们提供另一个思路:如果能求出两个结点到根的路径,那么就可以转换成链表相交问题,两个链表的交点就是公共祖先。我们用一个两个栈即可。代码如下:

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

五、二叉搜索树转换成双向链表

我们提供一个简单的思路:中序遍历二叉树,将二叉树的结点存在vector容器中,再把结点的链接关系进行更改即可。在此我们就不用代码演示了。

我们来看下一种思路:依旧是中序遍历二叉树,遍历过程中修改左指针为前驱和右指针为后继指针。记录一个cur和prev,cur为当前中序遍历到的结点,prev为上一个中序遍历的结点,cur->left=prev,但我们不知道cur->right指向哪里,但我们可以让prev->right=cur,也就是说,每个结点的左是再中遍历到当前结点时修改指向前驱,但当前结点的右,是在遍历到下一个结点才可修改后继的。

代码如下:
 

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* root) {
        if(root==nullptr)
		return nullptr;

		TreeNode* prev=nullptr;
		inorderconvert(root, prev);
		TreeNode*head=root;
		while(head->left)
		{
			head=head->left;
		}
//循环链表,头尾相接
		head->left=prev;
		prev->right=head;
		return head;

    }
};

我们发现,以上所有的题目貌似都是用递归的思想去实现代码的,接下来我们来实现一下非递归的思想。

六、二叉树的前、中、后序遍历(非递归)

前序:先访问左路结点,再访问左路结点的右子树,访问右子树要以循环从栈依次取出这些结点,循环子问题的思想访问左路结点的右子树,所以我们需要用一个栈和循环实现。

代码如下:
 

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
       stack<TreeNode*>st;
       vector<int> v;
       TreeNode*cur=root;
       while(cur||!st.empty())
       {
        //每次循环代表访问一棵树的开始
        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> preorderTraversal(TreeNode* root) {
       stack<TreeNode*>st;
       vector<int> v;
       TreeNode*prev=nullptr;
       TreeNode*cur=root;
       while(cur||!st.empty())
       {
        //每次循环代表访问一棵树的开始
        while(cur)
        {
            st.push(cur);
            cur=cur->left;
        }
       //取一个左路节点的右子树出来访问,这时代表左路节点的左子树已经访问过了
        TreeNode* top=st.top();
         //右子树为空或者上一个访问的节点是右子树的根,代表右子树也访问过了。
         if(top->right==nullptr||top->right==prev)
         {
          v.push_back(top->val);
          st.pop();
           prev=top;
         }
         else
         {
         cur=top->right;
          }
    }return v;
};

1. 什么是二叉树二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值