二叉树 part04
题目描述
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
解题思路
递归法思路
大致思路:
通过深度优先搜索(DFS)确保了树的每个节点都被访问,且在访问过程中动态更新最大深度和对应的节点值,直到找到最底层最左边的节点。
递归函数 traversal 说明:
-
函数参数和返回值
-
参数:函数接收两个参数,一个是当前节点的指针
root
,另一个是当前的深度 depth
-
返回值:没有显示的返回值,是通过修改全局变量
maxDepth
和 result
来记录相关信息
-
-
递归函数的终止条件:
- 当 root 指向的节点是叶子节点时,递归终止,此时函数会检查当前深度 depth 是否大于之前记录的最大深度
maxDepth
,如果是,更新 maxDepth
和 result
。
- 当 root 指向的节点是叶子节点时,递归终止,此时函数会检查当前深度 depth 是否大于之前记录的最大深度
-
递归函数的单层逻辑:
- 如果当前节点不是叶子节点,函数会检查是否存在子左节点。如果有,递归调用
traversal
函数,传入左子节点和增加后的深度(depth + 1
) - 如果当前节点存在右子节点,也会递归调用
traversal
函数,传入右子节点和增加后的深度
- 如果当前节点不是叶子节点,函数会检查是否存在子左节点。如果有,递归调用
整体流程:
- 主函数
findBottomLeftValue
从根节点开始调用traversal
函数,初始深度为0。 traversal
函数通过递归遍历树的每个节点,每次递归时深度增加。- 当遇到叶子节点时,检查并更新最大深度和最底层最左边的节点的值。
- 递归完成后,
result
变量中存储的就是最底层最左边的节点的值,由findBottomLeftValue
函数返回。
代码实现
测试地址:https://leetcode.cn/problems/find-bottom-left-tree-value/
递归
class Solution {
public:
// 记录当前找到的最大深度
int maxDepth = INT_MIN;
// 记录最底层最左边的节点的值
int result;
// 递归函数,用于遍历树并找到最底层最左边的节点
void traversal(TreeNode *root, int depth) {
// 如果当前节点是叶子节点
if (root->left == nullptr && root->right == nullptr) {
// 如果当前深度大于之前记录的最大深度
if (depth > maxDepth) {
// 更新最大深度
maxDepth = depth;
// 记录当前节点的值
result = root->val;
}
// 返回,结束当前递归分支
return;
}
// 如果当前节点有左子节点,递归遍历左子树,深度加1
if (root->left) {
traversal(root->left, depth + 1);
}
// 如果当前节点有右子节点,递归遍历右子树,深度加1
if (root->right) {
traversal(root->right, depth + 1);
}
// 返回,结束当前递归分支
return;
}
// 主函数,用于找到并返回最底层最左边的节点的值
int findBottomLeftValue(TreeNode *root) {
// 从根节点开始递归遍历树
traversal(root, 0);
// 返回找到的最底层最左边的节点的值
return result;
}
};
迭代法:
class Solution {
public:
// 函数用于找到二叉树中最底层最左边的节点的值
int findBottomLeftValue(TreeNode *root) {
// 使用队列进行层序遍历
queue<TreeNode *> que;
// 如果根节点不为空,将其加入队列
if (root != nullptr)
que.push(root);
// 初始化结果为0,用于存储最底层最左边的节点的值
int result = 0;
// 当队列不为空时,循环执行以下操作
while (!que.empty()) {
// 获取当前队列的大小,即当前层的节点数
int size = que.size();
// 遍历当前层的所有节点
for (int i = 0; i < size; i++) {
// 取出队列的头部节点
TreeNode *node = que.front();
que.pop();
// 如果是当前层的最左节点(即第一个节点),则记录其值
if (i == 0)
result = node->val;
// 如果当前节点有左子节点,将其加入队列
if (node->left)
que.push(node->left);
// 如果当前节点有右子节点,将其加入队列
if (node->right)
que.push(node->right);
}
}
// 返回找到的最底层最左边的节点的值
return result;
}
};
路径总和
题目描述
给你二叉树的根节点 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
解释:由于树是空的,所以不存在根节点到叶子节点的路径。
解题思路
递归法思路
递归函数说明:
-
递归函数的参数和返回值
- 参数:函数接收两个参数,当前节点的指针
cur
和剩余的和count
- 返回值:函数返回一个bool值,用于判断是否存在一条从当前节点到叶子节点的路径,其路径和等于剩余的和
- 参数:函数接收两个参数,当前节点的指针
-
递归函数的终止条件
- 当
cur
指向的节点是 叶子节点时,递归终止,说明本条路径已到达最低处,判断此时剩余的和 count 是否为0
,如果是的话返回true
,否则返回false
。
- 当
-
递归函数的单层逻辑
- 如果当前节点存在左子节点,减少剩余的和,并递归调用
traversal
函数检查左子树。如果左子树中存在满足条件的路径,直接向上返回true
,否则回溯,恢复剩余的和。 - 如果当前节点存在右子节点,减少剩余的和,并递归调用
traversal
函数检查右子树。如果右子树中存在满足条件的路径,直接向上返回true
,否则回溯,恢复剩余的和。 - 如果左右字数都没有合适的路径,返回
false
- 如果当前节点存在左子节点,减少剩余的和,并递归调用
整体流程:
- 主函数
hasPathSum
从根节点开始调用traversal
函数,初始剩余和为targetSum
减去根节点的值。 traversal
函数通过递归遍历树的每个节点,每次递归时更新剩余的和。- 当遇到叶子节点时,检查剩余的和是否为0,以确定是否存在满足条件的路径。
- 递归完成后,返回结果,表示是否存在这样的路径。
迭代法思路
核心操作说明:
当栈不为空时,执行以下操作:
- 弹出栈顶元素,记为
node
,其中node.first
是节点,node.second
是到该节点的路径和。 - 如果
node.first
是叶子节点(即没有左右子节点)且路径和等于sum
,则返回true
。 - 如果
node.first
有右子节点,将右子节点及其新的路径和(当前路径和加上右子节点的值)压入栈中。 - 如果
node.first
有左子节点,将左子节点及其新的路径和压入栈中。
代码实现
测试地址:https://leetcode.cn/problems/path-sum/
递归法:
class Solution {
private:
// 递归函数,用于检查是否存在从当前节点到叶子节点的路径,其路径和等于给定的目标和
bool traversal(TreeNode *cur, int count) {
// 如果当前节点是叶子节点,且剩余的和为0,返回true
if (!cur->left && !cur->right && count == 0)
return true;
// 如果当前节点是叶子节点,但剩余的和不等于0,返回false
if (!cur->left && !cur->right)
return false;
// 如果当前节点有左子节点
if (cur->left) {
// 减少剩余的和,递归检查左子树
count -= cur->left->val;
if (traversal(cur->left, count))
return true;
// 回溯,恢复剩余的和
count += cur->left->val;
}
// 如果当前节点有右子节点
if (cur->right) {
// 减少剩余的和,递归检查右子树
count -= cur->right->val;
if (traversal(cur->right, count))
return true;
// 回溯,恢复剩余的和
count += cur->right->val;
}
// 如果左右子树都没有找到合适的路径,返回false
return false;
}
public:
// 主函数,用于检查从根节点到叶子节点是否存在一条路径,其路径和等于targetSum
bool hasPathSum(TreeNode *root, int targetSum) {
// 如果根节点为空,直接返回false
if (root == nullptr)
return false;
// 从根节点开始递归检查,初始剩余和为targetSum减去根节点的值
return traversal(root, targetSum - root->val);
}
};
迭代法:
class Solution {
public:
// 检查是否存在从根节点到叶子节点的路径,其路径和等于给定的sum
bool hasPathSum(TreeNode *root, int sum) {
// 如果根节点为空,则不存在路径,返回false
if (root == nullptr)
return false;
// 创建一个栈,用于存储节点及其当前路径和的pair
stack<pair<TreeNode *, int>> st;
// 将根节点及其值压入栈中
st.push(pair<TreeNode *, int>(root, root->val));
// 当栈不为空时,继续遍历
while (!st.empty()) {
// 弹出栈顶元素
pair<TreeNode *, int> node = st.top();
st.pop();
// 如果当前节点是叶子节点且路径和等于sum,返回true
if (!node.first->left && !node.first->right && sum == node.second)
return true;
// 如果当前节点有右子节点,将右子节点及其新的路径和压入栈中
if (node.first->right) {
st.push(pair<TreeNode *, int>(node.first->right, node.second + node.first->right->val));
}
// 如果当前节点有左子节点,将左子节点及其新的路径和压入栈中
if (node.first->left) {
st.push(pair<TreeNode *, int>(node.first->left, node.second + node.first->left->val));
}
}
// 如果遍历完所有可能的路径后都没有找到满足条件的路径,返回false
return false;
}
};
路径总和 II
题目描述
给你二叉树的根节点 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
输出:[]
解题思路
核心思路:
- 递归函数的参数和返回值:函数接受当前节点
cur
和剩余路径和count
作为参数,无返回值。 - 递归函数的终止条件:当遇到叶子节点且剩余路径和为0时,将当前路径添加到结果中。如果遇到叶子节点但剩余路径和不为0,则直接返回。
- 递归函数的单层逻辑:对于每个节点,首先尝试遍历其左子树,更新路径和剩余路径和,然后递归调用左子树。之后进行回溯,恢复路径和。接着尝试遍历右子树,同样更新路径和剩余路径和,递归调用右子树,然后进行回溯。这样,每个节点都会尝试其左右子树,直到找到所有满足条件的路径。
代码实现
测试地址:https://leetcode.cn/problems/path-sum-ii/
class Solution {
private:
// 存储所有满足条件的路径
vector<vector<int>> result;
// 存储当前路径
vector<int> path;
// 递归函数:遍历树并寻找满足条件的路径
void traversal(TreeNode *cur, int count) {
// 终止条件:如果当前节点是叶子节点且剩余路径和为0,则将当前路径添加到结果中
if (!cur->left && !cur->right && count == 0) {
result.push_back(path);
return;
}
// 如果当前节点是叶子节点但剩余路径和不等于0,则返回,不添加路径
if (!cur->left && !cur->right)
return;
// 单层逻辑:遍历左子树
if (cur->left) {
// 添加左子节点的值到当前路径,并更新剩余路径和
path.push_back(cur->left->val);
count -= cur->left->val;
// 递归调用左子树
traversal(cur->left, count);
// 回溯:恢复路径和
count += cur->left->val;
path.pop_back();
}
// 单层逻辑:遍历右子树
if (cur->right) {
// 添加右子节点的值到当前路径,并更新剩余路径和
path.push_back(cur->right->val);
count -= cur->right->val;
// 递归调用右子树
traversal(cur->right, count);
// 回溯:恢复路径和
count += cur->right->val;
path.pop_back();
}
// 返回,结束当前节点的处理
return;
}
public:
// 主函数:找到所有从根到叶子的路径,其路径和等于targetSum
vector<vector<int>> pathSum(TreeNode *root, int targetSum) {
// 如果根节点为空,返回空结果
if (root == nullptr)
return result;
// 添加根节点的值到路径,并更新剩余路径和
path.push_back(root->val);
// 从根节点开始递归遍历
traversal(root, targetSum - root->val);
// 返回所有找到的路径
return result;
}
};
从中序与后序遍历序列构造二叉树
题目描述
给定两个整数数组 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]
解题思路
整体逻辑: 利用中序和后序遍历的特性(中序遍历可以确定左右子树的元素分布,后序遍历可以确定根节点),逐步构建出整个二叉树。
递归函数说明:
- 递归函数的参数和返回值:
traversal
函数接受中序和后序遍历数组的起始和结束索引,以及数组本身。返回值是构建的子树的根节点。 - 递归函数的终止条件:如果后序遍历数组为空(即起始索引等于结束索引),则返回
NULL
。如果后序数组只有一个元素,直接返回根节点。 - 递归函数的单层逻辑:首先确定当前子树的根节点(后序遍历的最后一个元素),然后在中序遍历中找到根节点的位置,以此分割中序和后序数组为左右两部分。接着递归地构建左子树和右子树,并将它们分别设置为当前根节点的左右子节点。最后返回当前子树的根节点。
代码实现
测试地址:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/
class Solution {
private:
// 递归函数:根据中序和后序遍历结果构建二叉树
TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
// 终止条件:如果后序遍历数组为空,返回NULL
if (postorderBegin == postorderEnd) return NULL;
// 后序遍历的最后一个元素是当前子树的根节点
int rootValue = postorder[postorderEnd - 1];
TreeNode* root = new TreeNode(rootValue);
// 如果后序数组只有一个元素,直接返回根节点
if (postorderEnd - postorderBegin == 1) return root;
// 在中序数组中找到根节点的位置
int delimiterIndex;
for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序数组
int leftInorderBegin = inorderBegin;
int leftInorderEnd = delimiterIndex;
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
// 切割后序数组
int leftPostorderBegin = postorderBegin;
int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin;
int rightPostorderBegin = postorderBegin + delimiterIndex - inorderBegin;
int rightPostorderEnd = postorderEnd - 1;
// 递归构建左子树和右子树
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);
// 返回当前子树的根节点
return root;
}
public:
// 主函数:根据给定的中序和后序遍历结果构建二叉树
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return NULL;
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
从前序与中序遍历序列构造二叉树
题目描述
给定两个整数数组 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]
解题思路
整体思路: 利用前序和中序遍历的特性(前序遍历可以确定根节点,中序遍历可以确定左右子树的元素分布),逐步构建出整个二叉树。
递归函数说明:
- 递归函数的参数和返回值:
traversal
函数接受中序和前序遍历数组的起始和结束索引,以及数组本身。返回值是构建的子树的根节点。 - 递归函数的终止条件:如果前序遍历数组为空(即起始索引等于结束索引),则返回
NULL
。如果前序数组只有一个元素,直接返回根节点。 - 递归函数的单层逻辑:首先确定当前子树的根节点(前序遍历的第一个元素),然后在中序遍历中找到根节点的位置,以此分割中序和前序数组为左右两部分。接着递归地构建左子树和右子树,并将它们分别设置为当前根节点的左右子节点。最后返回当前子树的根节点。
代码实现
测试地址:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
class Solution {
private:
// 递归函数:根据前序和中序遍历结果构建二叉树
TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
// 终止条件:如果前序遍历数组为空,返回NULL
if (preorderBegin == preorderEnd) return NULL;
// 前序遍历的第一个元素是当前子树的根节点
int rootValue = preorder[preorderBegin];
TreeNode* root = new TreeNode(rootValue);
// 如果前序数组只有一个元素,直接返回根节点
if (preorderEnd - preorderBegin == 1) return root;
// 在中序数组中找到根节点的位置
int delimiterIndex;
for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序数组
int leftInorderBegin = inorderBegin;
int leftInorderEnd = delimiterIndex;
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
// 切割前序数组
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin;
int rightPreorderBegin = preorderBegin + 1 + delimiterIndex - inorderBegin;
int rightPreorderEnd = preorderEnd;
// 递归构建左子树和右子树
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, preorder, leftPreorderBegin, leftPreorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd);
// 返回当前子树的根节点
return root;
}
public:
// 主函数:根据给定的前序和中序遍历结果构建二叉树
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (inorder.size() == 0 || preorder.size() == 0) return NULL;
return traversal(inorder, 0, inorder.size(), preorder, 0, preorder.size());
}
};