二叉树递归修炼【上】

BingWallpaper23

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

题目链接:606. 根据二叉树创建字符串

1.1 题目描述

image-20220425204755197

1.2 顺藤摸瓜

题目要求通过括号来表示这个二叉树的结构,而且不能破坏输入与输出的映射关系

那么首先应该是想到用递归解决,然后分别判断条件,我们可以再递归调用左子树和右子树的时候再前后分别包上括号

不过,包括号是有条件的,因为我们要保证一一映射的关系,所以说如果右子树为空的话就不用加括号,但是如果左子树为空而右子树不为空要加上空括号表示括号中的是空树,这题目中给出了,所以根据分析我们可以列出下面的表格

1.3 抽丝剥茧

Situation操作
空树返回string空的构造器
左树或者右树有一个不为空左括号+递归左子树+右括号
右树不为空左括号+递归右子树+右括号

1.4 手到拈来

 string tree2str(TreeNode* root) {
        if(root==nullptr)
            return string();

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

image-20220425211459968

1.5 刻意求工

上述这样的写法还有值得优化的地方

string存在问题,传值返回调用的都是临时对象,也就是如果树结构比较大的话,会需要不断地调用拷贝构造,全是string的深拷贝,这样的话会有较大的消耗

    void _tree2str(TreeNode* root,string& str) {
        if(root==nullptr)
            return;

        str += to_string(root->val) ;

        if(root->left || root->right)
        {
            str+="(";
            _tree2str(root->left,str);
            str+=")";
        }

        if(root->right)
        {
            str+="(";
            _tree2str(root->right,str);
            str+=")";
        }
    }

     string tree2str(TreeNode* root) {
        string str;
        _tree2str(root,str);
        return str;
     }

image-20220425213707349

2. 二叉树的层序遍历

题目链接:102. 二叉树的层序遍历

2.1 题目描述

image-20220425213956871

2.2 顺藤摸瓜

怎么判断是节点是哪一层的是关键点,所以要控制levelSize,直到levelSize在for循环走完

我们知道第一层必然只有一个,但是怎么知道后面层的数据?当前层的数据出完之后,队列中剩的就是下一层的数据个数,所以可以记录一个levelSize来确定

2.3 抽丝剥茧

如果是空树,直接返回空的vector就可以

如果非空,第一层已经确定一定是levelSize为1,然后开始循环,直到队列中拿出来的是空的,不然不断循环

凡是某一层的节点全部出完之后,一定会把下一层的所有节点全部进入队列,然后就可以通过此来计算levelSize,然后再通过levelSize来出父亲,进孩子,在每次出数据之后push_back到vector<int>中,在每次循环之后把vector<int>给push_back到vector<vector<int>>中即可

2.4 手到拈来

    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==nullptr)
            return vector<vector<int>>();
        
        queue<TreeNode*> q;
        int levelSize=1;
        q.push(root);

        vector<vector<int>> vv;
        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);
            }
                //更新levelSize
            vv.push_back(v);
            levelSize=q.size();
        }
        return vv;
    }

3. 二叉树的层序遍历II

题目链接:107. 二叉树的层序遍历 II

3.1 题目描述

image-20220425221022959

3.2 手到拈来

本题只需要在上一题的基础上用一下reverse就可以了其实

    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        if(root==nullptr)
            return vector<vector<int>>();
        
        queue<TreeNode*> q;
        int levelSize=1;
        q.push(root);

        vector<vector<int>> vv;
        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);
            }
                //更新levelSize
            vv.push_back(v);
            levelSize=q.size();
        }
        reverse(vv.begin(),vv.end());
        return vv;

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

题目链接:236. 二叉树的最近公共祖先

4.1 题目描述

image-20220426143939231

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */

4.2 顺藤摸瓜

注意自己是可以当作自己的祖先的

image-20220426144533446

比如上图的5和4的公共祖先就是5

其实所以我们可以发现,只要我们去找当前节点中往根走的路径中第一个相交的就是最近的公共祖先

这道题目其实分结构讨论

4.3 抽丝剥茧

4.3.1 三叉链

假如这道题目改成树结构是三叉链结构就会很简单,有一个带有parent的指针,只需要倒着走把题目简化成链表相交就可以了

然而如果不是三叉链,那么只能找规律

4.3.2 搜索二叉树结构

image-20220426145852936

如果是搜索二叉树的话,从根开始查找

查找根找到找不到
子节点都比根要小/递归往左子树寻找
子节点都比根要大/递归往右子树寻找
一个比根节点大,一个比根节点小✔️/

4.3.3 普通二叉树

image-20220426145607116

从根开始搜索,其实只要节点一个在左树,一个在右树就说明是最近公共祖先

子节点的位置下一步操作
都在左树递归到左子树
都在右树递归到右子树
一左一右根就是最近的公共祖先

4.4 手到拈来

这道题目是一个普通二叉树,把上面的写出来还是比较简单的,但是我们还会面临一个问题

    bool Find(TreeNode* root,TreeNode*x)
    {
        if(root==nullptr)
            return false;
        if(root==x)
            return true;
        return Find(root->left,x)||Find(root->right,x);
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        bool is_p_left,is_p_right,is_q_left,is_q_right;
        //两者互为真假
        is_p_left=Find(root->left,p);
        is_p_right=!is_p_left;

        is_q_left=Find(root->left,q);
        is_q_right=!is_q_left;

        if(is_p_left && is_q_left)
            return lowestCommonAncestor(root->left,p,q);
        else if(is_p_right && is_q_right)
            return lowestCommonAncestor(root->right,p,q);
        else
            return root;
    }

如果找的是自己为公共祖先的话就找不到了,所以要处理一下,在最前面加一个判断

  if(root==p || root==q)
            return root;

也就是测试用例中其实存在一个root的左树或者是右树就是空,所以不加这一行会报错,存在空指针

image-20220426152131356

但是这个时间复杂度看起来太可怕了,理论上说时间复杂度是可以达到O(n2)的等差数列

image-20220426155106549

4.5 刻意求工

上述这样的写法还有值得优化的地方

我们可以用三叉链的思想来把题目简化成O(N)的时间复杂度

算法找出路径保存起来就可以了

我们用一个栈或者vector来保存一下从根节点到该节点之间的路径

image-20220426161101236

然后分别开始出数据,长的出到一样长,直到找到两个节点相等的时候就可以说明是公共的祖先了(图中是数字但是实际栈种放的是节点的指针)

于是极端情况下时间复杂度就是O(N)

4.5.1 FindPath

只要是true说明是对的路径,往下走回找到节点,就放入栈中不要动了,如果返回是false的话我们就pop掉

bool FindPath(TreeNode*root,TreeNode* target,stack<TreeNode*>& path)
{
    //走到空
    if(root==nullptr)
        return false;
    //找到节点
    if(root==target)
    {
        path.push(root);
        return true;
    }

    //搜索
    path.push(root);
    //如果左树中、有找到
    if(FindPath(root->left,target,path))
        return true;
    //如果左树中、有找到
    if(FindPath(root->right,target,path))
        return true;
    //左右子树都没有找到该节点,那么说明root不是路径中的节点就pop掉
    path.pop();
    return false;
}

4.5.2 最终答案

其实我认为不用栈用vector的话会写的更简单一点,vector后面可以直接pop,而且在保证size相等时可以直接resize其中的min(size_p,size_q),可能会简单一点点

    bool FindPath(TreeNode*root,TreeNode* target,stack<TreeNode*>& path)
    {
        //走到空
        if(root==nullptr)
            return false;
        //找到节点
        if(root==target)
        {
            path.push(root);
            return true;
        }

        //继续搜索
        path.push(root);
        //如果左树中、有找到
        if(FindPath(root->left,target,path))
            return true;
        //如果左树中、有找到
        if(FindPath(root->right,target,path))
            return true;
        //左右子树都没有找到该节点,那么说明root不是路径中的节点就pop掉
        path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        
        stack<TreeNode*> p_Path,q_Path;
        FindPath(root,p,p_Path);
        FindPath(root,q,q_Path);
        stack<TreeNode*>* longPath=&p_Path;
        stack<TreeNode*>* shortPath=&q_Path;
        //把两个栈的数据出到一样多
        if(p_Path.size()<q_Path.size())
            swap(longPath,shortPath);
        while(longPath->size()>shortPath->size())
        {
            longPath->pop();
        }
        //两个指针分别往前走,直到两个top数据相等
        while(longPath->top()!=shortPath->top())
        {
            longPath->pop();
            shortPath->pop();
        }
        return longPath->top();
    }

image-20220426164354576

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

题目链接:JZ36 二叉搜索树与双向链表

5.1 题目描述

image-20220427152849049

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/

5.2 顺藤摸瓜

看到排序链表和搜索二叉树我们就能想到这个二叉树要用中序遍历来去做

如何调整转换二叉树到循环链表,也就是让左指针变成prev指针,让右指针转为next

如何去链接prev和next就是这道题目的关键点

父亲找到孩子的指针是好弄的,但是孩子连到父亲的就比较困难,我们不得不借助两个指针来做

5.3 抽丝剥茧

5.3.1 思路

image-20220427194350874

在这样的一棵树中,我们用中序先找得到的是1节点,那么我们可以先让prev指向空,然后cur指向1,在我走中序的过程中的时候就不断按照顺序移动cur和prev

image-20220427194545131

先让prev等于cur,然后cur往下走,这时让prev指向节点的右指针指向cur节点,这样就形成了一个双向节点

image-20220427195018488

注意也不是所有的节点确定指针都一成不变,这里的6就是把左指针改变指向到了5

5.3.2 修改prev指针

还有一个注意点就是传参要传引用,因为我们的希望是整个递归的过程中全局最好只有一个prev在变化,而不是每次传的实参变成了下一层递归的实参,使得每一个prev都是不一样的

如果没有传引用的话可能会出现这样的问题,由于我递归调用下一层的prev确实是修改了值,但是上一层的prev因为没有传参主动修改所以不会改变

image-20220427203826099

5.4.3 修改next指针

cur知道他的上一个是prev,prev知道他的下一个是cur,所以也要把prev的右指针指向cur,注意此时prev的指针可能是空的,要加一个判断

5.4.4 返回值

返回值也要注意,因为要求返回的是左子树的最左节点所以说,我们得找到它,那么思考发现当前的pRootOfTree是指向根的,我们只需要不断地找它的left,找到底就是左子树的最左节点

5.4 手到拈来

    void InOrder(TreeNode* cur,TreeNode*& prev)
    {
        if(cur==nullptr)
            return;
        //left tree
        InOrder(cur->left,prev);
        //cur
        //当前的节点的左指向前一个节点
        cur->left=prev;
        //前一个节点的右指向cur        
        if(prev)
            prev->right=cur;
        //往下移动cur指针
        prev=cur;
        //right tree
        InOrder(cur->right,prev);
    }
    
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(pRootOfTree==nullptr)
            return nullptr;            
        TreeNode*prev=nullptr;
        InOrder(pRootOfTree,prev);
        TreeNode*head=pRootOfTree;
        //返回的是左子树嘴左节点,当前pRootOfTree指向了根节点
        //当然这里的值有可能传入的pRoot可能是空树
        while(head->left)
        {
            head=head->left;
        }
        return head;
    }

image-20220427210128075

5.5 刻意求工

如果题目要求把左指针变成next指针,反之右指针变成prev怎么办

那我反过来就好了吗?

image-20220427210955307

好像没这么简单,这样的话就会影响递归逻辑了

image-20220427211423861

如果把右指针改了指向了prev,那么就不好往右子树去递归了

最简单的思路其实转换完之后逆置一下,双向链表还是很好逆置的,prev就指向了尾,尾也比较好找

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言之命至9012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值