Day17【二叉树】110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

110.平衡二叉树

题目链接/文章讲解/视频讲解  

高度平衡二叉树:任何一个节点的左右子树高度差不超过 1 

这道题如何定义递归函数作用是关键 

定义递归函数作用:返回一个高度平衡二叉树的高度,若非高度平衡的二叉树,应该返回 -1

明确参数和返回值:根据递归函数作用得知:参数为一个节点表示树,返回值为 int 

确定终止条件:若为空树,直接返回0;若该树的左孩子或者右孩子不是高度平衡二叉树(即左遍历左孩子或者右孩子得到的高度为 -1),则该树本身肯定不是高度平衡二叉树,直接返回 -1

单层递归逻辑:根据递归函数作用,需要利用左子树的高度和右子树的高度(通过递归可以得到)判断当前树是否为平衡二叉树,是则返回左右子树最大高度 + 1,否则返回 -1

另外说一点,这里完整遍历完根节点需要用到遍历左子树和遍历右子树的返回结果,是后序遍历

代码如下

class Solution {
public:
    // 返回以该节点为根节点的二叉树的高度,如果不是高度平衡二叉树则返回-1
    int getHeight(TreeNode* node) {
        if (node == NULL) {
            return 0;
        }    // 终止条件
        int leftHeight = getHeight(node->left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;    // 终止条件
        return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);    // 单层处理逻辑
    }
    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};

257.二叉树的所有路径

题目链接/文章讲解/视频讲解 

本题是回溯算法的基础

本题需要按照前序遍历的过程遍历,这样在遍历过程中才能将遍历过的路径节点按序输出 

在遍历到叶子节点的时候,需要记录已经走过的路径,然后回退至上一层,此时需要撤销已经走过的路径里面的最后一个节点,继续去寻找别的路径

这个撤销过程就是回溯 

回溯在后面详细介绍,今天只是单纯介绍这道题目,不涉及回溯的框架 

想想一个人在城市间旅游,他需要记录到达目的城市时,物理路径上的城市。因此他需要不断记录走到某个城市,从起点城市到该城市物理路径上所经过的城市名

 

假设这个人走到了成都,他所做的是,拿出小本本记下这个城市名称,这个小本本就是 path

然后,这个人要判断是不是下面还有别的城市可以去,如果还有别的城市,比如这里还有杭州、三亚、上海可以去,那么这个人就要继续走下去

那么这个人继续走,此时走到了杭州,拿出小本本 path 记下杭州这个城市名字 

然后他发现,到达目的城市了,好了,此时他应该把小本本上的 path 记录下来,放进 result 里,表明记录了一条完整的到达目的城市的记录了。然后他应该小本本擦去杭州这个城市名字,并往回走到成都,从而能继续去寻找别的目标城市

 

然后他继续看,发现还有三亚和上海可以去,继续走

这里,走到三亚了,拿出小本本 path 记下三亚这个城市名字。这里就体现了为啥之前要擦去杭州。如果不擦去的话,记录的路径就是彭州->成都->杭州->三亚。而擦去了,记录的路径才是彭州->成都->三亚。注意记录的是物理路径

然后他发现,到达目的城市了,好了,此时他应该把小本本上的 path 记录下来,放进 result 里,表明记录了一条完整的到达目的城市的记录了。然后他应该在小本本擦去三亚这个城市名字,并往回走到成都,从而能继续去寻找别的目标城市

继续循环执行,直到杭州三亚上海都去过了,此时站在成都,发现都去过了,无路可走了

此时,他需要在小本本擦去成都这个城市名字 ,然后再往回走到彭州,从而能继续去寻找别的目标城市

擦去的原因同理,如果不擦去,那么这个人下次到达其他目标城市的时候,路径会是彭州->成都->其他目标城市,而非彭州->其他目标城市,不符合题意

如此我们就演绎了整个过程,过程中的关键就是擦除操作,擦除就体现了回溯 

/**
 * 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:
    vector<int> path;   // 用于记录城市名的小本本
    vector<string> result;  // 用于记录到达目标城市后的路径结果
    vector<string> binaryTreePaths(TreeNode* root) {
        if (root != nullptr)
            backtracking(root);
        return result;
    }
    void backtracking(TreeNode* root) {
        path.push_back(root -> val);    // 拿出小本本记录当前到达的城市名
        if (root -> left == nullptr && root -> right == nullptr)    // 如果已经到达了目标城市
        {
            string pathString;
            for (int i = 0; i < path.size() - 1; ++i) {
                pathString += to_string(path[i]);
                pathString += "->";
            }
            pathString += to_string(path[path.size() - 1]);
            result.push_back(pathString);   // 把小本本上的 path 记录下来,放进 result 里
            path.pop_back();    // 小本本擦去
            return; // 往回走到上一个城市
        }
        // 如果没有到目标城市
        if (root -> left)   // 继续往后面的目标城市走
            backtracking(root -> left); 
        if (root -> right)  // 继续往后面的目标城市走
            backtracking(root -> right);    
        path.pop_back();    // 去过了所有的目标城市,无路可走了
        return; // 往回走到上一个城市
    }
};

其实我们发现,定义的backtracking就像是那个人,在城市间行走,进入backtracking(arg)函数,函数内容就是描述这个人到达了arg城市之后应该做什么事

整个代码涉及到了判断结束条件(判断是否到达目标城市)、撤销选择操作(擦除城市名)、做选择操作(选择后面的目标城市继续走、记录)等,这些具体内容在回溯章节将继续讲解

404.左叶子之和

题目链接/文章讲解/视频讲解 

定义递归函数作用:返回一个树的左叶子值之和

确定递归函数参数及返回值:要传入树的根节点,返回值为数值之和

int sumOfLeftLeaves(TreeNode* root);

确定递归函数终止条件

如果遍历到根为空,则空树左叶子值之和为0,直接返回0

确定递归函数逻辑:整棵树的左叶子之和应该等于其左子树左叶子之和与右子树左叶子之和的和,而子树左叶子之和可以通过递归函数本身获取。这是个后序遍历

int leftValue = sumOfLeftLeaves(root->left);    // 左

int rightValue = sumOfLeftLeaves(root->right);  // 右

int sum = leftValue + rightValue;               // 中

return sum;

然而,这个代码是错误的!!! 

因为不断递归遍历下去,总会递归遍历到空节点,然后返回0.我们没有任何方式判断某个节点是左叶子并进行值的收集,因此上面的代码总会返回0.

添加上判断某个节点是左叶子节点并进行值收集的代码逻辑即可。问题又来了,如何判断?

判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子 

如果该节点的左孩子不为空,该节点的左孩子节点的左右孩子为空,则找到了一个左叶子,判断代码如下 

if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
    左叶子节点处理逻辑
}

将这部分代码逻辑加上。注意左叶子节点只可能是一个“左子树”,其值只可能是“左子树左叶子之和”,故如果找到了左叶子,用这个值覆盖掉leftValue即可,不需要覆盖rightValue.

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == 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;
    }
};

回顾总结 

初步涉及回溯

在递归三部曲的时候一定要小心,三部曲每个步骤都应该细品 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林沐华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值