代码随想录算法训练营第十六天| 513.找树左下角的值 112. 路径总和 106.从中序与后序遍历序列构造二叉树


一、LeetCode 513.找树左下角的值

题目链接:LeetCode 513.找树左下角的值

文章讲解:代码随想录
视频讲解:怎么找二叉树的左下角? 递归中又带回溯了,怎么办?| LeetCode:513.找二叉树左下角的值

思路:

 注意审题,题目中要求的顺序是先最底层,再最左边,因此是找二叉树最后一层的最左边结点;那么只需要按照层次遍历,遍历到最后一层,记录最后一层最左边的值即可。

C++代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que; //层次遍历
        TreeNode* prel_left;
        if(root){
            que.push(root);
        }
        while(!que.empty()){
            int size = que.size();
            prel_left = que.front(); //记录最左边的值
            for(int i = 0; i < size; i++){
                TreeNode* node = que.front();
                que.pop();
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
        }
        return prel_left->val;
    }
};

二、LeetCode 112. 路径总和

题目链接:LeetCode 112. 路径总和

文章讲解:代码随想录
视频讲解:拿不准的遍历顺序,搞不清的回溯过程,我太难了! | LeetCode:112. 路径总和

思路

 题目要求,在树中所有路径中,找出一条路径,使得路径中所有节点值之和等于一个给定值;类似这样在解空间树中寻找一个解的问题可以使用回溯法/分支限界法来进行求解。

 具体函数设计为传递参数为当前结点、targetSum值,以及当前结点值总和sum;每层判断当前结点加上以后是否等于给定值,用bool值返回是否存在这样一个解。

C++代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    bool backtrack(TreeNode* node, int targetSum, int sum) { //回溯算法
        if (!node) {
            return false;
        } else if (!node->left && !node->right) {
            if (sum + node->val == targetSum) {
                return true;
            }
            return false;
        }
        //sum不传引用,只传一个参数
        //每层函数函数不修改上一层sum的值,因此达成回溯。
        return backtrack(node->left, targetSum, sum + node->val) ||
               backtrack(node->right, targetSum, sum + node->val);
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        return backtrack(root, targetSum, 0);
    }
};

二、LeetCode 106.从中序与后序遍历序列构造二叉树

题目链接:LeetCode 106.从中序与后序遍历序列构造二叉树

文章讲解:代码随想录
视频讲解:坑很多!来看看你掉过几次坑 | LeetCode:106.从中序与后序遍历序列构造二叉树

思路

 题目开始上难度了。我们一贯的构建二叉树方式为完全二叉树顺序进行广度优先的构建,本题的难点一在于反常识,二在于抽象,需要根据中序遍历和后序遍历两个“平面视图”来推算出该一般二叉树的形状。

 遇到困难先不要着急,我们可以先把一棵二叉树的中序遍历和后序遍历的结点表写出来,观察各有什么特点、与二叉树有什么关系。
在这里插入图片描述
 我们首先可以写出来这一棵二叉树的中序和后序序列:

  • 中序: [ 9 , 3 , 15 , 20 , 7 ] [9, 3, 15, 20, 7] [9,3,15,20,7]
  • 后序: [ 9 , 15 , 7 , 20 , 3 ] [9, 15, 7, 20, 3] [9,15,7,20,3]

 对照二叉树的结构我们可以发现,后序遍历“左右中”的顺序,使得后序序列最后一个元素一定是根结点;而根结点对应到“左中右”顺序的中序序列,可以发现,“3”号结点的左边所有元素是该结点的左子树,右边所有元素是该结点的右子树.

 那么我们就可以根据这两条特性来进行构建,主要思路为:用后序序列来确定当前的结点(根结点),用中序序列来划分左右子树

在这里插入图片描述
 在递归函数的设计上,笔者采用了在递归函数内构建左右孩子的方式;首先按照我们找到的特性,先在中序和后序序列中找到根节点位置:

//在后序序列中寻找当前根节点
auto it = --postorder.end();
//根结点在中序序列中的位置
auto it_in = find(inorder.begin(), inorder.end(), *it);

 这里找根节点位置采用了迭代器( i t e r a t o r iterator iterator),类似于C语言中的指针,主要在C++标准库中的容器类内使用,具体的使用原因我们放在后面讲。

 然后分别构建左右孩子,构建孩子的方法如下:

 左右孩子其实就是左右子树的根节点,我们要在左右子树中找到根节点仍然需要依托后序序列,而确定左右子树则需要先在中序序列中划分。因此需要先在中序序列中找到左右子树,再对应后序序列的位置找到左右子树的根节点,代码如下:

//使用后序序列寻找根结点位置
vector<int>::iterator it_left; //左子树根结点在后序序列中的位置
int dist = it_in - inorder.begin() - 1; //用中序序列求出左子树有多少个元素
it_left = postorder.begin() + dist;//在后序序列中确定位置
TreeNode* left = new TreeNode(*it_left);  
subroot->left = left;//构建左孩子

右孩子构建与左孩子类似。

 构建结束后,我们需要进入下一层继续递归构建。这里采用了代码随想录的方法:将当前函数传入的中序序列和后序序列作切分,都切分成左子树的中、后序和右子树的中、后序,传入下一层继续递归。

 切分vector容器就需要依靠迭代器iterater了,这也是前面使用迭代器确定位置的原因:

vector<int> in_l(inorder.begin(), it_in);//左子树中序序列
vector<int> post_l(postorder.begin(), it_left+1);//左子树后序序列
Subtree(left, in_l, post_l);

 另外注意递归的终止条件,以及左右子树构建的条件,即可编写出完整的程序。完整代码如下:

C++代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    void Subtree(TreeNode* subroot, vector<int> inorder, vector<int> postorder) {
        if(!inorder.empty()){
        	//在后序序列中寻找当前根节点
            auto it = --postorder.end();
            //根结点在中序序列中的位置
            auto it_in = find(inorder.begin(), inorder.end(), *it);

            //生成左子树
            if(it_in != inorder.begin()){
                vector<int>::iterator it_left;
                int dist = it_in - inorder.begin() - 1;
                it_left = postorder.begin() + dist;
                TreeNode* left = new TreeNode(*it_left);  
                subroot->left = left;
                vector<int> in_l(inorder.begin(), it_in);
                vector<int> post_l(postorder.begin(), it_left+1);
                Subtree(left, in_l, post_l);
            }

            //生成右子树
            if(it_in != --inorder.end()){
                auto it_right = it-1;
                TreeNode* right = new TreeNode(*it_right);
                subroot->right = right;
                int dist = inorder.end()-it_in-1;
                vector<int> in_r(it_in+1, inorder.end());
                vector<int> post_r(it-dist, it);
                Subtree(right, in_r, post_r);
            }
        }
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 中序来定左右子树,后序寻找子树的根
        TreeNode* root = new TreeNode(postorder[postorder.size() - 1]);
        Subtree(root, inorder, postorder);
        return root;
    }
};

总结

 仍然是二叉树遍历与递归的应用,中序后序构造二叉树题目较难,需要深入研究各种遍历的特点。在编写递归函数时仍要注意各种逻辑关系与终止条件是否合理。


文章图片来源:代码随想录 (https://programmercarl.com/)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值