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

LeetCode 513. 找树左下角的值

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

迭代法

思路:

迭代法找树的左下角的值十分简单,目标是要找到二叉树的最底层 最左边 节点的值,可以直接套用层序遍历二叉树的模板,每次遍历一层之前,用一个指针把该层第一个节点保存起来,遍历完该层后检测队列是否为空,如果为空说明该层为二叉树的最底层,直接返回之前保存的节点的值即可。

代码:

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> myQueue;
        myQueue.push(root);
        while (!myQueue.empty())
        {
            int size = myQueue.size();
            TreeNode *first = myQueue.front();
            for (int i = 0; i < size; i++)
            {
                TreeNode* node = myQueue.front();
                myQueue.pop();
                if (node->left)
                    myQueue.push(node->left);
                if (node->right)
                    myQueue.push(node->right);
            }
            if (myQueue.empty())
                return first->val;
        }
        return 0;
    }
};

递归法

思路:

用递归的做法稍难,难点就在于如何确定找到的叶子节点为最深的节点。大致思路还是用回溯的方法,自顶而下,记录所到达的最低深度,然后优先处理左边的节点,也就是说在递归的时候,优先把左边的节点放入参数,然后更新所到的最大深度,这样每到更深的一层,所记录的值都是最左边节点的值,直到一直达到树的最低层,保存的结果就是最底层最左边的值了。

代码:

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        int val = 0;
        int maxDepth = 0;
        dfs(root, 1, val, maxDepth);
        return val;
    }
    void dfs(TreeNode* root, int depth, int &val, int &maxDepth)
    {
        if (root == nullptr)
            return;
        dfs(root->left, depth + 1, val, maxDepth);
        dfs(root->right, depth + 1, val, maxDepth);
        if (depth > maxDepth)
        {
            maxDepth = depth;
            val = root->val;
        }
    }
};

LeetCode 112. 路径总和

链接:112. 路径总和

思路:

题目要求找到所有从根节点到叶子节点的路径上节点的值等于目标值的路径,注意必须是到叶子节点,到其他节点的路径不行。一看到这种类似找到所有路径,所有排列之类的题,第一反应应该是回溯,当找完一条路或一种排列后,通过回溯的方法寻找下一条路或下一种排列。

明确了用回溯之后,这道题的解法思路就变得十分简单。在递归查找路径的函数里,首先明确返回条件,返回条件有两个:

  1. 已经找到了一条路径:因为这道题目只需要找一条路径,找到了的话直接返回就可以了
  2. 当前路径为符合条件的路径:通过sum==targetsum并且当前节点为叶子节点这几个条件可以判断出来当前路径是否符合条件,如果符合条件则令found=true并返回

然后需要实现单层递归的逻辑,按照回溯算法的模板,在这里每一条可选择的路径就是递归-》回溯这样一个过程,在每一个节点都有left、right两种选择,当我们选择了其中一条路后,sum加上我们选择的路径节点上的值,同样回溯的时候也需要从sum中减去这个节点上的值。

代码:

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr)
            return false;
        bool found = false;
        dfs(root, targetSum, found, root->val);
        return found;
    }
    void dfs(TreeNode* root, int targetSum, bool &found, int sum)
    {
        if (found == true)
            return;
        
        if (sum == targetSum && root->left == nullptr && root->right == nullptr)
        {
            found = true;
            return;
        }
        if (root->left)
        {
            sum += root->left->val;
            dfs(root->left, targetSum, found, sum);
            // 回溯
            sum -= root->left->val;
        }
            
        if (root->right)
        {
            sum += root->right->val;
            dfs(root->right, targetSum, found, sum);
            // 回溯
            sum -= root->right->val;
        }
    }
};

附:LeetCode 113. 路径总和 II

链接:113. 路径总和 II

思路:

和上一题极其相似所以放在了一起,唯一不同的地方就是这道题需要找到所有的路径,办法还是用回溯,主题代码基本上一模一样,只有两点需要改:

  1. 返回的条件取消了found找到后返回,因为这里需要找所有的路径而不只是找一条路径
  2. 每选择一个左节点或右节点后将当前节点push到path里,回溯的时候也需要把path记录的当前路径pop出来

代码:

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>> ans;
        vector<int> path;
        if (root == nullptr)
            return ans;
        path.push_back(root->val);
        dfs(root, targetSum, root->val, ans, path);
        return ans;
    }
    void dfs(TreeNode* root, int targetSum, int sum, vector<vector<int>> &ans, vector<int> &path)
    {
        if (sum == targetSum && root->left == nullptr && root->right == nullptr)
        {
            ans.push_back(path);
            return;
        }
        if (root->left)
        {
            sum += root->left->val;
            path.push_back(root->left->val);
            dfs(root->left, targetSum, sum, ans, path);
            // 回溯
            sum -= root->left->val;
            path.pop_back();
        }
            
        if (root->right)
        {
            sum += root->right->val;
            path.push_back(root->right->val);
            dfs(root->right, targetSum, sum, ans, path);
            // 回溯
            sum -= root->right->val;
            path.pop_back();
        }
    }
};

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

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

思路:

今天最难的题目了,理解它花了很长的时间,最重要的一点是要理解中序数组和后序数组是怎么切割的。根据中序序列和后序序列构造二叉树,需要利用这两个序列的特性。

  • 中序序列:左->中->右
  • 后序序列:左->右->中

我们需要根据这两个序列找到二叉树的根节点,然后用递归的方法一步步补充它的子节点。后序序列的最后一个节点可以确认中间节点,那么怎么通过中间节点分割中序序列和后序序列呢?首先中序序列的顺序为左->中->右,那么只要以后序序列找到的中间节点为分割点,就可以立刻把中序序列分为左、右两个子序列,成为左中序序列右中序序列

同样,我们也需要把后序序列分割成左后序序列右后序序列。但是后序序列不能这样分,因为后序序列里面,左节点和右节点是挨在一起的,不能通过中间节点来分割,但是我们可以借助中序序列,因为它告诉了我们左中序序列是哪些,左中序序列的长度和左后序序列的长度是一致的,所以我们也可以很快得到左后序序列和右后序序列。补充左节点需要用到左中序序列左后序序列,补充右节点需要用到左后序序列右后序序列

最后还有几点细节需要注意。首先是区间的闭合问题,开区间和闭区间需要统一,关系到在中序序列找中间节点的时候索引的取值问题。其次是返回条件,后序序列长度小于1则说明是空节点,等于1则说明是叶子节点,可直接返回,最后用root->left或root->right接住它。

代码:

class Solution {
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        TreeNode* root = traversal(inorder, postorder, 0, inorder.size() - 1, 0, postorder.size() - 1);
        return root;
    }
    TreeNode* traversal(vector<int>& inorder, vector<int>& postorder, int inorderBegin, int inorderEnd,
                        int postorderBegin, int postorderEnd)
    {
        // inorder: 左->中->右
        // postorder: 左->右->中
        // 如果postorder数组大小为0,则返回空节点
        if (postorderBegin > postorderEnd)
            return nullptr;
        // 通过postorder的最后一个元素找到中间节点
        TreeNode* root = new TreeNode(postorder[postorderEnd]);
        if (postorderBegin == postorderEnd)
            return root;
        // 左中序数组
        int leftInorderBegin, leftInorderEnd;
        leftInorderBegin = inorderBegin; // 左中序数组的左指针等于中序数组的第一个指针
        // 右中序数组
        int rightInorderBegin, rightInorderEnd;
        rightInorderEnd = inorderEnd; // 右中序数组的右指针等于中序数组的最后一个指针
        for (int i = inorderBegin; i <= inorderEnd; i++) // 注意是闭区间所以可以取等号
        {
            // 在中序数找到中间节点
            // 找到之后,切割数组
            if (root->val == inorder[i])
            {
                
                leftInorderEnd = i - 1; //左中序数组的右指针等于切割点左边一位
                rightInorderBegin = i + 1; //右中序数组的左指针等于切割点右边一位
                break;
            }
        }
        // 左后序数组
        int leftPostorderBegin, leftPostorderEnd;
        leftPostorderBegin = postorderBegin; // 左后序数组的左指针等于后序数组的第一个指针
        // 右后序数组
        int rightPostorderBegin, rightPostorderEnd;
        rightPostorderEnd = postorderEnd - 1;  // 右后序数组的右指针等于后序数组的最后一个指针减一,排除最后一个元素
        leftPostorderEnd = leftPostorderBegin + (leftInorderEnd - leftInorderBegin); //左后序数组的右指针等于左指针加左前序数组的长度(因为长度一致)
        rightPostorderBegin = leftPostorderEnd + 1; //右后序数组的左指针等于左后序数组的右指针加1
        
        root->left = traversal(inorder, postorder, leftInorderBegin, leftInorderEnd, 
        leftPostorderBegin, leftPostorderEnd);
        root->right = traversal(inorder, postorder, rightInorderBegin, rightInorderEnd, 
        rightPostorderBegin, rightPostorderEnd);
        return root;
    }
};

附:LeetCode 105. 从前序与中序遍历序列构造二叉树

链接:105. 从前序与中序遍历序列构造二叉树

思路:

和从前序与中序遍历序列构造二叉树的思路是完全一样的,唯一的区别是要注意在前序序列中的中间节点是第一个节点,所以要用第一个节点去切割。

代码:

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        TreeNode* root = traversal(inorder, preorder, 0, inorder.size() - 1, 0, preorder.size() - 1);
        return root;
    }
    TreeNode* traversal(vector<int>& inorder, vector<int>& preorder, int inorderBegin, int inorderEnd,
                        int preorderBegin, int preorderEnd)
    {
        // inorder: 左->中->右
        // preorder: 中->左->右
        // 如果preorder数组大小为0,则返回空节点
        if (preorderBegin > preorderEnd)
            return nullptr;
        // 通过preorder的第一个元素找到中间节点
        TreeNode* root = new TreeNode(preorder[preorderBegin]);
        if (preorderBegin == preorderEnd)
            return root;
        // 左中序数组
        int leftInorderBegin, leftInorderEnd;
        leftInorderBegin = inorderBegin; // 左中序数组的左指针等于中序数组的第一个指针
        // 右中序数组
        int rightInorderBegin, rightInorderEnd;
        rightInorderEnd = inorderEnd; // 右中序数组的右指针等于中序数组的最后一个指针
        for (int i = inorderBegin; i <= inorderEnd; i++) // 注意是闭区间所以可以取等号
        {
            // 在中序数找到中间节点
            // 找到之后,切割数组
            if (root->val == inorder[i])
            {
                leftInorderEnd = i - 1; //左中序数组的右指针等于切割点左边一位
                rightInorderBegin = i + 1; //右中序数组的左指针等于切割点右边一位
                break;
            }
        }
        // 左前序数组
        int leftPreorderBegin, leftPreorderEnd;
        leftPreorderBegin = preorderBegin + 1; // 左前序数组的左指针等于前序数组的第一个指针加1,排除第一个元素
        // 右前序数组
        int rightPreorderBegin, rightPreorderEnd;
        rightPreorderEnd = preorderEnd;  // 右前序序数组的右指针等于后序数组的最后一个指针
        leftPreorderEnd = leftPreorderBegin + (leftInorderEnd - leftInorderBegin); //左后序数组的右指针等于左指针加左前序数组的长度(因为长度一致)
        rightPreorderBegin = leftPreorderEnd + 1; //右后序数组的左指针等于左后序数组的右指针加1
        
        root->left = traversal(inorder, preorder, leftInorderBegin, leftInorderEnd, 
        leftPreorderBegin, leftPreorderEnd);
        root->right = traversal(inorder, preorder, rightInorderBegin, rightInorderEnd, 
        rightPreorderBegin, rightPreorderEnd);
        return root;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值