2023年4月12日 22:35:33
第六章 二叉树 part05
今日内容
● 513.找树左下角的值
● 112. 路径总和 113.路径总和ii
● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
详细布置
找树左下角的值
【链接】(文章,视频,题目)
本地递归偏难,反而迭代简单属于模板题, 两种方法掌握一下
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】
- 层序遍历模板题,比较简单
【看后想法】
- 如果要用递归,反而比较复杂。递归遍历求深度,只要左比右先,任意顺序均可
【实现困难】
【自写代码】
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
// 层序遍历,最后一层,最左边
std::queue<TreeNode*> que;
if (root) {
que.push(root);
}
int res = 0;
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
// 处理
if (i == 0) {
res = node->val;
}
// 访问
if (node->left) {
que.push(node->left);
}
if (node->right) {
que.push(node->right);
}
}
}
return res;
}
};
class Solution {
public:
int max_depth_;
int bottom_left_value_;
void Traverse(TreeNode* node, int depth) {
// 结束条件
if (!node) {
return;
}
// 处理,判断是否新的最深层
if (depth > max_depth_) {
max_depth_ = depth;
bottom_left_value_ = node->val;
}
// 任意遍历,左,右
if (node->left) {
Traverse(node->left, depth + 1);
}
if (node->right) {
Traverse(node->right, depth + 1);
}
}
int findBottomLeftValue(TreeNode* root) {
// 递归遍历求深度,只要左比右先,任意顺序均可
max_depth_ = INT_MIN;
bottom_left_value_ = 0;
Traverse(root, 1);
return bottom_left_value_;
}
};
【收获与时长】30m
路径总和
【链接】(文章,视频,题目)
本题 又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解
- 路径总和,和 113. 路径总和ii 一起做了。 优先掌握递归法。
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】
- 递归,前序遍历到叶子结点,记录当前和
【看后想法】
-
对代码实现做了优化
-
对于求和得到目标的题目,可以只把剩余还缺少的和作为一个参数,而不需要同时传递目标和与当前和
-
本题属于不必遍历整颗二叉树的题目,如果有了任意一个路径综合符合要求,就应该立刻返回,因此必须有返回值,true立刻返回
-
-
迭代方法,关键是到了叶子结点的时候要知道这条路径的和是多少,因此,每个节点在遍历过程中,要把对应的当前路径和存下来,一起放到栈中
-
只需要搜索一条符合条件的路径,需要返回值来做早返回(一有符合条件的就返回)
【实现困难】
【自写代码】
class Solution {
public:
// 递归简化
bool IsHavePathSum(TreeNode* node, int targetSum) {
// 递归只接受非空结点
// 结束条件,叶子结点
if (!node->left && !node->right) {
return targetSum == node->val;
}
// 单层逻辑,左或右子树能拼成剩余目标和
if (node->left && IsHavePathSum(node->left, targetSum - node->val)) {
return true;
}
if (node->right && IsHavePathSum(node->right, targetSum - node->val)) {
return true;
}
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) {
return false;
}
return IsHavePathSum(root, targetSum);
}
};
class Solution {
public:
// 递归简化,复用题目函数
bool hasPathSum(TreeNode* root, int targetSum) {
// 根节点是空的特殊处理,或者认为是空节点的结束条件
if (!root) {
return false;
}
// 结束条件,叶子结点
if (!root->left && !root->right) {
return targetSum == root->val;
}
// 单层逻辑,左或右子树能拼成剩余目标和
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
// 迭代方法,栈中同时维护结点、当前路径和
std::stack<std::pair<TreeNode*, int>> st;
if (root) {
st.push(std::pair<TreeNode*, int>(root, root->val));
}
while (!st.empty()) {
auto cur_pair = st.top();
st.pop();
TreeNode* node = cur_pair.first;
int cur_sum = cur_pair.second;
// 处理
if (!node->left && !node->right && cur_sum == targetSum) {
return true;
}
// 访问,压栈右左
if (node->right) {
st.push(std::pair<TreeNode*, int>(node->right, cur_sum + node->right->val));
}
if (node->left) {
st.push(std::pair<TreeNode*, int>(node->left, cur_sum + node->left->val));
}
}
return false;
}
};
【收获与时长】50m
【链接】(文章,视频,题目)
【第一想法与实现(困难)】
- 记录剩余和,当前路径,返回可用路径集合
【看后想法】
-
由于是遍历所有路径,函数就不需要返回值了,而是在参数中多一个回传参数(出参)
-
传入传出参数比较多的时候,可以考虑在类内设置一个成员变量,则函数接口会变得更简洁, 代价是随时有被类内其他函数修改的风险、可读性变差(?)
【实现困难】
【自写代码】
class Solution {
public:
// 剩余和,路径,都已经考虑了node结点
void FindAllPathsWithSum(TreeNode* node, int remainder_sum, vector<int>* path_ptr, vector<vector<int>>* res_ptr) {
// 结束条件,结点默认非空
if (!node->left && !node->right) {
if (remainder_sum == 0) {
res_ptr->emplace_back(*path_ptr);
}
return;
}
// 单层逻辑
if (node->left) {
path_ptr->emplace_back(node->left->val);
FindAllPathsWithSum(node->left, remainder_sum - node->left->val, path_ptr, res_ptr);
path_ptr->pop_back();
}
if (node->right) {
path_ptr->emplace_back(node->right->val);
FindAllPathsWithSum(node->right, remainder_sum - node->right->val, path_ptr, res_ptr);
path_ptr->pop_back();
}
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<vector<int>> res;
if (!root) {
return res;
}
vector<int> path(1, root->val);
FindAllPathsWithSum(root, targetSum - root->val, &path, &res);
return res;
}
};
【收获与时长】30m
从中序与后序遍历序列构造二叉树
【链接】(文章,视频,题目)
本题算是比较难的二叉树题目了,大家先看视频来理解。
106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树 一起做,思路一样的
题目链接/文章讲解/视频讲解:代码随想录
【第一想法与实现(困难)】
【看后想法】
-
结束条件,遍历容器空
-
找中间结点,后序遍历最后一个结点
-
如果根节点有孩子,需要切割两种遍历
-
寻找根节点下标,中序遍历
-
切割中序,左闭右开
-
切割后序,左闭右开
-
递归获得左右孩子
【实现困难】
-
语法问题
-
new动态分配,返回指针,new后面不需要星号,
TreeNode* root = new TreeNode(root_val);
-
用std::find()函数之后的迭代器判断,是不等于end(),而不是begin(),
if (iter != inorder.end())
-
用vec.begin() + index的方法来初始化vector,是左闭右开的
-
【自写代码】
class Solution {
public:
TreeNode* BuildTreeFromInOrderPoserOrder(vector<int>& inorder, vector<int>& postorder) {
// 结束条件,遍历容器空
if (postorder.empty()) {
return nullptr;
}
// 找根结点,后序遍历最后一个结点
int root_val = postorder.back();
TreeNode* root = new TreeNode(root_val);
// 如果根节点有孩子,需要切割两种遍历
if (postorder.size() == 1) {
return root;
}
// 寻找根节点下标,中序遍历
auto iter = std::find(inorder.begin(), inorder.end(), root_val);
int root_idx = -1;
if (iter != inorder.end()) {
root_idx = std::distance(inorder.begin(), iter);
} else {
std::cout << "Can not find root_val:" << root_val << std::endl;
return nullptr;
}
// 切割中序,左闭右开
vector<int> inorder_left(inorder.begin(), inorder.begin() + root_idx);
vector<int> inorder_right(inorder.begin() + root_idx + 1, inorder.end());
// 切割后序,左闭右开
postorder.resize(postorder.size() - 1);
vector<int> postorder_left(postorder.begin(), postorder.begin() + root_idx);
vector<int> postorder_right(postorder.begin() + root_idx, postorder.end());
// 输出必要的调试信息
// 递归获得左右孩子
root->left = BuildTreeFromInOrderPoserOrder(inorder_left, postorder_left);
root->right = BuildTreeFromInOrderPoserOrder(inorder_right, postorder_right);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.empty() || postorder.empty() || inorder.size() != postorder.size()) {
return nullptr;
}
return BuildTreeFromInOrderPoserOrder(inorder, postorder);
}
};
class Solution {
public:
TreeNode* BuildTreeFromInorderPostorder(const vector<int>& inorder, int inorder_begin, int inorder_end, const vector<int>& postorder, int postorder_begin, int postorder_end) {
// 结束条件,空
if (postorder_begin >= postorder_end) {
return nullptr;
}
// 取根节点,后序遍历最后一个
int root_val = postorder[postorder_end - 1];
TreeNode* root = new TreeNode(root_val);
// 判断有叶子
if (postorder_end - postorder_begin == 1) {
return root;
}
// 根节点中序下标
int root_idx = -1;
for (int i = inorder_begin; i < inorder_end; ++i) {
if (inorder[i] == root_val) {
root_idx = i;
break;
}
}
if (root_idx == -1) {
std::cout << "Can not find root_val:" << root_val << std::endl;
}
// 切割中序,左闭右开
int left_inorder_begin(inorder_begin), left_inorder_end(root_idx);
int right_inorder_begin(root_idx + 1), right_inorder_end(inorder_end);
// 切割后序,左闭右开
int left_postorder_begin(postorder_begin), left_postorder_end(postorder_begin + (left_inorder_end - left_inorder_begin));
int right_postorder_begin(left_postorder_end), right_postorder_end(postorder_end - 1);
// 打印日志
// std::cout << "left_inorder:" << std::endl;
// for (int i = left_inorder_begin; i < left_inorder_end; ++i) {
// std::cout << inorder[i] << ' ';
// }
// std::cout << std::endl;
// std::cout << "right_inorder:" << std::endl;
// for (int i = right_inorder_begin; i < right_inorder_end; ++i) {
// std::cout << inorder[i] << ' ';
// }
// std::cout << std::endl;
// std::cout << "left_postorder:" << std::endl;
// for (int i = left_postorder_begin; i < left_postorder_end; ++i) {
// std::cout << postorder[i] << ' ';
// }
// std::cout << std::endl;
// std::cout << "right_postorder:" << std::endl;
// for (int i = right_postorder_begin; i < right_postorder_end; ++i) {
// std::cout << postorder[i] << ' ';
// }
// std::cout << std::endl;
// 递归左右孩子
root->left = BuildTreeFromInorderPostorder(inorder, left_inorder_begin, left_inorder_end, postorder, left_postorder_begin, left_postorder_end);
root->right = BuildTreeFromInorderPostorder(inorder, right_inorder_begin, right_inorder_end, postorder, right_postorder_begin, right_postorder_end);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.empty() || postorder.empty() || inorder.size() != postorder.size()) {
return nullptr;
}
return BuildTreeFromInorderPostorder(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
【收获与时长】1h40m