链接:https://leetcode.cn/problems/sum-of-left-leaves/
首先要理解题意:累计左叶子之和,那么首先要是叶子对吧,也就是我们在递归判断的时候,要满足其“左儿子为空且右儿子为空”并且是左儿子才会进行加和,但是前者很好判断,但是在递归结束的时候判断其是“左儿子”是比较困难的,所以我们考虑在“向下遍历之前”,就将其值加入到递归时维护的sum中去
如何加呢?在向左下递归的时候判断其是叶子节点就好
并且因为我们每次确认加和的时候都保证其是左叶子,所以我们维护的sum要取引用,如果不取引用的话return的时候值也会回溯,就很麻烦
class Solution {
public:
void recursion(TreeNode *head,int &sum)
{
if(head->left == NULL && head->right == NULL) return;
// 如果左节点不是空,那么就递归
if(head->left!=NULL)
{
TreeNode *l_son = head->left;
// 判断其是叶子节点后才并入其值
if(l_son ->left == NULL && l_son->right == NULL) sum += (head->left)->val;
recursion(head->left,sum);
}
if(head->right!=NULL) recursion(head->right,sum);
}
int sumOfLeftLeaves(TreeNode* root) {
int sum = 0;
if(root == NULL) return sum;
recursion(root,sum);
return sum;
}
};
链接:https://leetcode.cn/problems/find-bottom-left-tree-value/
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
if(root != NULL) q.push(root);
int ans;
while(!q.empty())
{
int size = q.size(); // 一定要做!!!
vector<int> res;
for(int i=0;i<size;i++) // 这直接给无限循环了,所以我们肯定要在一开始就记录当前queue的size
{
TreeNode *cur = q.front();
res.push_back(cur -> val);
q.pop();
if(cur->left !=NULL) q.push(cur->left);
if(cur->right!=NULL) q.push(cur->right);
}
ans = res[0];
}
return ans;
}
};
在我看来递归也可以实现层序遍历,所以用bfs和递归实现层序其实没有什么区别,用层序也简单很多很多,直接找到每层记录的数组中1第一个值就OK
链接:https://leetcode.cn/problems/path-sum/
我们先梳理梳理思路:这波肯定是需要回溯的,并且一定是在叶子节点处进行return。因为需要回溯,所以我们就不用sum的引用了,每次创建新的递归的时候都用(sum + val)的形式传入值,这样递归的函数return回来后sum + val会变成sum,这就相当于是实现了回溯
然后我们想想return的时候的逻辑:判断当前节点是叶子节点后肯定要比较维护的sum和初始targetSum的值,若值相同就返回true…
此时我们应该意识到了,因为一个节点可以向左向右递归,递归返回的是bool值,所以我们肯定要用两个bool值接住向左和向右遍历的结果,但是我们递归的节点都必须要是非空的节点,所以bool值在判断外侧定义,一定要注意初始化!!!并且初始化成false,最后输出两个bool值取逻辑或
class Solution {
public:
bool recursion(TreeNode *head,int sum,int targetSum)
{
// 只有当前节点是根节点并且sum 和 tar完全相等时,才是true
// 那要是其他路径返回false呢?所以要取逻辑或
if(head -> left == NULL && head -> right == NULL && sum == targetSum) return true;
bool l_is_true = false,r_is_true = false; // 只要赋初值就不会再报错了
if(head->left != NULL) l_is_true = recursion(head -> left,sum + (head->left)-> val,targetSum);
if(head->right != NULL) r_is_true =recursion(head -> right,sum + (head->right)-> val,targetSum);
return l_is_true || r_is_true;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == NULL) return false;
int sum = root->val;
bool res = recursion(root,sum,targetSum);
return res;
}
};
因为我们每次要记录路径,所以传入参数的时候需要一个vector,又因为需要回溯,所以我们就不用引用了,在递归到下一层之前push,在递归结束之后pop
class Solution {
public:
vector<vector<int>> res;
// 我们这次需要记录路径了,如果符合题意直接就用vector记录其中合法的路径
void recursion(TreeNode *head,int sum,int targetSum,vector<int>path) // 那我们不用引用不就不需要pop了吗
{
if(head -> left == NULL && head -> right == NULL && sum == targetSum) {res.push_back(path);return;}
if(head->left != NULL)
{
path.push_back((head->left)->val);
recursion(head -> left ,sum + (head->left)->val,targetSum,path);
path.pop_back();
}
if(head->right != NULL)
{
path.push_back((head->right)-> val);
recursion(head -> right,sum + (head->right)-> val,targetSum,path);
path.pop_back();
}
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if(root == NULL) return res;
int sum = root->val;
vector<int> path;
path.push_back(root->val);
recursion(root,sum,targetSum,path);
return res;
}
};
好题!让哥思索良久
首先要清楚中序遍历和后序遍历的顺序:
中序:[左 中 右]
后序:[左 右 中]
因为我们的目标是要建树,所以我们首先要找到根节点。命中后序数组中的最后一个元素后,我们将中序数组切分成左右区间(注意这里有一个细节,就是建树之后不管是中序还是后序,都是抛弃了这个节点的,区别就在于中序数组中在遍历的时候找到根节点的位置,然后切分的时候跳过这个元素;而后序数组是直接size-1,也就是数组往前缩减一格,自然就去掉根节点了…)
但是为什么先切分中序数组,因为中序数组中左右子树是被中间节点分割开的,而后序数组中左右区间是连在一起的,所以我们要先切分中序数组,利用中间节点的值将中序数组切分为左数组和右数组后,我们再用切分好的中序左数组去切分后序数组,为什么可以呢?注意到中序数组中最开始的就是左子树,后序数组中最开始的也是左子树,既然能建成同一棵树,那么左子树的大小一定是相等的!!!
然后我们再用切分好的四个数组去接着递归,这里要注意:递归左子树的参数是我们刚刚切分好的中序左数组和后序左数组,同理递归右子树的参数是中序右数组和后序右数组
最后返回根节点就OK,完美
class Solution {
public:
TreeNode * recursion(vector<int>& inorder, vector<int>& postorder)
{
// 如果两个vector中有一个为空,那么直接就返回null
if(inorder.size() == 0 || postorder.size() == 0) return NULL;
int val = postorder[postorder.size()-1];
TreeNode *cur = new TreeNode(val);
if(postorder.size() == 1) return cur;
postorder.resize(postorder.size() - 1); // 因为最后一个元素已经用过了,所以抛弃掉
// 然后就是依照得到的新节点去切分两个区间
int index = 0;
for(;index < inorder.size();index++)
{
if(inorder[index] == cur -> val) break;
}
// 得到切分位置了以后开始切分,注意后续递归的时候遍历中序数组的左区间和后序遍历的左区间,遍历中序数组的右区间和后序遍历的右区间
vector<int> inorder_l (inorder.begin(),inorder.begin() + index);
vector<int> inorder_r (inorder.begin() + index + 1,inorder.end()); // 其实是跳过了中间那个元素
vector<int> postorder_l (postorder.begin(),postorder.begin() + inorder_l.size()); // 因为中序遍历是左中右,后序遍历是左右中,这里隐藏的关键信息就是左子树的大小是一样的,所以我们构建后序遍历左区间的时候直接用的是和中序遍历左区间一样的大小
vector<int> postorder_r (postorder.begin() + inorder_l.size(),postorder.end());
cur->left = recursion(inorder_l, postorder_l);
cur->right = recursion(inorder_r, postorder_r);
return cur;
}
// 还是递归,每次传入左边区间和右边区间,递归的时候我们要对初始中序遍历和后序遍历的数组进行切割
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0 || postorder.size() == 0) return NULL;
TreeNode *root = recursion(inorder,postorder);
return root;
}
};
前序:[中 左 右]
后序:[左 右 中]
其实和上一道题的逻辑差不多,这次把后序数组变成了前序数组,就是在改变前序数组中的值的时候需要注意一下,就当是加深熟练度了呗
class Solution {
public:
TreeNode *recursion(vector<int> preorder,vector<int>inorder)
{
if(preorder.size() == 0 || inorder.size() == 0) return NULL;
// 然后取出前序数组的中间节点,也就是第一个
int val = preorder[0];
TreeNode * cur = new TreeNode(val);
if(preorder.size() == 1) return cur;
for(int i=1;i<preorder.size() ;i++) preorder[i-1] = preorder[i];
preorder.resize(preorder.size()-1); // 值应该是从后往前依次覆盖
// 然后我们在中序遍历中找到这个元素
int index = 0;
for(;index < inorder.size();index++) if(inorder[index] == val) break;
// 然后我们分别切割中序数组和前序数组
vector<int> inorder_l (inorder.begin(),inorder.begin() + index);
vector<int> inorder_r (inorder.begin() + 1 + index,inorder.end());
vector<int> preorder_l (preorder.begin() ,preorder.begin() + inorder_l.size());
vector<int> preorder_r (preorder.begin() + inorder_l.size(),preorder.end());
cur -> left = recursion(preorder_l,inorder_l);
cur -> right = recursion(preorder_r,inorder_r);
return cur;
}
// 和上一题差不多,这次前序遍历的中间节点在第一个
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size() == 0 || inorder.size() == 0) return NULL;
return recursion(preorder,inorder);
}
};
其实就是比之前两题还要简单,左右递归构树就完事儿了
// 思路:首先找到当前数组中的最大值,然后递归构建子树,
// 所以其实还是传入数组啊,左边的数组中的最大值作为头结点,然后左侧的就作为左节点,右侧的就作为右节点
class Solution {
public:
TreeNode* recursion(vector<int> nums) // 其实应该每次传入一个数组,然后在函数里面完成分割
{
if(nums.size() == 0) return NULL;
// 想一想我们如何构建树,先找到最大值和其索引
int maxx = -999; int index = -1;
for(int i=0;i<nums.size();i++) {if(maxx < nums[i]) {maxx = nums[i];index = i;}}
// 找到最大值后把中间节点构建出来
TreeNode * cur = new TreeNode(maxx);
// 然后把左右区间给划分出来
vector<int> l (nums.begin(),nums.begin() + index);
vector<int> r (nums.begin() + index + 1,nums.end());
cur -> left = recursion(l);
cur -> right = recursion(r);
return cur;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.size() == 0) return NULL;
return recursion(nums);
}
};
好耶~~~~~