代码随想录算法训练营第十六天

目录

LeetCode.513 找树左下角的值

题目链接 找树左下角的值

题解一 递归法

解题思路

题目二 迭代法

解题思路

LeetCode.112 路径总和

题目链接 路径总和

题解

解题思路

LeetCode.113 路径总和Ⅱ

题目链接 路径总和Ⅱ

题解

解题思路

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

题目链接 从中序与后序遍历序列构造二叉树

题解

解题思路

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

题目链接 从前序与中序遍历序列构造二叉树

题解

题目链接


LeetCode.513 找树左下角的值

题目链接 找树左下角的值

题解一 递归法

class Solution {
    int maxDeep = 0;
    int leftBottomValue;
    public int findBottomLeftValue(TreeNode root) {
        leftBottomValue = root.val;
        find(root,1);
        return leftBottomValue;
    }
    public void find(TreeNode node,int deep){
        if(node == null){
            return ;
        }
        if(node.left == null && node.right == null && deep > maxDeep){
            leftBottomValue = node.val;
            maxDeep = deep;
        }
        find(node.left,deep + 1);
        find(node.right,deep + 1);
    }
}

解题思路

这段代码的核心思路是借助深度优先搜索(DFS)来找出二叉树最底层最左边节点的值。具体逻辑如下:

  1. 变量设定

    • maxDeep:用于记录当前所探寻到的最大深度,初始值设为 0。
    • leftBottomValue:用于存储最底层最左边节点的值,一开始将其初始化为根节点的值。
  2. 方法功能

    • findBottomLeftValue:作为主方法,先把leftBottomValue初始化为根节点的值,接着调用递归方法find来更新结果,最后返回结果。
    • find:这是一个递归辅助方法,会对每个节点进行遍历。当遇到叶子节点(即没有左右子节点的节点)时,检查该节点的深度是否比当前记录的最大深度maxDeep还要大。若更大,就更新maxDeepleftBottomValue。递归时,先处理左子树,再处理右子树,这样能保证在同一深度下,左子树的节点会优先被访问到。
  3. 递归策略

    • 采用先左后右的前序遍历方式,确保在每一层中,左子树的节点会最先被处理。
    • 当遍历到叶子节点时,如果该叶子节点的深度超过了之前记录的最大深度,就更新结果为该叶子节点的值。由于是先遍历左子树,所以在同一深度下,最左边的节点会最先被更新。
  4. 初始值的作用

    • 当二叉树只有一个根节点时,根节点同时也是最底层最左边的节点,此时直接返回根节点的值。

关键点

  • 利用深度优先搜索并结合递归的方式,优先遍历左子树,以此保证能找到最底层最左边的节点。
  • 通过记录最大深度,保证每次更新的节点都是当前最深层的节点。
  • 初始值的设置保证了在二叉树只有一个节点的情况下,结果依然正确。

题目二 迭代法

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        int res = 0;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);
        while(!que.isEmpty()){
            int len = que.size();
            for(int i = 0;i<len;i++){
                TreeNode tmpNode = que.poll();
                if(i == 0){
                    res = tmpNode.val;
                }
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
            }
        }
        return res;
    }
}

解题思路

这段代码的解题思路是使用广度优先搜索(BFS)层序遍历来找到二叉树最底层的最左边节点的值。以下是详细的步骤解释:

核心思路

  1. 层序遍历:利用队列逐层遍历二叉树,确保每一层的节点按从左到右的顺序被处理。
  2. 记录每层最左节点:在每一层遍历开始时,队列的第一个元素即为该层的最左边节点。通过每次处理一层时记录第一个节点的值,最终遍历到最后一层时,记录的值即为最底层的最左节点。

代码逻辑

  1. 初始化

    • res:用于存储每一层的最左节点的值,初始化为 0。
    • Queue<TreeNode>:使用队列来进行层序遍历,初始时将根节点加入队列。
  2. 层序遍历循环

    • 外层循环:每次处理一层的所有节点,循环条件为队列非空。
    • 记录当前层节点数:通过 que.size() 获取当前层的节点数量 len
    • 内层循环:遍历当前层的所有节点(共 len 个)。
      • 取出队首节点:处理当前节点 tmpNode
      • 记录最左节点:当 i == 0 时,说明当前节点是该层的第一个节点(即最左边节点),更新 res 为该节点的值。
      • 添加子节点:将当前节点的左子节点和右子节点(若存在)依次加入队列。
  3. 返回结果

    • 遍历完所有层后,res 中存储的即为最后一层的最左节点的值。

关键点

  • 层序遍历的顺序:BFS 保证了每层节点按从左到右的顺序被处理,因此每层的第一个节点即为该层的最左节点。
  • 更新机制:每次进入新的一层时,res 会被更新为该层的最左节点的值。由于最后一层是最后被处理的,因此最终 res 存储的是最后一层的最左节点的值。
  • 无需额外判断深度:层序遍历自然地从根到叶逐层处理,最后一层的最左节点会在遍历结束时被记录,无需显式跟踪深度。

LeetCode.112 路径总和

题目链接 路径总和

题解

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null){
            return false;
        }
        targetSum -= root.val;
        if(root.left == null && root.right == null){
            return targetSum  == 0;
        }
        if(root.left != null){
            boolean left = hasPathSum(root.left,targetSum);
            if(left){
                return true;
            }
        }
        if(root.right != null){
            boolean right = hasPathSum(root.right,targetSum);
                if(right){
                    return true;
                }
            }
        return false;
    }
}

解题思路

这段代码的解题思路是使用  深度优先搜索(DFS) 来判断二叉树中是否存在一条从根节点到叶子节点的路径,使得该路径上所有节点的值之和等于给定的目标和 targetSum。以下是详细的步骤解释:

核心思路

  1. 递归遍历路径:从根节点开始,递归地检查每个节点的左右子树,同时更新剩余的目标和(即 targetSum 减去当前节点的值)。
  2. 终止条件
    • 若当前节点为空,返回 false(路径不存在)。
    • 若当前节点是叶子节点(即无左右子节点),检查剩余目标和是否为 0。若为 0,则说明当前路径的和等于目标和,返回 true;否则返回 false
  3. 递归逻辑
    • 若当前节点有左子树,递归检查左子树的路径是否满足条件。若满足,直接返回 true
    • 若当前节点有右子树,递归检查右子树的路径是否满足条件。若满足,直接返回 true
    • 若左右子树均未找到满足条件的路径,返回 false

代码逻辑

  1. 处理空节点

    • 若根节点为空,直接返回 false
  2. 更新目标和

    • 将 targetSum 减去当前节点的值,得到剩余需要的和。
  3. 检查叶子节点

    • 若当前节点是叶子节点(即 root.left == null && root.right == null),检查剩余目标和是否为 0。若是,返回 true;否则返回 false
  4. 递归检查子树

    • 左子树:若左子树存在,递归调用 hasPathSum(root.left, targetSum)。若结果为 true,说明左子树中存在满足条件的路径,直接返回 true
    • 右子树:同理,若右子树存在且递归调用 hasPathSum(root.right, targetSum) 结果为 true,返回 true
  5. 最终结果

    • 若左右子树均未找到满足条件的路径,返回 false

关键点

  • 路径定义:路径必须从根节点开始,到叶子节点结束。因此,只有当节点同时满足无左右子节点时,才检查剩余和是否为 0。
  • 剪枝优化:一旦在左子树或右子树中找到满足条件的路径,立即返回 true,避免继续遍历其他路径。
  • 目标和更新:每次递归调用时,targetSum 会减去当前节点的值,确保路径和的计算正确。

LeetCode.113 路径总和Ⅱ

题目链接 路径总和Ⅱ

题解

class Solution {
    List<List<Integer>> resList = new ArrayList<>();
    List<Integer> res = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        
        if(root == null){
            return resList;
        }
        preOrderDfs(root,targetSum);
        return resList;
    }
    public void preOrderDfs(TreeNode root,int count){
        res.add(root.val);
        if(root.left == null && root.right == null){
            if(count - root.val == 0){
                resList.add(new ArrayList<>(res));
            }
            return;
        }
        if(root.left != null){
            preOrderDfs(root.left,count - root.val);
            res.remove(res.size() - 1);
        }
        if(root.right != null){
            preOrderDfs(root.right,count - root.val);
            res.remove(res.size() - 1);
        }
    }
}

解题思路

这段代码的解题思路是使用深度优先搜索(DFS)结合回溯法,找出二叉树中所有从根节点到叶子节点的路径,使得路径上节点值的和等于给定的目标和 targetSum。以下是详细的步骤解释:

核心思路

  1. 递归遍历路径:从根节点开始,递归地遍历每个节点,同时维护当前路径和剩余目标和。
  2. 回溯法维护路径:使用一个列表 res 记录当前路径上的节点值。在递归返回时,需要移除当前节点的值(回溯),以确保路径的正确性。
  3. 路径记录条件:当遍历到叶子节点时,检查剩余目标和是否为 0。若为 0,则将当前路径加入结果列表 resList

代码逻辑

  1. 初始化

    • resList:存储所有满足条件的路径。
    • res:存储当前递归路径上的节点值。
  2. 主方法 pathSum

    • 处理根节点为空的情况,直接返回空列表。
    • 调用 preOrderDfs 开始深度优先搜索。
  3. 递归方法 preOrderDfs

    • 路径更新:将当前节点的值加入 res
    • 叶子节点判断:若当前节点是叶子节点,检查剩余目标和是否为 0。若是,则将当前路径的副本加入 resList
    • 递归遍历子树
      • 若左子树存在,递归处理左子树,并在返回后移除左子树节点(回溯)。
      • 若右子树存在,递归处理右子树,并在返回后移除右子树节点(回溯)。

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

题目链接 从中序与后序遍历序列构造二叉树

题解

class Solution {
    Map<Integer,Integer> map;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        map = new HashMap<>();
        for(int i = 0;i<inorder.length;i++){
            map.put(inorder[i],i);
        }
        return findNode(inorder,0,inorder.length,postorder,0,postorder.length);
    }
    public TreeNode findNode(
    int[] inorder, int inBegin,int inEnd,
    int[] postorder,int postBegin,int postEnd) {
        if(inBegin >= inEnd || postBegin >= postEnd){
            return null;
        }
        int index = map.get(postorder[postEnd - 1]);
        int leftOfLen = index - inBegin;
        TreeNode rootNode = new TreeNode(inorder[index]);
        TreeNode leftRoot = findNode(inorder,inBegin,index,postorder,postBegin,postBegin + leftOfLen);
        TreeNode rightRoot = findNode(inorder,index + 1,inEnd,postorder,postBegin + leftOfLen,postEnd -1);
        rootNode.left = leftRoot;
        rootNode.right = rightRoot;
        return rootNode;
    }
}

解题思路

这段代码的解题思路是利用 ** 中序遍历(inorder)后序遍历(postorder)** 的特性,递归地构建二叉树。以下是详细的步骤解释:

核心思路

  1. 后序遍历的特性: 后序遍历的顺序是 左子树 → 右子树 → 根节点,因此后序数组的最后一个元素必定是当前子树的根节点。

  2. 中序遍历的特性: 中序遍历的顺序是 左子树 → 根节点 → 右子树,因此可以通过根节点将中序数组划分为 左子树部分 和 右子树部分

  3. 递归构建

    • 通过后序数组找到根节点的值。
    • 在中序数组中定位根节点的位置,确定左右子树的节点数量。
    • 递归构建左右子树,更新对应的索引范围。

代码逻辑

  1. 哈希表预处理

    • 使用哈希表 map 存储中序数组中每个值的索引,以便快速定位根节点在中序数组中的位置。
  2. 主方法 buildTree

    • 初始化哈希表,将中序数组的值和索引存入 map
    • 调用递归函数 findNode,传入中序和后序数组的初始范围。
  3. 递归方法 findNode

    • 终止条件:若索引范围无效(inBegin >= inEnd 或 postBegin >= postEnd),返回 null
    • 确定根节点:后序数组的最后一个元素 postorder[postEnd - 1] 即为当前子树的根节点。
    • 划分左右子树
      • 在中序数组中找到根节点的索引 index
      • 计算左子树的节点数量 leftOfLen = index - inBegin
    • 递归构建左右子树
      • 左子树:中序范围为 [inBegin, index),后序范围为 [postBegin, postBegin + leftOfLen)
      • 右子树:中序范围为 [index + 1, inEnd),后序范围为 [postBegin + leftOfLen, postEnd - 1)

关键点

  1. 索引范围的确定

    • 后序数组中,左子树的长度与中序数组中左子树的长度相同,因此可以通过 leftOfLen 划分左右子树的范围。
    • 注意递归时的索引边界:左闭右开区间(如 [inBegin, inEnd)),确保不越界。
  2. 递归逻辑

    • 每次递归时,后序数组的最后一个元素作为根节点,通过中序数组划分左右子树,缩小问题规模。

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

题目链接 从前序与中序遍历序列构造二叉树

题解

class Solution {
    Map<Integer,Integer> map;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        map = new HashMap<>();
        for(int i = 0;i<inorder.length;i++){
            map.put(inorder[i],i);
        }
        return findNode(preorder,0,preorder.length,inorder,0,inorder.length);
    }
    public TreeNode findNode(int[] preorder,int preBegin,int preEnd,int[] inorder,int inBegin,int inEnd){
        if(preBegin >= preEnd || inBegin >= inEnd){
            return null;
        }

        int rootIndex = map.get(preorder[preBegin]);
        TreeNode rootNode = new TreeNode(inorder[rootIndex]);
        int leftOfLen = rootIndex - inBegin;
        rootNode.left = findNode(preorder,preBegin + 1,preBegin + 1 + leftOfLen,inorder,inBegin,rootIndex);
        rootNode.right = findNode(preorder,preBegin + 1 + leftOfLen,preEnd,inorder,rootIndex + 1,inEnd);
        return rootNode;
    }
}

题目链接

思路如LeetCode.106。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值