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

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

1 LeetCode 513.找树左下角的值

题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入: root = [2,1,3]
输出: 1

示例 2:

img

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1

题目要求我们找到二叉树的最底层最左边节点的值,并不是单纯的一路向左遍历就行,最底层最左边节点的值不一定就是左子树的底层最左边的值,而是这棵二叉树最大深度的这一层的最左边的值,也就是我们需要求二叉树的最大深度(这一实现思路我们在前面有刷到过题目),我们可以定义一个深度优先搜索函数,跟踪当前节点的深度,当我们到达一个叶子节点时,我们就能知道其深度,并与当前记录的最大深度进行比较.

如何更新最深层最左侧的值?

  • 当遍历到一个叶子节点时,检查当前深度是否大于之前记录的最大深度。
  • 如果是更新最大深度,并记录这个叶子节点的值作为当前最深层最左侧的值,因为DFS默认会优先深入左子树,这保证了如果在同一深度的不同节点中有多个候选,我们总是优先选择最左侧的节点。(前中后序遍历都可以,因为我们不会去处理中间节点)

实现DFS时,从根节点开始遍历,并且为每个节点传递当前的深度信息,每当向下遍历到树的下一层时,深度加1(这里也就包含了回溯)。

  • 如果当前节点是叶子节点,比较并可能更新最大深度和最深层最左侧值。
  • 如果节点有左子节点,优先遍历左子节点,然后遍历右子节点。

下面我们一起写出对应实现代码。

(1)Python版本代码

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
# Definition for a binary tree node.
class Solution:
    def findBottomLeftValue(self, root):
        self.maxDepth = float('-inf')   # 用来记录最大深度,初始化为负无穷大
        self.result = None  # 用来存储最左侧节点的值
        self.dfs(root, 0)  # 从根节点开始深度优先搜索,初始深度为0
        return self.result  # 返回最底层最左侧的值
    
    def dfs(self, node, depth):
        # 如果节点是叶子节点
        if not node.left and not node.right:
            # 如果当前深度大于记录的最大深度
            if depth > self.maxDepth:
                self.maxDepth = depth  # 更新最大深度为当前深度
                self.result = node.val  # 更新结果为当前节点的值
            return
        # 如果存在左子节点,递归调用dfs函数,深度加1
        if node.left:
            self.dfs(node.left, depth + 1)
        # 如果存在右子节点,也递归调用dfs函数,深度加1
        if node.right:
            self.dfs(node.right, depth + 1)

(2)C++版本代码

#include <iostream>
using namespace std;
/**
 * 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 {
private:
    int maxDepth = INT_MIN;  // 记录最大深度,初始化为最小整数
    int result;  // 存储最左侧节点的值
    void dfs(TreeNode* node, int depth) {
        // 如果是叶子节点
        if (!node->left && !node->right) {
            // 如果当前深度大于记录的最大深度
            if (depth > maxDepth) {
                maxDepth = depth;  // 更新最大深度为当前深度
                result = node->val;  // 更新结果为当前节点的值
            }
            return;
        }
        // 如果存在左子节点,递归调用dfs函数,深度加1
        if (node->left) dfs(node->left, depth + 1);
        // 如果存在右子节点,也递归调用dfs函数,深度加1
        if (node->right) dfs(node->right, depth + 1);
    }

public:
    int findBottomLeftValue(TreeNode* root) {
        dfs(root, 0);  // 从根节点开始深度优先搜索,初始深度为0
        return result;  // 返回最底层最左侧的值
    }
};

2 LeetCode 112. 路径总和

题目链接:https://leetcode.cn/problems/path-sum/description/

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

img

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

  • 树中节点的数目在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

在本题中我们也需要用到回溯算法,通过深度优先搜索算法一直沿着一条路径走下去,直到遇见叶子节点,判断是否符合题意,如果不符合就需要向上返回False,如果符合就返回True,这里如何判断是否符合,我们就可以通过不断地利用目标值减去路径上的值,直到遇见叶子节点且刚好减为0就是符合条件的路径,大致思路就是这样。(本题并没有用到前中后序遍历,因为我们不需要对中节点进行处理)

思路知道之后我们就可以按照思路写出对应的代码。

(1)Python版本代码

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        # 如果根节点为空,则不存在这样的路径
        if not root:
            return False
        # 从根节点开始深度优先搜索(DFS),减去根节点的值,继续向下搜索
        return self.dfs(root, targetSum - root.val)
    def dfs(self, node, count):
        # 如果当前节点是叶子节点(没有子节点)且累计值等于0,则找到了一条符合条件的路径
        if not node.left and not node.right and count == 0:
            return True
        # 如果当前节点是叶子节点但累计值不为0,则这条路径不符合条件
        if not node.left and not node.right:
            return False
        # 如果存在左子节点,递归地调用dfs函数,继续搜索左子树,减去左子节点的值
        if node.left:
            if self.dfs(node.left, count - node.left.val):
                return True  # 如果找到一条符合条件的路径,则返回True
        # 如果存在右子节点,同样递归地调用dfs函数,继续搜索右子树,减去右子节点的值
        if node.right:
            if self.dfs(node.right, count - node.right.val):
                return True  # 如果找到一条符合条件的路径,则返回True
        # 如果左右子树都没有找到符合条件的路径,则返回False
        return False

(2)C++版本代码

#include <iostream>
/**
 * 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 hasPathSum(TreeNode* root, int targetSum) {
        // 如果根节点为空,则不存在这样的路径
        if (!root) {
            return false;
        }
        // 从根节点开始深度优先搜索(DFS),减去根节点的值,继续向下搜索
        return dfs(root, targetSum - root->val);
    }

private:
    bool dfs(TreeNode* node, int count) {
        // 如果当前节点是叶子节点(没有子节点)且累计值等于0,则找到了一条符合条件的路径
        if (!node->left && !node->right && count == 0) {
            return true;
        }
        // 如果存在左子节点,递归地调用dfs函数,继续搜索左子树,减去左子节点的值
        if (node->left) {
            if (dfs(node->left, count - node->left->val)) {
                return true;  // 如果找到一条符合条件的路径,则返回true
            }
        }
        // 如果存在右子节点,同样递归地调用dfs函数,继续搜索右子树,减去右子节点的值
        if (node->right) {
            if (dfs(node->right, count - node->right->val)) {
                return true;  // 如果找到一条符合条件的路径,则返回true
            }
        }
        // 如果左右子树都没有找到符合条件的路径,则返回false
        return false;
    }
};

3 LeetCode 113. 路径总和 II

题目链接:https://leetcode.cn/problems/path-sum-ii/description/

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

img

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

  • 树中节点总数在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

本题我们可以在上一题的基础上进行修改,在本题中我们还需要将符合条件的路径保存下来而不是直接返回退出递归,也涉及到回溯过程。

下面我们来写出对应的代码。

(1)Python版本代码

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        self.result = []  # 用来存储所有符合条件的路径
        if not root:
            return self.result
        # 从根节点开始DFS,路径初始包含根节点的值
        self.dfs(root, [root.val], targetSum - root.val)
        return self.result
    
    def dfs(self, node, path, count):
        # 如果是叶子节点且当前路径总和等于目标值,则将当前路径添加到结果列表中
        if not node.left and not node.right and count == 0:
            self.result.append(list(path))  # 注意深拷贝当前路径
            return
        # 遍历左子节点
        if node.left:
            path.append(node.left.val)  # 将左子节点的值加入当前路径
            self.dfs(node.left, path, count - node.left.val)  # 更新剩余目标和,继续DFS
            path.pop()  # 回溯,移除最后一个节点的值
        # 遍历右子节点
        if node.right:
            path.append(node.right.val)  # 将右子节点的值加入当前路径
            self.dfs(node.right, path, count - node.right.val)  # 更新剩余目标和,继续DFS
            path.pop()  # 回溯,移除最后一个节点的值

需要注意的是,对于每一个节点,检查是否达到了叶子节点且当前路径的总和等于目标值,如果找到了一条符合条件的路径,我们需要将这条路径的深拷贝添加到结果列表中,使用深拷贝是因为路径列表在递归过程中是会变的,直接添加路径列表的引用会导致最终所有保存的路径都是递归结束时路径列表的状态。

对于回溯操作,每次递归返回前,将当前节点从路径列表中移除(实际操作是在进入子节点探索前添加节点值,探索结束后移除),以保证回溯到上一层递归时,路径列表反映的是正确的路径状态。

(2)C++版本代码

#include <vector>
using namespace std;
/**
 * 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:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        vector<vector<int>> result;  // 存储所有符合条件的路径
        vector<int> path;  // 当前探索的路径
        dfs(root, sum, path, result);
        return result;
    }
    
private:
    void dfs(TreeNode* node, int sum, vector<int>& path, vector<vector<int>>& result) {
        if (!node) return;  // 如果节点为空,则直接返回
        path.push_back(node->val);  // 将当前节点值加入路径
        // 检查是否达到叶子节点且路径总和等于目标值
        if (!node->left && !node->right && node->val == sum) {
            result.push_back(path);  // 将当前路径加入结果列表
        } else {
            // 继续递归探索左右子树
            dfs(node->left, sum - node->val, path, result);
            dfs(node->right, sum - node->val, path, result);
        }
        path.pop_back();  // 回溯,移除当前节点值,以探索其他路径
    }
};

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

题目链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例 1:

img

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorderpostorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

这种题目虽然在408算法题中还未出现,但是选择题考察频率想当的大,也可能是未来408在二叉树这一考点的命题。

先抛开代码实现,我们先学习在理论上我们应该如何去实现,如何通过中序遍历顺序和后序遍历顺序唯一的确定一棵二叉树,对于我们肯定要从后序入手,因为后序是左右中的遍历顺序,那么最后一个节点一定是根节点,然后我们确定根节点之后再去中序遍历顺序中找根节点的位置,来确定左右子树的节点有哪些,也就是对其进行分割,然后我们再递归的对左右子树进行上述操作即可,对于空节点我们插入null即可。

需要特别注意的是,本题操作其实也就是在数组上面进行,因此我们还有需要注意边界问题,在确定根节点之后我们切割出来的左右子树区间需要保持同一要求(比如左闭右开)。

理论清楚,现在我们开始写对应的代码。

(1)Python版本代码

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        # 用于根据节点值快速定位该节点在中序遍历中的位置
        index_map = {val: idx for idx, val in enumerate(inorder)}

        def build(left, right):
            # 如果没有元素可以构造二叉树,则返回None
            if left > right:
                return None
            # 后序遍历的最后一个元素是当前子树的根节点
            root_val = postorder.pop()
            root = TreeNode(root_val)
            # 在中序遍历中定位根节点的位置,从而划分左右子树
            index = index_map[root_val]
            # 先构造右子树,再构造左子树,因为后序遍历是左右中的顺序,
            # 在数组的最后弹出元素时,先弹出的是右子树的根节点
            root.right = build(index + 1, right)
            root.left = build(left, index - 1)
            return root
        # 从整个后序遍历的范围开始构造二叉树
        return build(0, len(inorder) - 1)

(2)C++版本代码

#include <vector>
#include <unordered_map>
using namespace std;
/**
 * 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:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 创建节点值到中序遍历索引的映射
        unordered_map<int, int> index_map;
        for (int i = 0; i < inorder.size(); ++i) {
            index_map[inorder[i]] = i;
        }
        // 调用递归函数构建二叉树
        return build(index_map, inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1);
    }

private:
    TreeNode* build(unordered_map<int, int>& index_map, vector<int>& inorder, int inStart, int inEnd, 
                    vector<int>& postorder, int postStart, int postEnd) {
        // 递归终止条件
        if (postStart > postEnd) {
            return nullptr;
        }
        // 后序遍历的最后一个元素是根节点
        int rootVal = postorder[postEnd];
        TreeNode* root = new TreeNode(rootVal);
        // 在中序遍历中找到根节点的索引,从而确定左右子树的范围
        int index = index_map[rootVal];
        // 构建左子树
        root->left = build(index_map, inorder, inStart, index - 1, postorder, postStart, postStart + index - inStart - 1);
        // 构建右子树
        root->right = build(index_map, inorder, index + 1, inEnd, postorder, postStart + index - inStart, postEnd - 1);
        return root;
    }
};

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

题目链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

img

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorderinorder无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

本题和上一题类似,也就是先通过前序遍历确立根节点位置然后后续操作类似。

(1)Python版本代码

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder or not inorder:
            return None
        # 创建节点值到中序遍历索引的映射
        index_map = {val: idx for idx, val in enumerate(inorder)}
        # 递归构建二叉树
        def build(preStart, preEnd, inStart, inEnd):
            if preStart > preEnd or inStart > inEnd:
                return None
            # 前序遍历的第一个元素是根节点
            rootVal = preorder[preStart]
            root = TreeNode(rootVal)
            # 在中序遍历中找到根节点的索引
            index = index_map[rootVal]
            # 左子树的节点数
            leftSize = index - inStart 
            # 构建左子树和右子树
            root.left = build(preStart + 1, preStart + leftSize, inStart, index - 1)
            root.right = build(preStart + leftSize + 1, preEnd, index + 1, inEnd)
            return root 
        return build(0, len(preorder) - 1, 0, len(inorder) - 1)

(2)C++版本代码

#include <vector>
#include <unordered_map>
using namespace std;
/**
 * 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:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        unordered_map<int, int> index_map;
        for (int i = 0; i < inorder.size(); ++i) {
            index_map[inorder[i]] = i;
        }      
        return build(index_map, preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);
    }
    
private:
    TreeNode* build(unordered_map<int, int>& index_map, vector<int>& preorder, int preStart, int preEnd, 
                    vector<int>& inorder, int inStart, int inEnd) {
        if (preStart > preEnd || inStart > inEnd) {
            return nullptr;
        }    
        int rootVal = preorder[preStart];
        TreeNode* root = new TreeNode(rootVal);    
        int index = index_map[rootVal];
        int leftSize = index - inStart;     
        root->left = build(index_map, preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1);
        root->right = build(index_map, preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd);     
        return root;
    }
};
  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-北天-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值