代码随想录 10.09 || 二叉树 LeetCode 110.平衡二叉树、257.二叉树的所有路径、404. 左叶子之和

110.平衡二叉树

        给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一课高度平衡的二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。在开始题目之前,需要明确二叉树的深度和高度,是两种不同的概念。二叉树的深度,指根节点到当前节点的最长路径;二叉树的高度,指当前节点到叶子节点的最长路径。所以,在104.二叉树的最大深度中,我们通过求根节点的高度,进而得到二叉树的最大深度,因为根节点的高度就是这颗二叉树的最大深度。

        求深度,是自上而下;求高度,是自下而上。因此,本题不能使用层序遍历,因为层序遍历是自上而下。根据平衡二叉树的定义,所有子树都是平衡二叉树,使用后序遍历,求左右子树的高度,并判断是否为平衡二叉树。为什么使用后序遍历?需要先获得左右孩子节点的信息,才能判断当前节点的深度,所以使用左右根的顺序,收集孩子节点信息。

class Solution { // 后序遍历,迭代法实现
public:
    int getHeight(TreeNode* node) { // 1. 明确形参列表,根节点
        if (node == nullptr) return 0; // 2. 明确终止条件,在当前节点为nullptr时,终止递归

        // 3. 明确递归的基本逻辑
        int leftHeight = getHeight(node->left); // 向左子树递归,求深度
        if (leftHeight == -1) return -1; // 判断以当前节点为根节点的二叉树是否为平衡二叉树

        int rightHeight = getHeight(node->right); // 向右子树递归,求深度
        if (rightHeight == -1) return -1; // 判断以当前节点为根节点的二叉树是否为平衡二叉树

        // 返回左右子树差的绝对值,判断是否为平衡二叉树,返回 -1 or 深度
        return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
    }

    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};

        递归至二叉树的叶子节点,从下至上计算高度,将孩子节点的信息逐层返回至父节点,同时判断各个二叉树是否为平衡二叉树,如果存在一个非平衡二叉树,返回-1。明确递归的三个步骤:1.确定参数;2.确定终止条件;3.确定基本递归逻辑。一如递归深似海!

257. 二叉树的所有路径

        给定一个二叉树,返回所有从根节点到叶子节点的路径。说明:叶子节点是指没有子节点的节点。返回类型为vecotr的容器。根据题意,需要从根节点出发至各个叶子节点,记录路径,所以采用前序遍历,根左右。存在一个明显的回溯的过程,by the way,这是我第一次接触回溯。在此借用代码随想录中的示例图片。

代码随想录 10.09 || 二叉树 LeetCode 110.平衡二叉树、257.二叉树的所有路径、404. 左叶子之和_第1张图片

        从图中可以看出,从根节点出发,顺着根左右的顺序:先遍历最左的叶子节点,记录路径;回溯至其最左叶子节点的父节点;遍历其右节点。如此往复,直至遍历整棵树,递归->回溯->递归->回溯->......。

class Solution { // 详细版
private:
    void traversal(TreeNode* node, vector& path, vector& result) {

        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]);
            result.push_back(sPath);
            return ;
        }

        if (node->left) {
            traversal(node->left, path, result);
            path.pop_back();
        }

        if (node->right) {
            traversal(node->right, path, result);
            path.pop_back();
        }
    }

public:
    vector binaryTreePaths(TreeNode* root) {
        vector result;
        vector path;
        if (root == nullptr) return result;
        traversal(root, path, result);
        return result;
    }
};

class Solution { // 精简版
private:
    void traversal(TreeNode* cur, string path, vector& result) {
        path += to_string(cur->val);
        if (cur->left == NULL && cur->right == NULL) {
            result.push_back(path);
            return;
        }
        if (cur->left) traversal(cur->left, path + "->", result); // 左
        if (cur->right) traversal(cur->right, path + "->", result); // 右
    }

public:
    vector binaryTreePaths(TreeNode* root) {
        vector result;
        string path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;

    }
};

        我们选择前序遍历 + 递归的方式解决本题,在详细版的代码中:

        1. 明确形参列表,二叉树的根节点、用于记录路径的整型 vector 变量 path、作为结果集的字符串型 vector 变量 result,注意形参列表使用的是引用传递;

        2. 明确终止条件,当前节点没有孩子节点时,递归终止;

        3.明确基本递归逻辑,当满足终止条件时,将 path 中记录的路径转换为字符串,添加进结果集 result 中,此时我们得到了一条从根节点到叶子节点的路径;然后需要回溯至父节点,查看父节点的其他节点是否还有路径,path 记录了当前的路径,向深处递归时,向 path 中添加元素,回溯时,则从 path 中弹出元素。这个弹出的操作,就对应着回溯。

        在精简版的代码中,使用的形参类型和详细版不同,path 没有使用引用传递,而是值传递,意味着在每次递归中,都会创立一个 path 的副本,不同递归次数之间的 path 互不影响,在同一个递归中,path 是相同的。回溯的过程,精简版本的代码隐藏了回溯的过程,对于新手不友好(说的就是我)。

        除了递归方法,还可以使用前序遍历 + 迭代的方式解决本题。

class Solution { // 前序遍历,迭代法
public:
    vector binaryTreePaths(TreeNode* root) {
        stack treeSt; // TreeNode* 类型的栈,用于遍历二叉树
        stack pathSt; // string 类型的栈,用于记录当前节点的路径
        vector 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;
    }
};

        忽然发现,前序遍历迭代法也有模板,和层序遍历的模板还差不太多,前者使用栈,后者使用队列。treeSt 负责遍历二叉树,pathSt 负责记录路径,如果当前节点为叶子节点,将 pathSt 中的栈顶元素,对应的是从根节点至当前叶子节点的路径,添加进结果集中。

404.左叶子之和

        给定二叉树的根节点 root,返回其所有左叶子之和。需要明确,左叶子节点,需要判断当前节点是否为左节点,还需要判断当前节点是否为叶子节点。层序遍历秒了,前序递归法秒了,前序迭代法秒了,前序迭代和层序遍历真的很像。

class Solution { // 层序遍历
public:
    int sumOfLeftLeaves(TreeNode* root) {
        queue que;
        if (root != nullptr) que.push(root);
        int sum = 0;
        while (!que.empty()) {
            int size = que.size();
            while (size--) {
                TreeNode* node = que.front();
                que.pop();

                if (node->left) {
                    que.push(node->left);
                    
                    if (node->left->left == nullptr && node->left->right == nullptr) sum += node->left->val;
                }

                if (node->right) que.push(node->right);
            }
        }
        return sum;
    }
};

class Solution { // 前序递归
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == nullptr) return 0;

        int leftValue = sumOfLeftLeaves(root->left);
        int rightValue = sumOfLeftLeaves(root->right);
        
        if (root->left && !root->left->left && !root->left->right) {
            leftValue += root->left->val;
        }

        int sum = leftValue + rightValue;
        return sum;
    }
};

class Solution { // 前序迭代
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack st;
        if (root != nullptr) st.push(root);
        int sum = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            
            if (node->right) st.push(node->right);

            if (node->left) {
                st.push(node->left);

                if (node->left->left == nullptr && node->left->right == nullptr) {
                    sum += node->left->val;
                }
            }
        }
        return sum;
    }
};

你可能感兴趣的:(算法,leetcode,数据结构)