目录
一、LeetCode 513.找树左下角的值
题目链接:LeetCode 513.找树左下角的值
文章讲解:代码随想录
视频讲解:怎么找二叉树的左下角? 递归中又带回溯了,怎么办?| LeetCode:513.找二叉树左下角的值
思路:
注意审题,题目中要求的顺序是先最底层,再最左边,因此是找二叉树最后一层的最左边结点;那么只需要按照层次遍历,遍历到最后一层,记录最后一层最左边的值即可。
C++代码
/**
* 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:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que; //层次遍历
TreeNode* prel_left;
if(root){
que.push(root);
}
while(!que.empty()){
int size = que.size();
prel_left = que.front(); //记录最左边的值
for(int i = 0; i < size; i++){
TreeNode* node = que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return prel_left->val;
}
};
二、LeetCode 112. 路径总和
题目链接:LeetCode 112. 路径总和
文章讲解:代码随想录
视频讲解:拿不准的遍历顺序,搞不清的回溯过程,我太难了! | LeetCode:112. 路径总和
思路
题目要求,在树中所有路径中,找出一条路径,使得路径中所有节点值之和等于一个给定值;类似这样在解空间树中寻找一个解的问题可以使用回溯法/分支限界法来进行求解。
具体函数设计为传递参数为当前结点、targetSum
值,以及当前结点值总和sum
;每层判断当前结点加上以后是否等于给定值,用bool
值返回是否存在这样一个解。
C++代码
/**
* 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 backtrack(TreeNode* node, int targetSum, int sum) { //回溯算法
if (!node) {
return false;
} else if (!node->left && !node->right) {
if (sum + node->val == targetSum) {
return true;
}
return false;
}
//sum不传引用,只传一个参数
//每层函数函数不修改上一层sum的值,因此达成回溯。
return backtrack(node->left, targetSum, sum + node->val) ||
backtrack(node->right, targetSum, sum + node->val);
}
bool hasPathSum(TreeNode* root, int targetSum) {
return backtrack(root, targetSum, 0);
}
};
二、LeetCode 106.从中序与后序遍历序列构造二叉树
题目链接:LeetCode 106.从中序与后序遍历序列构造二叉树
文章讲解:代码随想录
视频讲解:坑很多!来看看你掉过几次坑 | LeetCode:106.从中序与后序遍历序列构造二叉树
思路
题目开始上难度了。我们一贯的构建二叉树方式为完全二叉树顺序进行广度优先的构建,本题的难点一在于反常识,二在于抽象,需要根据中序遍历和后序遍历两个“平面视图”来推算出该一般二叉树的形状。
遇到困难先不要着急,我们可以先把一棵二叉树的中序遍历和后序遍历的结点表写出来,观察各有什么特点、与二叉树有什么关系。
我们首先可以写出来这一棵二叉树的中序和后序序列:
- 中序: [ 9 , 3 , 15 , 20 , 7 ] [9, 3, 15, 20, 7] [9,3,15,20,7]
- 后序: [ 9 , 15 , 7 , 20 , 3 ] [9, 15, 7, 20, 3] [9,15,7,20,3]
对照二叉树的结构我们可以发现,后序遍历“左右中”的顺序,使得后序序列最后一个元素一定是根结点;而根结点对应到“左中右”顺序的中序序列,可以发现,“3”号结点的左边所有元素是该结点的左子树,右边所有元素是该结点的右子树.
那么我们就可以根据这两条特性来进行构建,主要思路为:用后序序列来确定当前的结点(根结点),用中序序列来划分左右子树
在递归函数的设计上,笔者采用了在递归函数内构建左右孩子的方式;首先按照我们找到的特性,先在中序和后序序列中找到根节点位置:
//在后序序列中寻找当前根节点
auto it = --postorder.end();
//根结点在中序序列中的位置
auto it_in = find(inorder.begin(), inorder.end(), *it);
这里找根节点位置采用了迭代器( i t e r a t o r iterator iterator),类似于C语言中的指针,主要在C++标准库中的容器类内使用,具体的使用原因我们放在后面讲。
然后分别构建左右孩子,构建孩子的方法如下:
左右孩子其实就是左右子树的根节点,我们要在左右子树中找到根节点仍然需要依托后序序列,而确定左右子树则需要先在中序序列中划分。因此需要先在中序序列中找到左右子树,再对应后序序列的位置找到左右子树的根节点,代码如下:
//使用后序序列寻找根结点位置
vector<int>::iterator it_left; //左子树根结点在后序序列中的位置
int dist = it_in - inorder.begin() - 1; //用中序序列求出左子树有多少个元素
it_left = postorder.begin() + dist;//在后序序列中确定位置
TreeNode* left = new TreeNode(*it_left);
subroot->left = left;//构建左孩子
右孩子构建与左孩子类似。
构建结束后,我们需要进入下一层继续递归构建。这里采用了代码随想录的方法:将当前函数传入的中序序列和后序序列作切分,都切分成左子树的中、后序和右子树的中、后序,传入下一层继续递归。
切分vector
容器就需要依靠迭代器iterater
了,这也是前面使用迭代器确定位置的原因:
vector<int> in_l(inorder.begin(), it_in);//左子树中序序列
vector<int> post_l(postorder.begin(), it_left+1);//左子树后序序列
Subtree(left, in_l, post_l);
另外注意递归的终止条件,以及左右子树构建的条件,即可编写出完整的程序。完整代码如下:
C++代码
/**
* 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:
void Subtree(TreeNode* subroot, vector<int> inorder, vector<int> postorder) {
if(!inorder.empty()){
//在后序序列中寻找当前根节点
auto it = --postorder.end();
//根结点在中序序列中的位置
auto it_in = find(inorder.begin(), inorder.end(), *it);
//生成左子树
if(it_in != inorder.begin()){
vector<int>::iterator it_left;
int dist = it_in - inorder.begin() - 1;
it_left = postorder.begin() + dist;
TreeNode* left = new TreeNode(*it_left);
subroot->left = left;
vector<int> in_l(inorder.begin(), it_in);
vector<int> post_l(postorder.begin(), it_left+1);
Subtree(left, in_l, post_l);
}
//生成右子树
if(it_in != --inorder.end()){
auto it_right = it-1;
TreeNode* right = new TreeNode(*it_right);
subroot->right = right;
int dist = inorder.end()-it_in-1;
vector<int> in_r(it_in+1, inorder.end());
vector<int> post_r(it-dist, it);
Subtree(right, in_r, post_r);
}
}
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
// 中序来定左右子树,后序寻找子树的根
TreeNode* root = new TreeNode(postorder[postorder.size() - 1]);
Subtree(root, inorder, postorder);
return root;
}
};
总结
仍然是二叉树遍历与递归的应用,中序后序构造二叉树题目较难,需要深入研究各种遍历的特点。在编写递归函数时仍要注意各种逻辑关系与终止条件是否合理。
文章图片来源:代码随想录 (https://programmercarl.com/)