第六章 二叉树 part04

二叉树 part04

题目描述

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

输入: root = [2,1,3]
输出: 1

示例 2:

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

解题思路

递归法思路

大致思路:

通过深度优先搜索(DFS)确保了树的每个节点都被访问,且在访问过程中动态更新最大深度和对应的节点值,直到找到最底层最左边的节点。

递归函数 traversal 说明:

  1. 函数参数和返回值

    • 参数:函数接收两个参数,一个是当前节点的指针 root ,另一个是当前的深度 depth

    • 返回值:没有显示的返回值,是通过修改全局变量 maxDepthresult 来记录相关信息

  2. 递归函数的终止条件:

    • 当 root 指向的节点是叶子节点时,递归终止,此时函数会检查当前深度 depth 是否大于之前记录的最大深度 maxDepth,如果是,更新 maxDepthresult
  3. 递归函数的单层逻辑:

    • 如果当前节点不是叶子节点,函数会检查是否存在子左节点。如果有,递归调用 traversal 函数,传入左子节点和增加后的深度(depth + 1
    • 如果当前节点存在右子节点,也会递归调用 traversal 函数,传入右子节点和增加后的深度

整体流程:

  • 主函数 findBottomLeftValue 从根节点开始调用 traversal 函数,初始深度为0。
  • traversal 函数通过递归遍历树的每个节点,每次递归时深度增加。
  • 当遇到叶子节点时,检查并更新最大深度和最底层最左边的节点的值。
  • 递归完成后,result 变量中存储的就是最底层最左边的节点的值,由 findBottomLeftValue 函数返回。

代码实现

测试地址:https://leetcode.cn/problems/find-bottom-left-tree-value/

递归

class Solution {
public:
    // 记录当前找到的最大深度
    int maxDepth = INT_MIN;
    // 记录最底层最左边的节点的值
    int result;

    // 递归函数,用于遍历树并找到最底层最左边的节点
    void traversal(TreeNode *root, int depth) {
        // 如果当前节点是叶子节点
        if (root->left == nullptr && root->right == nullptr) {
            // 如果当前深度大于之前记录的最大深度
            if (depth > maxDepth) {
                // 更新最大深度
                maxDepth = depth;
                // 记录当前节点的值
                result = root->val;
            }
            // 返回,结束当前递归分支
            return;
        }
        // 如果当前节点有左子节点,递归遍历左子树,深度加1
        if (root->left) {
            traversal(root->left, depth + 1);
        }
        // 如果当前节点有右子节点,递归遍历右子树,深度加1
        if (root->right) {
            traversal(root->right, depth + 1);
        }
        // 返回,结束当前递归分支
        return;
    }

    // 主函数,用于找到并返回最底层最左边的节点的值
    int findBottomLeftValue(TreeNode *root) {
        // 从根节点开始递归遍历树
        traversal(root, 0);
        // 返回找到的最底层最左边的节点的值
        return result;
    }
};

迭代法:

class Solution {
public:
    // 函数用于找到二叉树中最底层最左边的节点的值
    int findBottomLeftValue(TreeNode *root) {
        // 使用队列进行层序遍历
        queue<TreeNode *> que;
        // 如果根节点不为空,将其加入队列
        if (root != nullptr)
            que.push(root);
        // 初始化结果为0,用于存储最底层最左边的节点的值
        int result = 0;
        // 当队列不为空时,循环执行以下操作
        while (!que.empty()) {
            // 获取当前队列的大小,即当前层的节点数
            int size = que.size();
            // 遍历当前层的所有节点
            for (int i = 0; i < size; i++) {
                // 取出队列的头部节点
                TreeNode *node = que.front();
                que.pop();
                // 如果是当前层的最左节点(即第一个节点),则记录其值
                if (i == 0)
                    result = node->val;
                // 如果当前节点有左子节点,将其加入队列
                if (node->left)
                    que.push(node->left);
                // 如果当前节点有右子节点,将其加入队列
                if (node->right)
                    que.push(node->right);
            }
        }
        // 返回找到的最底层最左边的节点的值
        return result;
    }
};

路径总和

题目描述

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

解题思路

递归法思路

递归函数说明:

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

    • 参数:函数接收两个参数,当前节点的指针 cur 和剩余的和 count
    • 返回值:函数返回一个bool值,用于判断是否存在一条从当前节点到叶子节点的路径,其路径和等于剩余的和
  2. 递归函数的终止条件

    • cur 指向的节点是 叶子节点时,递归终止,说明本条路径已到达最低处,判断此时剩余的和 count 是否为0,如果是的话返回true,否则返回false
  3. 递归函数的单层逻辑

    • 如果当前节点存在左子节点,减少剩余的和,并递归调用traversal函数检查左子树。如果左子树中存在满足条件的路径,直接向上返回true,否则回溯,恢复剩余的和。
    • 如果当前节点存在右子节点,减少剩余的和,并递归调用traversal函数检查右子树。如果右子树中存在满足条件的路径,直接向上返回true,否则回溯,恢复剩余的和。
    • 如果左右字数都没有合适的路径,返回false

整体流程:

  • 主函数 hasPathSum 从根节点开始调用 traversal 函数,初始剩余和为 targetSum 减去根节点的值。
  • traversal 函数通过递归遍历树的每个节点,每次递归时更新剩余的和。
  • 当遇到叶子节点时,检查剩余的和是否为0,以确定是否存在满足条件的路径。
  • 递归完成后,返回结果,表示是否存在这样的路径。

迭代法思路

核心操作说明:

当栈不为空时,执行以下操作:

  • 弹出栈顶元素,记为 node,其中 node.first 是节点,node.second 是到该节点的路径和。
  • 如果 node.first 是叶子节点(即没有左右子节点)且路径和等于 sum,则返回 true
  • 如果 node.first 有右子节点,将右子节点及其新的路径和(当前路径和加上右子节点的值)压入栈中。
  • 如果 node.first 有左子节点,将左子节点及其新的路径和压入栈中。

代码实现

测试地址:https://leetcode.cn/problems/path-sum/

递归法:

class Solution {
private:
    // 递归函数,用于检查是否存在从当前节点到叶子节点的路径,其路径和等于给定的目标和
    bool traversal(TreeNode *cur, int count) {
        // 如果当前节点是叶子节点,且剩余的和为0,返回true
        if (!cur->left && !cur->right && count == 0)
            return true;
        // 如果当前节点是叶子节点,但剩余的和不等于0,返回false
        if (!cur->left && !cur->right)
            return false;

        // 如果当前节点有左子节点
        if (cur->left) {
            // 减少剩余的和,递归检查左子树
            count -= cur->left->val;
            if (traversal(cur->left, count))
                return true;
            // 回溯,恢复剩余的和
            count += cur->left->val;
        }
        // 如果当前节点有右子节点
        if (cur->right) {
            // 减少剩余的和,递归检查右子树
            count -= cur->right->val;
            if (traversal(cur->right, count))
                return true;
            // 回溯,恢复剩余的和
            count += cur->right->val;
        }
        // 如果左右子树都没有找到合适的路径,返回false
        return false;
    }

public:
    // 主函数,用于检查从根节点到叶子节点是否存在一条路径,其路径和等于targetSum
    bool hasPathSum(TreeNode *root, int targetSum) {
        // 如果根节点为空,直接返回false
        if (root == nullptr)
            return false;
        // 从根节点开始递归检查,初始剩余和为targetSum减去根节点的值
        return traversal(root, targetSum - root->val);
    }
};

迭代法:

class Solution {
public:
    // 检查是否存在从根节点到叶子节点的路径,其路径和等于给定的sum
    bool hasPathSum(TreeNode *root, int sum) {
        // 如果根节点为空,则不存在路径,返回false
        if (root == nullptr)
            return false;

        // 创建一个栈,用于存储节点及其当前路径和的pair
        stack<pair<TreeNode *, int>> st;
        // 将根节点及其值压入栈中
        st.push(pair<TreeNode *, int>(root, root->val));

        // 当栈不为空时,继续遍历
        while (!st.empty()) {
            // 弹出栈顶元素
            pair<TreeNode *, int> node = st.top();
            st.pop();

            // 如果当前节点是叶子节点且路径和等于sum,返回true
            if (!node.first->left && !node.first->right && sum == node.second)
                return true;

            // 如果当前节点有右子节点,将右子节点及其新的路径和压入栈中
            if (node.first->right) {
                st.push(pair<TreeNode *, int>(node.first->right, node.second + node.first->right->val));
            }

            // 如果当前节点有左子节点,将左子节点及其新的路径和压入栈中
            if (node.first->left) {
                st.push(pair<TreeNode *, int>(node.first->left, node.second + node.first->left->val));
            }
        }

        // 如果遍历完所有可能的路径后都没有找到满足条件的路径,返回false
        return false;
    }
};

路径总和 II

题目描述

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

解题思路

核心思路

  1. 递归函数的参数和返回值:函数接受当前节点 cur 和剩余路径和 count 作为参数,无返回值。
  2. 递归函数的终止条件:当遇到叶子节点且剩余路径和为0时,将当前路径添加到结果中。如果遇到叶子节点但剩余路径和不为0,则直接返回。
  3. 递归函数的单层逻辑:对于每个节点,首先尝试遍历其左子树,更新路径和剩余路径和,然后递归调用左子树。之后进行回溯,恢复路径和。接着尝试遍历右子树,同样更新路径和剩余路径和,递归调用右子树,然后进行回溯。这样,每个节点都会尝试其左右子树,直到找到所有满足条件的路径。

代码实现

测试地址:https://leetcode.cn/problems/path-sum-ii/

class Solution {
private:
    // 存储所有满足条件的路径
    vector<vector<int>> result;
    // 存储当前路径
    vector<int> path;

    // 递归函数:遍历树并寻找满足条件的路径
    void traversal(TreeNode *cur, int count) {
        // 终止条件:如果当前节点是叶子节点且剩余路径和为0,则将当前路径添加到结果中
        if (!cur->left && !cur->right && count == 0) {
            result.push_back(path);
            return;
        }

        // 如果当前节点是叶子节点但剩余路径和不等于0,则返回,不添加路径
        if (!cur->left && !cur->right)
            return;

        // 单层逻辑:遍历左子树
        if (cur->left) {
            // 添加左子节点的值到当前路径,并更新剩余路径和
            path.push_back(cur->left->val);
            count -= cur->left->val;
            // 递归调用左子树
            traversal(cur->left, count);
            // 回溯:恢复路径和
            count += cur->left->val;
            path.pop_back();
        }

        // 单层逻辑:遍历右子树
        if (cur->right) {
            // 添加右子节点的值到当前路径,并更新剩余路径和
            path.push_back(cur->right->val);
            count -= cur->right->val;
            // 递归调用右子树
            traversal(cur->right, count);
            // 回溯:恢复路径和
            count += cur->right->val;
            path.pop_back();
        }
        // 返回,结束当前节点的处理
        return;
    }

public:
    // 主函数:找到所有从根到叶子的路径,其路径和等于targetSum
    vector<vector<int>> pathSum(TreeNode *root, int targetSum) {
        // 如果根节点为空,返回空结果
        if (root == nullptr)
            return result;
        // 添加根节点的值到路径,并更新剩余路径和
        path.push_back(root->val);
        // 从根节点开始递归遍历
        traversal(root, targetSum - root->val);
        // 返回所有找到的路径
        return result;
    }
};

从中序与后序遍历序列构造二叉树

题目描述

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例 1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

解题思路

整体逻辑: 利用中序和后序遍历的特性(中序遍历可以确定左右子树的元素分布,后序遍历可以确定根节点),逐步构建出整个二叉树。

递归函数说明:

  1. 递归函数的参数和返回值traversal 函数接受中序和后序遍历数组的起始和结束索引,以及数组本身。返回值是构建的子树的根节点。
  2. 递归函数的终止条件:如果后序遍历数组为空(即起始索引等于结束索引),则返回 NULL。如果后序数组只有一个元素,直接返回根节点。
  3. 递归函数的单层逻辑:首先确定当前子树的根节点(后序遍历的最后一个元素),然后在中序遍历中找到根节点的位置,以此分割中序和后序数组为左右两部分。接着递归地构建左子树和右子树,并将它们分别设置为当前根节点的左右子节点。最后返回当前子树的根节点。

代码实现

测试地址:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/

class Solution {
private:
    // 递归函数:根据中序和后序遍历结果构建二叉树
    TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
        // 终止条件:如果后序遍历数组为空,返回NULL
        if (postorderBegin == postorderEnd) return NULL;

        // 后序遍历的最后一个元素是当前子树的根节点
        int rootValue = postorder[postorderEnd - 1];
        TreeNode* root = new TreeNode(rootValue);

        // 如果后序数组只有一个元素,直接返回根节点
        if (postorderEnd - postorderBegin == 1) return root;

        // 在中序数组中找到根节点的位置
        int delimiterIndex;
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }

        // 切割中序数组
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割后序数组
        int leftPostorderBegin = postorderBegin;
        int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin;
        int rightPostorderBegin = postorderBegin + delimiterIndex - inorderBegin;
        int rightPostorderEnd = postorderEnd - 1;

        // 递归构建左子树和右子树
        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd);
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);

        // 返回当前子树的根节点
        return root;
    }

public:
    // 主函数:根据给定的中序和后序遍历结果构建二叉树
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }
};

从前序与中序遍历序列构造二叉树

题目描述

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

解题思路

整体思路: 利用前序和中序遍历的特性(前序遍历可以确定根节点,中序遍历可以确定左右子树的元素分布),逐步构建出整个二叉树。

递归函数说明:

  1. 递归函数的参数和返回值traversal 函数接受中序和前序遍历数组的起始和结束索引,以及数组本身。返回值是构建的子树的根节点。
  2. 递归函数的终止条件:如果前序遍历数组为空(即起始索引等于结束索引),则返回 NULL。如果前序数组只有一个元素,直接返回根节点。
  3. 递归函数的单层逻辑:首先确定当前子树的根节点(前序遍历的第一个元素),然后在中序遍历中找到根节点的位置,以此分割中序和前序数组为左右两部分。接着递归地构建左子树和右子树,并将它们分别设置为当前根节点的左右子节点。最后返回当前子树的根节点。

代码实现

测试地址:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

class Solution {
private:
    // 递归函数:根据前序和中序遍历结果构建二叉树
    TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
        // 终止条件:如果前序遍历数组为空,返回NULL
        if (preorderBegin == preorderEnd) return NULL;

        // 前序遍历的第一个元素是当前子树的根节点
        int rootValue = preorder[preorderBegin];
        TreeNode* root = new TreeNode(rootValue);

        // 如果前序数组只有一个元素,直接返回根节点
        if (preorderEnd - preorderBegin == 1) return root;

        // 在中序数组中找到根节点的位置
        int delimiterIndex;
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }

        // 切割中序数组
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割前序数组
        int leftPreorderBegin = preorderBegin + 1;
        int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin;
        int rightPreorderBegin = preorderBegin + 1 + delimiterIndex - inorderBegin;
        int rightPreorderEnd = preorderEnd;

        // 递归构建左子树和右子树
        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, preorder, leftPreorderBegin, leftPreorderEnd);
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd);

        // 返回当前子树的根节点
        return root;
    }

public:
    // 主函数:根据给定的前序和中序遍历结果构建二叉树
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (inorder.size() == 0 || preorder.size() == 0) return NULL;
        return traversal(inorder, 0, inorder.size(), preorder, 0, preorder.size());
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值