代码随想录day18-二叉树(6)

代码随想录day18-二叉树(6)

1、LeetCode 404 左叶子之和

题目分析:

本题跟之前的题目有一点而不同,要判断左叶子结点,靠自己的力量是不能判断的,需要借助父节点才能判断是不是左叶子结点。由于要计算左叶子之和,所以要采用后序遍历。
当左孩子就是一个叶子结点的时候,此时的左孩子的值就是左子树的所有左叶子结点的值。

题目解答:
class Solution {
public:
    int getSum(TreeNode* node) {
        if (node == nullptr) return 0;  // 递归的终止条件

        int leftSum = getSum(node->left);  // 左
        if (node->left && node->left->left == nullptr && node->left->right == nullptr) {
            leftSum = node->left->val;  // 如果左孩子就是叶子结点,此时leftSum就是结点的值
        }
        int rightSum = getSum(node->right);  // 右
        int sum = leftSum + rightSum;  // 中
        return sum;
    } 

    int sumOfLeftLeaves(TreeNode* root) {
        // 本题可以使用后序遍历,使用递归实现
        return getSum(root);
    }
};

本题也可以简化一下:

class Solution {
public:
    int getSum(TreeNode* node) {
        if (node == nullptr) return 0;  // 递归的终止条件
        if (node->left == nullptr) return getSum(node->right);  // 左结点为空,最终的答案就是右结点中得到
        if (node->left != nullptr && node->left->left == nullptr && node->left->right == nullptr) {
            return node->left->val + getSum(node->right);  // 左结点的左结点为空,此时的答案就是左结点加上右边的的和
        }
        return getSum(node->left) + getSum(node->right);  // 除开上述情况,就是左边的和加右边的和
    } 

    int sumOfLeftLeaves(TreeNode* root) {
        // 本题可以使用后序遍历,使用递归实现
        return getSum(root);
    }
};

也可以使用迭代法实现,用前序遍历,后序遍历以及层序遍历都是可以的。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        if (root == nullptr) return 0;
        st.push(root);
        int result = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
                result += node->left->val;  // 如果存在左叶子的情况,就将其记录下来。
            }
            if (node->right) st.push(node->right);
            if (node->left) st.push(node->left);
        }
        return result;
    }
};

本题第一次见到还要使用父节点的来判断子节点的情况,希望能拓宽思路。

2、LeetCode 513 找树左下角的值

题目分析:

本题需要寻找最后一行最左边的结点,那么使用递归如何才能找到最后一行呢?其实跟我们之前做的二叉树的最大深度一样,使用前序遍历和后序遍历都可以得到最大的深度,然后得到最大深度的一层的最左边的值即可。
本题没有中间结点的处理逻辑,所以使用前序遍历和后序遍历都可以,不需要进行中间结点的处理。

注意: 这个题和上面的题的判断方式不一样,上面明确说了是【左叶子结点的值】,而本题则是【最后一层的最左边的结点】,需要区分的是最后一层的最左边结点也有可能是右叶子结点,不一定是左叶子结点。

递归三部曲:

  • 确定递归函数的参数和返回值
    参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。 这里就不需要返回值了,所以递归函数的返回类型为void,另外还需要记录结果。此外,还需要定义一个全局变量maxDepth来存最大的深度。
    代码如下:
int maxDepth = INT32_MIN;  // 定义一个最大的层
void traverse(TreeNode* node, int depth, int& result) {}

  • 确定终止条件
    当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。
       if (node->left == nullptr && node->right == nullptr) {
            // 如果是叶子结点
            if (depth > maxDepth) {  // 如果当前层大于了最大的层,就进行更新
            // 这样就保证了只有当前层的第一个值,也就是最左边的值可以被得到
                maxDepth = depth;
                result = node->val;  // 更新值
            }
            return;
        }
  • 单层递归的处理逻辑(注意回溯的体现)
    如果左结点不为空的话,首先层数增加1,然后遍历左子树的所有情况,找到最左边的结点。当递归函数运行完之后,说明当前结点的左边都弄完了,需要看一下右边的情况,此时的深度需要减1,这就是回溯的过程。右结点同理。
        // 中
        if (node->left) {  // 左
            depth++;
            traverse(node->left, depth, result);
            depth--;  // 回溯,体现回溯的过程
        }
        if (node->right) {  // 右
            depth++;
            traverse(node->right, depth, result);
            depth--;  // 回溯
        }
题目解答:

递归法:

class Solution {
public:
    int maxDepth = 0;  // 定义一个最大的层
    void traverse(TreeNode* node, int depth, int& result) {
        if (node->left == nullptr && node->right == nullptr) {
            // 如果是叶子结点
            if (depth > maxDepth) {  // 如果当前层大于了最大的层,就进行更新
            // 这样就保证了只有当前层的第一个值,也就是最左边的值可以被得到
                maxDepth = depth;
                result = node->val;  // 更新值
            }
            return;
        }
        // 中
        if (node->left) {  // 左
            depth++;
            traverse(node->left, depth, result);
            depth--;  // 回溯,体现回溯的过程
        }
        if (node->right) {  // 右
            depth++;
            traverse(node->right, depth, result);
            depth--;  // 回溯
        }
    }

    int findBottomLeftValue(TreeNode* root) {
        int result;
        int depth = 1;  // 这里depth设置为0和1都是可以的
        traverse(root, depth, result);
        return result;
    }
};

上述代码可以简化来隐藏回溯的过程:

// 中
if (node->left) traverse(node->left, depth + 1, result);  // 左
if (node->right) traverse(node->right, depth + 1, result);  // 右

这里传进的参数是depth + 1,既保证了遍历到下一层的层数增加,同时由于depth不是引用,所以当递归函数执行完,depth不会加1,就是在这里隐藏了回溯的过程。

此外,本题可以使用层序遍历来做,非常简单,直接使用模板即可。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        //  可以直接使用层序遍历
        queue<TreeNode*> que;
        que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> temp;
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                temp.emplace_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.emplace_back(temp);
        }
        return result[result.size() - 1][0];  // 最后一行的第一个元素就是我们要的值
    }
};

总结: 本题也是一个回溯的题目,和之前使用前序遍历求二叉树的最大深度非常类似,这里在这里把那一道题目重新写一遍。
使用前序遍历求二叉树的最大深度:

class Solution {
public:
    int result = 0;
    void traverse(TreeNode* node, int depth) {
        // 这里的中要放在判断叶子结点之前,不然会漏掉叶子结点
        result = result < depth ? depth : result;  // 只有当前的depth大于result才进行更新

        // 递归的终止条件
        if (node->left == nullptr && node->right == nullptr) return;

        // 左
        if (node->left) {
            depth++;  // 深度先增加
            traverse(node->left, depth);
            depth--;  // 回溯
        }

        // 右
        if (node->right) {
            depth++;  // 深度先增加
            traverse(node->right, depth);
            depth--;  // 回溯
        }
    }

    int maxDepth(TreeNode* root) {
        int depth = 1;
        traverse(root, depth);
        return result;
    }
};

这里中间逻辑的处理要放在叶子结点的判断之前,否则会漏掉叶子结点。跟二叉树的所有路径的处理是一样的。
对比二叉树的最大深度以及最左边结点以及二叉树的所有路径三个题目,三个题目都用了回溯的思想,简单来说,就是走到深处需要退回到之前的结点,然后进行另一个方向的遍历。前两个题目非常之类似,就是中间结点的处理不太一样,其实也就是提前设置一个值,然后进行更新即可。
希望能通过这几个题对回溯在二叉树的应用有个初步的感觉。

你可能感兴趣的:(代码随想录刷题,算法,leetcode,数据结构)