代码随想录算法训练营第十八天| 513. 找树左下角的值、112. 路径总和、113. 路径总和 II、106. 从中序与后序遍历序列构造二叉树、105. 从前序与中序遍历序列构造二叉树

[LeetCode] 513. 找树左下角的值

[LeetCode] 513. 找树左下角的值 视频解释

[LeetCode] 513. 找树左下角的值 文章解释

题目:

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

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

示例 1:

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

示例 2:

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

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1 

[LeetCode] 513. 找树左下角的值

自己看到题目的第一想法

递归法:

    1. 不停的一左节点优先的顺序向下遍历每一个节点, 同时记录当前遍历的节点的深度.

    2. 同时用 maxDepth 记录当前遍历过的节点中, 深度最大的那一个. 当遍历到一个节点的深度大雨 maxDepth 时, 用 maxDepth 保存这个节点, 同时用 value 记录住节点的值.

    3. 遍历完成后返回 value.

迭代法:

    1. 层序遍历法

    2. 标记法: 需要带回溯的, 可以用标记法.

看完代码随想录之后的想法

    这一题没有出现太多的偏差

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
// 递归法:
class Solution {
    private int value;
    private int maxDepth = 0;
    public int findBottomLeftValue(TreeNode root) {
        findBottomLeftValue(root, 1);
        return value;
    }
    public void findBottomLeftValue(TreeNode root, int depth) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            if (depth > maxDepth) {
                maxDepth = depth;
                value = root.val;
                return;
            }
        }
        if (root.left != null) {
            findBottomLeftValue(root.left, depth + 1);
        }
        if (root.right != null) {
            findBottomLeftValue(root.right, depth + 1);
        }
    }
}
// 迭代法: 层序遍历. 效率低: 55.16%
class Solution {
    public int findBottomLeftValue(TreeNode root) {
        LinkedList<TreeNode> nodes = new LinkedList<>();
        TreeNode node = null;
        int size = 0;
        nodes.push(root);
        int value = 0;
        while (!nodes.isEmpty()) {
            size = nodes.size();
            value = nodes.peek().val;
            while (size-- > 0) {
                node = nodes.pop();
                if (node.left != null) {
                    nodes.addLast(node.left);
                }
                if (node.right != null) {
                    nodes.addLast(node.right);
                }
            }
        }
        return value;
    }
}
// 迭代法: 标记法. 效率低: 5.6%
class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Stack<TreeNode> nodes = new Stack<>();
        TreeNode node;
        nodes.push(root);
        int depth = 0;
        int maxDepth = 0;
        int value = 0;
        while (!nodes.isEmpty()) {
            node = nodes.pop();
            if (node != null) {
                nodes.push(node);
                nodes.push(null);
                depth++;
                if (node.right != null) {
                    nodes.push(node.right);
                } 
                if (node.left != null) {
                    nodes.push(node.left);
                }
            } else {
                node = nodes.pop();
                if (node.left == null && node.right == null) {
                    if (depth > maxDepth) {
                        maxDepth = depth;
                        value = node.val;
                    }    
                }
                depth--;
                node = null;
            }
        }
        return value;
    }
}

自己实现过程中遇到哪些困难

    无(也可能是记不得了)

[LeetCode] 112. 路径总和

[LeetCode] 112. 路径总和 文章解释

[LeetCode] 112. 路径总和 视频解释

给你二叉树的根节点 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
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

  • 树中节点的数目在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

[LeetCode] 112. 路径总和

自己看到题目的第一想法 

    1. 遍历所有节点, 并记录当前路径上所有节点的和. 如果发现当前节点为叶子结点, 并且加上当前节点的值后, 总和与要预期的总和相同, 则返回 true, 表示存在一条路径, 当前路径的所有节点的值相加起来和总和相同.

看完代码随想录之后的想法

    1. 递归法: 想法是一致的

    2. 迭代法: 我用了标记法. 随想录使用 Pair<Node, sum> 记录住了当前节点对应的路径总和的值, 这样也挺好的, 比笔记法少了几个递归, 应该是更高效的.

// 递归法
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        return hasPathSum(root, targetSum, 0);
    }

    private boolean hasPathSum(TreeNode node, int targetSum, int sum) {
        if (node == null) {
            return false;
        }
        sum += node.val;
        if (node.left == null && node.right == null && sum == targetSum) {
            return true;
        }

        return hasPathSum(node.left, targetSum, sum) || hasPathSum(node.right, targetSum, sum);
    }
}
// 迭代法
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return false;
        }
        Stack<TreeNode> nodes = new Stack<>();
        TreeNode node = null;
        nodes.push(root);
        int sum = 0;
        while (!nodes.isEmpty()) {
            node = nodes.pop();
            if (node != null) {
                nodes.push(node);
                nodes.push(null);
                sum += node.val;
                if (node.right != null) {
                    nodes.push(node.right);
                }
                if (node.left != null) {
                    nodes.push(node.left);
                }
            } else {
                node = nodes.pop();
                if (node.left == null && node.right == null && sum == targetSum) {
                    return true;
                }
                sum -= node.val;
                node = null;
            }
        }
        return false;
    }
}
// 迭代法: 封装对象.
// 将每个节点封装到 Pair 中, 
// first 为 TreeNode 本身, second 为 到这个节点的 sum 的和
// 然后用后序遍历的方式展开所有节点. 
// 这样再遇到叶子结点的时候, 检查 Pair.second == targetSum 即可
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // 未实现
    }
}

自己实现过程中遇到哪些困难

    主要是会有些迷茫. 一个一个的去套模板方法, 经常能发现有一个是能行的. 但是对于什么时候回溯, 如何回溯, 总感觉是蒙了一层薄膜的感觉. 不是特别的清晰.

[LeetCode] 113. 路径总和 II

[LeetCode] 113. 路径总和 II 文章解释

[LeetCode] 113. 路径总和 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
输出:[]

提示:

  • 树中节点总数在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

[LeetCode] 113. 路径总和 II

自己看到题目的第一想法

    1. 依旧是遍历所有节点, 但是不需要返回值了, 因为需要的节点都在路径列表里保存以及删除

    2. 遍历节点右很多种顺序, 这里前序会方便一些.

    3. 因为需要回溯, 因此这里可以使用 “递归法“ 或者 “标记法”.

看完代码随想录之后的想法

    有些忘记了, 大体上可能是差不多的. 最近的题目不是特别需要看文章解释.

// 递归法
class Solution {
    List<List<Integer>> result = null;
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        pathSum(root, targetSum, new ArrayList<Integer>(), 0);
        return result;
    }
    private void pathSum(TreeNode root, int targetSum, List<Integer> path, int sum) {
        if (root == null) {
            return;
        }
        sum += root.val;
        path.add(root.val);
        if (root.left == null && root.right == null && sum == targetSum) {
            result.add(new ArrayList<Integer>(path));
            return;
        }
        if (root.right != null) {
            pathSum(root.right, targetSum, path, sum);
            path.remove(path.size() - 1);
        }
        if (root.left != null) {
            pathSum(root.left, targetSum, path, sum);
            path.remove(path.size() - 1);
        }
    }
}
// 迭代法:
class Solution {
    List<List<Integer>> result = null;
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> nodes = new Stack<>();
        List<Integer> path = new ArrayList<>();
        int sum = 0;
        TreeNode node;
        nodes.push(root);
        while (!nodes.isEmpty()) {
            node = nodes.pop();
            if (node != null) {
                nodes.push(node);
                nodes.push(null);
                path.add(node.val);
                sum += node.val;
                if (node.right != null) {
                    nodes.push(node.right);
                }
                if (node.left != null) {
                    nodes.push(node.left);
                }
            } else {
                node = nodes.pop();
                if (node.left == null && node.right == null && sum == targetSum) {
                    result.add(new ArrayList<>(path));
                }
                path.remove(path.size() - 1);
                sum -= node.val;
                node = null;
            }
        }
        return result;
    }    
}

自己实现过程中遇到哪些困难

    无

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

[LeetCode] 106. 从中序与后序遍历序列构造二叉树 文章解释

[LeetCode] 106. 从中序与后序遍历序列构造二叉树 视频解释

题目:

给定两个整数数组 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 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

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

自己看到题目的第一想法 

    1. 很经典的题目

    2. 看过

    3. 不会...(小声一点)

看完代码随想录之后的想法

    1. 如此简单

    2. 对于从遍历结果反推二叉树, 中序遍历是决定性的存在, 因为只有中序遍历, 才能将前序遍历、中序遍历中, 根节点的左右子树的序列划分出来. 前序遍历、后序遍历有个特点就是, 除了根节点, 剩余的节点遵循了一个规律: 从左到右的顺序, 左边的都是左子树的节点, 右边的都是右子树的节点. 因此通过中序遍历, 我们可以很容易的从前序、后序遍历中拿到左右子树对应的节点.

    3. 同时, 对于前序遍历, 从左往右的每一个当前节点, 分别是当前中序遍历序列的根节点. 这里得模拟一下二叉树的遍历过程, 才能想明白. 或者我得画个图才能说清楚.

// 迭代法1: 
class Solution {
    private int postOrderIndex = 0;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        Map<Integer, Integer> nodeIndexs = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            nodeIndexs.put(inorder[i], i);
        }
        postOrderIndex = postorder.length - 1;
        return buildTree(inorder, postorder, nodeIndexs, 0, inorder.length - 1);
    }
    private TreeNode buildTree(int[] inorder, int[] postorder, Map<Integer, Integer> nodeIndexs, int startIndex, int endIndex) {
        if (startIndex > endIndex) {
            return null;
        }
        // 注意这里的 postOrderIndex--
        TreeNode root = new TreeNode(postorder[postOrderIndex--]);

        // 下面两行的顺序不能变, 一定要先遍历中序遍历的右侧部分的节点.
        // 中序遍历为 左 中 右, 而右侧部分如果是后序遍历的话, 则是 左 右 中
        // 因此对于完整的后序遍历序列, 如果按照 中 =》 右 =》 左 的顺序来回溯
        // 后序遍历从右往左, 就依次是每次迭代时的根节点.
        root.right = buildTree(inorder, postorder, nodeIndexs, nodeIndexs.get(root.val) + 1, endIndex);
        root.left = buildTree(inorder, postorder, nodeIndexs, startIndex, nodeIndexs.get(root.val) - 1);
        return root;
    }
}
// 迭代法2:
class Solution {
    private Map<Integer, Integer> nodeIndexs = null;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        nodeIndexs = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            nodeIndexs.put(inorder[i], i);
        }
        return buildTree(inorder, postorder, 0, inorder.length - 1, 0, inorder.length - 1);
    }
    private TreeNode buildTree(int[] inorder, int[] postorder,
                               int inOrderStartIndex, int inOrderEndIndex, 
                               int postOrderStartIndex, int postOrderEndIndex) {
        if (inOrderStartIndex > inOrderEndIndex) {
            return null;
        }
        int middleNodeValue = postorder[postOrderEndIndex];
        int middleNodeIndex = nodeIndexs.get(middleNodeValue);
        TreeNode root = new TreeNode(middleNodeValue);
        root.left = buildTree(inorder, postorder, inOrderStartIndex, middleNodeIndex - 1, 
                postOrderStartIndex, postOrderStartIndex + middleNodeIndex - inOrderStartIndex - 1);
        root.right = buildTree(inorder, postorder, middleNodeIndex + 1, inOrderEndIndex, 
                postOrderStartIndex + middleNodeIndex - inOrderStartIndex, postOrderEndIndex - 1);
        return root;
    }
}

自己实现过程中遇到哪些困难 

    后序和中序对应的序列区间的索引, 经常计算错误, 导致死循环.

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

[LeetCode] 105. 从前序与中序遍历序列构造二叉树 文章解释

题目:

给定两个整数数组 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 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

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

自己看到题目的第一想法

    和后序+中序遍历一样的逻辑, 修改一下获取根节点值和索引的逻辑就行.

看完代码随想录之后的想法

// 解法1: 注意每次迭代的单词循环逻辑中, 获取根节点的逻辑
class Solution {
    private int[] preorder;
    private int[] inorder;
    private Map<Integer, Integer> inorderNodeIndexs = new HashMap<>();
    private int middleNodeIndex = 0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        this.inorder = inorder;
        for (int i = 0; i < inorder.length; i++) {
            inorderNodeIndexs.put(inorder[i], i);
        }
        return buildTree(0, inorder.length - 1);
    }

    private TreeNode buildTree(int inorderStartIndex, int inorderEndIndex) {
        if (inorderStartIndex > inorderEndIndex) {
            return null;
        }
        // 注意这里的 middleNodeIndex++
        TreeNode root = new TreeNode(preorder[middleNodeIndex++]);
        int middleNodeIndex = inorderNodeIndexs.get(root.val);
        root.left = buildTree(inorderStartIndex, middleNodeIndex - 1);
        root.right = buildTree(middleNodeIndex + 1, inorderEndIndex);
        return root;
    }
}
// 递归解法2
class Solution {
    private int[] preorder;
    private int[] inorder;
    private Map<Integer, Integer> inorderNodeIndexs = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        this.inorder = inorder;
        for (int i = 0; i < inorder.length; i++) {
            inorderNodeIndexs.put(inorder[i], i);
        }
        return buildTree(0, inorder.length - 1, 0, inorder.length - 1);
    }

    private TreeNode buildTree(int inorderStart, int inorderEnd, 
                                int preorderStart, int preorderEnd) {
        if (inorderStart > inorderEnd) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[preorderStart]);
        int middleNodeIndex = inorderNodeIndexs.get(root.val);
        root.left = buildTree(inorderStart, middleNodeIndex - 1, preorderStart + 1, preorderStart + middleNodeIndex - inorderStart);
        root.right = buildTree(middleNodeIndex + 1, inorderEnd, preorderStart + middleNodeIndex - inorderStart + 1, preorderEnd);
        return root;
    }
}

自己实现过程中遇到哪些困难

    因为先写了后序+中序的逻辑, 这里就是简单的复制一下, 短时间记忆让自己背下来了.

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值