题目如图,虽然是个简单题,但是如果把两种写法对比着看,对理解递归过程还是挺有帮助的。
另外借这道题说一说对递归死活想不明白时的一些思路。
首先明确一点返回左叶子之和不是从二叉数左侧视图,右侧视图那种题,这两道题使用层序遍历方法的判断是不一样的。
无论是层序遍历还是递归解法,最不一样的地方就是判断一个节点是不是左叶子节点的这个判断怎么写了。
根据节点本身是没办法判断了,只能根据父节点判断,判断条件如下:
if (root->left && !root->left->left && !root->left->right)
首先第一种方法是官方题解和大多数人的做法:
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0;
int leftValue = sumOfLeftLeaves(root->left); // 左
if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
}
};
这写法的思路就是后序遍历,然后把结果一层层再返回上去最后得出sum。我真就觉得邪门了,本来递归的思路就很绕人了,我一层层递下来了你还给我搞一个左value,右value的再一层层传回去,下一层leftvalue和rightvalue的和搞一个变量叫sum,这个sum会成为上一层调用里的某一个leftvalue或者right value,你自己说这东西绕不绕,你不这么写不行吗?这种写法还必须是后序遍历,因为必须从下往上的传结果。
学习递归最有一个误区就是,拿到一个新问题后,妄图想在思考这题怎么写的时候在脑子里模拟出整个递归过程,这种解题想法出现很容易一题写一天的情况,然后在脑海里递归个两三层就把CPU干烧了,我之前做题一直有这个误区,结果往往简单题都做不出来。最后得出总结提炼一句话就是:要意识到递归的每一次调用其实完成的是同一件事,但是在不同地方完成。这也是很多教程里说的递归三部曲里确定单层逻辑的另一种解释。
然而,然而,这道题的这种第一种解法就有误导你往模拟递归过程这方面想的成分,如果你熟练掌握了递归算法肯定不会这么想,但是对于新手来说,想要把结果最后再一层层传回去,为了避免传错了可不得先把整个递归过程想明白吗
再者说,任何人第一次学二叉树,都是从前中后序三遍历开始的:
class Solution {
public:
void postorder(TreeNode *root, vector<int> &res) {
if (root == nullptr) {
return;
}
postorder(root->left, res);
postorder(root->right, res);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode *root) {
vector<int> res;
postorder(root, res);
return res;
}
};
。
这写法简洁明了,非常符合“递归的每一次调用其实完成的是同一件事,但是在不同地方完成。”这句话的内涵。明明都是入门题,那你这道左子叶的题凭什么就非要再搞一个后序遍历传结果回去的方法呢,二叉树遍历法里在单层逻辑里有这么类似这么两行的东西存在吗?
int leftValue = sumOfLeftLeaves(root->left);
int rightValue = sumOfLeftLeaves(root->right);
没必要再弄一个新方法来把本来脑子就没绕明白的人再绕一圈,那下一道题我该用遍历的方法还是后序传值的方法呢?两个方法分别适合应用于什么问题,该怎么用?老手自然觉得这点事也叫事?新手是真摸不清方向。
说回这道题,换个思路来说,我无非不也是遍历二叉树所有的节点然后判断某一个节点是不是左叶子节点就行了,是的话把值全加起来不就完事了。
于是第二种写法,显然易于理解递归到底是怎么一回事
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int ans=0;
return getleftpoint(root,ans);
}
int getleftpoint(TreeNode*root,int &n)
{
if (root == NULL) return 0;
getleftpoint(root->left,n);
getleftpoint(root->right,n);
if (root->left && !root->left->left && !root->left->right)
{
n = n+root->left->val;
}
return n;
}
};
第二个方法完全符合二叉树遍历的思路,只是单层逻辑的判断不一样了,整个刷题过程是层层渐进的,后面题做多了,对递归了解更深入了,再想着用用别的方法不是正好。
第二种写法里,if判断写对了,前中后序遍历哪种不是遍历,也就不用必须要求后序遍历了。
//前序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int ans=0;
return getleftpoint(root,ans);
}
int getleftpoint(TreeNode*root,int &n)
{
if (root == NULL) return 0;
if (root->left && !root->left->left && !root->left->right)
{
n = n+root->left->val;
}
getleftpoint(root->left,n);
getleftpoint(root->right,n);
return n;
}
};
///
//中序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int ans=0;
return getleftpoint(root,ans);
}
int getleftpoint(TreeNode*root,int &n)
{
if (root == NULL) return 0;
getleftpoint(root->left,n);
if (root->left && !root->left->left && !root->left->right)
{
n = n+root->left->val;
}
getleftpoint(root->right,n);
return n;
}
};
///
//后序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int ans=0;
return getleftpoint(root,ans);
}
int getleftpoint(TreeNode*root,int &n)
{
if (root == NULL) return 0;
getleftpoint(root->left,n);
getleftpoint(root->right,n);
if (root->left && !root->left->left && !root->left->right)
{
n = n+root->left->val;
}
return n;
}
};
也就是说这三种写法都对。