代码随想录算法训练营第十五天 | 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:
输入: [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:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 输出:true 解释:等于目标和的根节点到叶节点路径如上图所示。
示例 2:
输入: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:
输入: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/
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1:
输入: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
inorder
和postorder
都由 不同 的值组成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/
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1:
输入: 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
preorder
和inorder
均 无重复 元素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;
}
};