代码随想录算法训练营第十七天 | 平衡二叉树、二叉树的所有路径、左叶子之和

110 平衡二叉树

文档讲解:代码随想录 (programmercarl.com)

视频讲解:后序遍历求高度,高度判断是否平衡 | LeetCode:110.平衡二叉树_哔哩哔哩_bilibili

状态:递归法做出来了。建议再看一下原理。迭代法就是把写一个求高度的函数,遍历每个节点时,通过对比其左右子树的高度来判断是否平衡,没什么意思。

思路

深度可以从上到下去查,所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)

有的同学一定疑惑,为什么104.二叉树的最大深度 (opens new window)中求的是二叉树的最大深度,也用的是后序遍历。那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。

递归

既然要求比较高度,必然是要后序遍历

  1. 明确递归函数的参数和返回值

    参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度

    那么如何标记左右子树是否差值大于1呢?如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。

    // -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
    int getHeight(TreeNode* node)
    
  2. 明确终止条件

    递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0。

    if (node == NULL) {return 0;}
    
  3. 明确单层递归的逻辑

    如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。

    分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

    int leftHeight = getHeight(node->left); // 左
    if (leftHeight == -1) return -1;
    int rightHeight = getHeight(node->right); // 右
    if (rightHeight == -1) return -1;
    
    int result;
    if (abs(leftHeight - rightHeight) > 1) {  // 中
        result = -1;
    } else {
        result = 1 + max(leftHeight, rightHeight); // 以当前节点为根节点的树的最大高度
    }
    
    return result;
    

自己写的代码

class Solution {
public:
    int getHeight(TreeNode* node){
        if(node == nullptr) return 0;
        int leftHeight = getHeight(node->left);
        int rightHeight = getHeight(node->right);

        if(leftHeight == -1) return -1;
        if(rightHeight == -1) return -1;

        if(leftHeight - rightHeight > 1 || rightHeight - leftHeight > 1){
            return -1;
        }
        else{
            return (max(leftHeight, rightHeight) + 1);
        }
    }
    bool isBalanced(TreeNode* root) {
        int height = getHeight(root);
        if(height == -1) return false;
        else return true;
    }
};

257 二叉树的所有路径

文档讲解:代码随想录 (programmercarl.com)

视频讲解:递归中带着回溯,你感受到了没?| LeetCode:257. 二叉树的所有路径_哔哩哔哩_bilibili

状态:递归法能写出来,迭代法没想到。

思路

递归法

!不能把递归的出口条件设置为node==NULL,否则就会出现下图情况:指针到了元素2左子树时保留路径,即"1–>2",这是不对的。只有当某个节点都没有左右孩子时才能保留路径,而图中元素2还有右孩子。

在这里插入图片描述

所以递归的出口条件应该为node–>left == NULL && node->right == NULL

因为路径的保留要从根节点到叶子节点逐一保留,需要“中左右”的顺序来实现,所以用前序遍历前序遍历用形参来传值,而不用返回值

class Solution {
public:
    //前序遍历
    //这里保留路径之所以用vector<int>而不用string,是因为元素值可能为一位数也可能为两位数,回溯时需要从路径中取出元素值,用string就不好判断去掉一个字符还是两个字符了。
    void preorderTraversal(TreeNode* node, vector<int>& path, vector<string>& res){
        path.push_back(node->val);
        //到了叶子节点,把路径存储下来
        if(node->left == nullptr && node->right == nullptr){
            string sPath;
            for(int i = 0; i < path.size() - 1; ++i){
                sPath += to_string(path[i]);
                sPath += "->";
            }
            sPath += to_string(path[path.size() - 1]);
            res.push_back(sPath);
        }

		// !!!注:回溯是在左节点递归结束后回溯,右节点递归结束后也回溯。(两个if里)
        if(node->left){
            preorderTraversal(node->left, path, res);
            path.pop_back();//!注意要回溯
        }
        if(node->right){
            preorderTraversal(node->right, path, res);
            path.pop_back();//!注意要回溯
        }
    } 

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<int> path;
        vector<string> res;
        if(root == nullptr) return res;
        preorderTraversal(root, path, res);
        return res;
    }
};

注意本题需要回溯,如下图,回溯和递归是一一对应的,有一个递归,就要有一个回溯

在这里插入图片描述

迭代法(前序遍历)

这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。路径栈和元素栈里的元素一一对应,个数相同,同时入栈出栈。当指针指向两个栈的同一位置时,路径栈的元素表示的是:从根节点到元素栈的元素的路径

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        stack<TreeNode*> treeST;// 元素栈:保存树的遍历节点
        stack<string> pathST;// 路径栈:保存遍历路径的节点
        vector<string> result;// 保存最终路径集合

        if(root == nullptr) return result;
        treeST.push(root); pathST.push(to_string(root->val));

        while(!treeST.empty()){
            TreeNode* node = treeST.top(); treeST.pop();// 取出节点 中
            string path = pathST.top(); pathST.pop();// 取出该节点对应的路径

            if(node->left == nullptr && node->right == nullptr){// 遇到叶子节点
                result.push_back(path);
            }

            if(node->right){// 右
                treeST.push(node->right);
                pathST.push(path + "->" + to_string(node->right->val));
            }
            if(node->left){// 左
                treeST.push(node->left);
                pathST.push(path + "->" + to_string(node->left->val));
            }
        }
        return result;
    }
};

小总结

本周小结!(二叉树系列二) | 代码随想录 (programmercarl.com)

404 左叶子之和

文档讲解:代码随想录 (programmercarl.com)

视频讲解:二叉树的题目中,总有一些规则让你找不到北 | LeetCode:404.左叶子之和_哔哩哔哩_bilibili

状态:递归法做出来了,不过不能保证下次能做出来。

思路

递归法

左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点

那么判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。 如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:

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

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和

递归三部曲:

  1. 确定递归函数的参数和返回值

    判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int。使用题目中给出的函数就可以了。

  2. 确定终止条件

    如果遍历到空节点,那么左叶子值一定是0

    if (root == NULL) return 0;
    

    注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:

    if (root == NULL) return 0;
    if (root->left == NULL && root->right== NULL) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
    
  3. 确定单层递归的逻辑

    当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。代码如下:

    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;
    

整体递归代码如下

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;
    }
};

本人写的递归代码如下

class Solution {
public:
    //后序遍历:因为要将返回值作为左叶子之和
    int postorderTraversal(TreeNode* node){
        if(node == nullptr) return 0;   //叶结点,出口

        int leftSum = postorderTraversal(node->left);   //当前节点左子树的 左叶子之和
        int rightSum = postorderTraversal(node->right); //当前节点右子树的 左叶子之和

        //中:若当前节点的左孩子为叶子结点,则将该叶子节点的值 加到 该结点左子树的左叶子之和
        if(node->left != nullptr && node->left->left == nullptr && node->left->right == nullptr){
            leftSum += node->left->val;
        }
        // 当前节点的左叶子之和 = 当前节点左子树的 左叶子之和 + 当前节点右子树的 左叶子之和
        return (leftSum + rightSum);
    }

    int sumOfLeftLeaves(TreeNode* root) {
        return postorderTraversal(root);
    }
};

迭代法

前中后序、层序遍历都可以,只要把符合条件的节点加上就行。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        int result = 0;
        if(root == nullptr) return result;
        st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top();
            st.pop();
            if(node->left != nullptr && node->left->left == nullptr && node->left->right == nullptr){
                result += node->left->val;
            }

            if(node->left) st.push(node->left);
            if(node->right) st.push(node->right);
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值