思路(递归):题意是找树的最后一行,最左边的值,所以不一定该节点是左孩子,也有可能是右孩子。本题不需要中的处理过程,所以前中后序遍历都可以,因为只需要左右的遍历顺序即可。用一个全局变量MaxDepth记录最大的深度,depth记录当前遍历的层数(所以需要回溯过程)。因为最先递归遍历左边,所以如果当第一次最大深度出现节点,则result一定记录的是最左值,再之后遍历如果最大深度改变则记录替换之前的值。
代码:
int MaxDepth = INT_MIN;//记录树的最大深度
int result;//存放最终节点的值
void traversal(TreeNode* node,int depth){//depth记录当前遍历到的深度
if(node->left==nullptr && node->right==nullptr){//终止条件
if(MaxDepth < depth){
MaxDepth = depth;
result = node->val;
}
return;//如果当前不是最大深度但又是叶节点则会返回上一个节点
}
if(node->left){//左
depth++;
traversal(node->left,depth);
depth--;//回溯深度
}
if(node->right){//右
depth++;
traversal(node->right,depth);
depth--;
}
return;
}
int findBottomLeftValue(TreeNode* root) {
traversal(root,0);
return result;
}
思路(迭代法):本题用层序遍历是最简单的方法,只需要每次记录每一行的第一个节点值即可.
代码:
int findBottomLeftValue(TreeNode* root) {
queue que;
if(root!=nullptr) que.push(root);
int result = 0;
while(!que.empty()){
int size = que.size();
for(int i = 0;i < size;i++){
TreeNode* node = que.front();//该行最左的第一个节点
if(i == 0) result = node->val;//i=0,则这一行刚开头,记录下结果
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
que.pop();
}
}
return result;
}
思路(递归法):首先遍历顺序由于没有需要中节点的处理逻辑,所以中为空,遍历顺序左右都可以。
正常思路都是把每遍历的节点进行累加,若等于目标值且到了叶子节点则符合目标路径。这种方法需要count值设为0,但是还需要传目标值,会比较麻烦。下面的方法是count值直接为目标值,每遍历一个节点就减去该节点的目标val值。最后遍历到叶子节点且count为0则为目标路径。
注1:返回true和false的条件多,这题只需要找到其中一条满足条件的路径,所以如果找到目标路径一直往上返回true,所以需要返回值.若只是遍历的题目,不需要返回值。
注2:需要用到回溯,有递归就有回溯,这题和上题的思路相似。
代码:
bool traversal(TreeNode* node,int count){//count是目标值,每遍历一个节点减去节点值,最后为0且叶子节点则是目标路径
//终止条件1,遍历到叶子节点且count已经减到0,则是目标路径
if(node->left==nullptr && node->right==nullptr && count==0)
return true;
//终止条件2,遍历到叶子节点但count不为0,则不是目标路径
if(node->left==nullptr && node->right==nullptr && count!=0)
return false;
//左右遍历顺序
if(node->left){//左
count-=node->left->val;
if(traversal(node->left,count))//如果返回结果是真,则找到了目标路径,所以不需要进行下一步回溯,直接一层层返回true
return true;
count+=node->left->val;//若上面条件不满足,说明到了叶子节点但是不满足条件,所以要回溯
}
if(node->right){//右
count-=node->right->val;
if(traversal(node->right,count))
return true;
count+=node->right->val;//回溯
}
return false;//从根出发经历了上面的遍历,都没有返回true说明没有符合条件的路径
}
bool hasPathSum(TreeNode* root, int targetSum) {
if(root==nullptr)
return false;
return traversal(root,targetSum-root->val);
}
思路(递归法):这题思路112题和257.二叉树的所有路径结合,找符合目标值的路径112题,把路径加入结果集257题。
代码:
vector> result;//存放结果
vector path;//存放当前路径
void traversal(TreeNode* node,int count){
//终止条件1,符合目标路径,把当前路径放入结果集
if(node->left==nullptr && node->right==nullptr && count==0){
result.push_back(path);
return;
}
//终止条件2,遍历到叶节点但不符合目标路径
if(node->left==nullptr && node->right==nullptr && count!=0)
return;
if(node->left){//左
count-=node->left->val;
path.push_back(node->left->val);
traversal(node->left,count);
count+=node->left->val;//回溯
path.pop_back();//回溯
}
if(node->right){//右
count-=node->right->val;
path.push_back(node->right->val);
traversal(node->right,count);
count+=node->right->val;//回溯
path.pop_back();//回溯
}
return;
}
vector> pathSum(TreeNode* root, int targetSum) {
result.clear();
path.clear();
if(root==nullptr)
return result;
path.push_back(root->val);
traversal(root,targetSum-root->val);
return result;
}
思路:首先要理解构造二叉树的理论知识。整体思路是确定切割点,将其中序数组和后序数组进行切割分为左右子树的集合,再递归切割构造。
以下分步骤解析:
第一步:从中序数组中确定切割点在数组的下标(位置)(后序数组末尾是中间节点也是要在中序数组中找的切割点)
第二步:利用中序切割点的位置把中序数组切割为左中序,右中序数组(不包括切割点)
第三步:把后序数组切割为左后序数组和右后序数组;ps:中序是有已知的切割点当然方便切割,但是切割点也就是中间节点在后序数组是最后一个节点,所以并不能作为后序数组切割的依据。但是从另一个方面来看,第一二步中已经明确划分了左右子树的集合了(左右中序数组)。子树集合大小无论在前中后序中都是不变的,这个可以作为后序数组切割依据。
第四步:递归进行切割构造(分别传入左子树集合包括左中序数组,左后序数组;传入右子树集合包括右中后序数组)
最后在传会其构造节点
注:这题还需要遵守递归的循环不变量原则,即切割的区间。
代码:
TreeNode* traversal(vector& inorder,vector& postorder){//inorder中序数组,postorder后序数组
//终止条件1,因为主函数有了判断是否为空数组,这个条件是递归循环时遇到传入空数组的终止情况
if(postorder.size() == 0) return nullptr;
int rootValue = postorder[postorder.size() - 1];//后序数组的最后一个元素是当前的中间节点
TreeNode* root = new TreeNode(rootValue);
//终止条件2,情况1:第一次递归数组只有一个节点,返回当前节点;情况2:递归传入数组只有一个节点,即是叶子节点,所以下面不需要再递归,把当前叶子节点返回
if(postorder.size() == 1) return root;
/*第一步.从中序数组确定切割点的位置*/
int delimiterIndex;
for(delimiterIndex = 0;delimiterIndex < inorder.size();delimiterIndex++){
if(inorder[delimiterIndex] == rootValue) break;
}//遍历结束,delimiterIndex就是中序数组的切割点位置
/*第二步.切割中序数组使其分为左中序和右中序数组(不包含中节点)*/
//左区间,当前区间为左闭右开[0,delimiterIndex)
vector leftInorder(inorder.begin(),inorder.begin() + delimiterIndex);
//右区间,[delimiter+1,end)
vector rightInorder(inorder.begin() + delimiterIndex+1,inorder.end());
/*第三步,切割后序数组使其为左后序和右后序数组,有个注意的点,因为中间节点是在后序数组末尾,所以要舍弃*/
//左闭右开,切割点无论左右区间的长度都和中序时一样的,所以和中序切割方法不同,用其长度进行切割
postorder.resize(postorder.size()-1);//舍弃最后一个元素
//左区间,[0,leftIndex)
vector leftPostorder(postorder.begin(),postorder.begin() + leftInorder.size());
//右区间,(leftIndex,end],后序不像中序,后序根节点已经在数组末尾并且被舍弃,所以切割是连续的
vector rightPostorder(postorder.begin() + leftInorder.size(),postorder.end());
/*第四步,把左子树集,右子树集进行递归切割*/
root->left = traversal(leftInorder,leftPostorder);//传入左中序,左后序集合
root->right = traversal(rightInorder,rightPostorder);//传入右中序,右后序
return root;
}
TreeNode* buildTree(vector& inorder, vector& postorder) {
if(inorder.size()==0 || postorder.size()==0) return nullptr;
return traversal(inorder,postorder);
}
思路:和106思路是一样的,唯一区别是要知道前序的根节点是在数组第一个位置。所以代码在切割部分注意区分即可。