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

今日内容

  • leetcode. 513 找树左下角的值
  • leetcode. 112 路径总和
  • leetcode. 106 从中序与后续遍历序列构造二叉树

Leetcode. 513 找树左下角的值

文章链接:代码随想录 (programmercarl.com)

题目链接:513. 找树左下角的值 - 力扣(LeetCode)

本题用层序遍历反而更简单一点。用层序遍历的思路就是记录每一层的第一个节点,这样遍历到最后一层时就可以得到树的左下角的值了。代码如下:

/**
 * 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 {
    public int findBottomLeftValue(TreeNode root) {
        if (root == null){return 0;}
        Queue<TreeNode> queue = new LinkedList<>();
        int result = 0;
        queue.offer(root);
        while (!queue.isEmpty()){
            int len = queue.size();
            for (int i = 0; i < len; i++){
                TreeNode n = queue.peek();
                queue.poll();
                if (i == 0){result = n.val;} // 记录每层的第一个节点
                if (n.left != null){queue.offer(n.left);}
                if (n.right != null){queue.offer(n.right);}
            }
        }
        return result;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

也可以使用递归来解决该题,在本题中采用前序遍历来解决。

递归三要素:

  1. 判断递归函数的参数和返回值。节点肯定需要被传入,还有记录深度的变量也需要被传入。我们并不需要递归函数返回什么结果,所以有两个参数、没有结果。
  2. 根据题意,当前节点为叶子节点时,就可以记录当前深度,并停止递归了。
  3. 单层逻辑就是查看节点是否为叶子节点,不是则先探左子树,再探右子树;是则按照步骤 2 进行。

根据上述三个递归要素,写出如下代码:

// 递归法
class Solution {
    int maxDepth = 0;
    int result = 0;
    public int findBottomLeftValue(TreeNode root) {
        result = root.val; // 考虑root为空或者root就是叶子节点的情况
        findBottom(root, 0);
        return result;
    }
    public void findBottom(TreeNode node, int depth){
        if (node.left == null && node.right == null){ // 要素 2
            if (depth > maxDepth){
                maxDepth = depth;
                result = node.val;
            }
            return;
        }
        if (node.left != null){
            findBottom(node.left, depth + 1);
            /** 隐含了回溯,其等同于
            depth++;
            findBottom(node.left, depth);
            depth--; 
            */
        }
        if (node.right != null){
            findBottom(node.right, depth + 1);
        }
        return;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

Leetcode. 112 路径总和

文章链接:代码随想录 (programmercarl.com)

题目链接:112. 路径总和 - 力扣(LeetCode)

本题要求从根节点开始,查找一条到叶子节点的符合要求的路径。那么这可以直接使用前序遍历来解决了。

这样又要分析递归三要素了:

  1. 递归函数的参数和返回值。参数肯定要有节点。本题需要判断路径上节点值的和是否等于targetSum,因此还需要传入一个状态参数用于判断。为了表示某一条路径是否符合要求,该函数需要返回Boolean类型。
  2. 递归函数停止条件。当发现是叶子节点时就可以停下,判断该路径是否符合要求,符合则返回True,不符合则回溯,继续探测下一条路径。
  3. 单层逻辑。首先判断是否为叶子节点,是则按步骤 2 执行;不是则先探测左子树,再探测右子树。 

此处细说一下步骤 1 中的状态参数。一般我们会想直接把路径上所有节点的元素值相加,将得到的结果和targetSum进行比较,但实现这个代码比较麻烦。我们可以使用递减思维,每遍历一个节点就将targetSum减去该节点值,如果到叶子节点发现targetSum等于0了,说明路径符合条件;反之则不符合,进行回溯寻找下一条。

根据上述分析,可以写出如下代码:

/**
 * 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 {
    // 步骤 1,因为本身这个函数就符合条件了,所以不需要重新创建递归函数
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null){return false;}
        
        // 中
        targetSum -= root.val; // 根节点的值一开始就要减去
        // 是叶子节点且targetSum等于0时,说明符合条件
        if (root.left == null && root.right == null && targetSum == 0){return true;}
        // 否则不符合
        if (root.left == null && root.right == null){return false;}
        
        // 左
        if (root.left != null){
            boolean isPath = hasPathSum(root.left, targetSum); // 此处一定要这样用一个变量接收递归函数结果,否则直接会返回false。
            if (isPath){return true;}
        }
        
        // 右
        if (root.right != null){
            boolean isPath = hasPathSum(root.right, targetSum);
            if (isPath){return true;}
        }
        return false;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

根据本题思路还可以解决 113. 路径总和 II - 力扣(LeetCode),不同之处在于Leetcode. 113 需要返回所有满足条件的路径的列表。

代码如下:

class Solution {
    
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null){return result;}
        List<Integer> path = new ArrayList<>();
        findPath(root, result, path, targetSum);
        return result;
    }
    
    // 本题返回的是列表,列表是引用类型,所以递归函数不用返回值
    public void findPath(TreeNode n, List<List<Integer>> result, List<Integer> path, int targetSum){
        if (n == null){return;}
        
        // 中
        targetSum -= n.val;
        path.add(n.val);    
        if (n.left == null && n.right == null && targetSum == 0){    
            result.add(new ArrayList<>(path)); // 此处还需要套一层New,否则插入的不是整个路径列表
        }
        
        // 左
        if (n.left != null){
            findPath(n.left, result, path, targetSum);
            path.remove(path.size() - 1); // 遍历完一个节点后就回溯
        }
        
        // 右
        if (n.right != null){
            findPath(n.right, result, path, targetSum);
            path.remove(path.size() - 1); 
        }
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

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

文章链接:代码随想录 (programmercarl.com)

题目链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

本题也让我们复习了一个小知识点:前序/后序遍历 和 中序遍历 可以构造出唯一的二叉树 

本题思路也是围绕着这两种遍历的方式来的:

  1. 后序遍历顺序为左右中,最后一个元素必是根元素。因此先获取后序遍历序列的最后一个元素。
  2. 中序遍历顺序为左中右。在中序遍历序列中进行查找,找到上一步获取到的根元素。将中序遍历进行拆分,分成左右两部分。
  3. 相同序列、相同根节点的左右子树长度肯定相同。根据上一步拆分的左右部分的长度,将后序遍历也拆分成同样长度的左右部分。
  4. 不断重复步骤 1-3,直到左右部分不可拆分时,二叉树构造完成。

上述思路可以如下图所示:

根据上述思路,写出如下代码,要注意区间不变量,本题一直贯彻“左闭右开”:

/**
 * 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 {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if (inorder.length == 0 || postorder.length == 0){return null;}
        TreeNode root = splitOrder(inorder, 0, inorder.length, postorder, 0, postorder.length);
        return root;
    }

    public TreeNode splitOrder(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd){
        // 判断是否已经不可分割
        if (postStart == postEnd){return null;}
        // 步骤 1 ,获取后序遍历序列的最后一个元素
        int postValue = postorder[postEnd - 1];
        TreeNode root = new TreeNode(postValue);
        
        // 步骤 2 ,拆分中序遍历序列
        int delimeterIndex = 0;
        for (delimeterIndex = 0; delimeterIndex < inorder.length; delimeterIndex++){
            if (inorder[delimeterIndex] == postValue){
                break;
            }
        }
        // 切割中序遍历
        int leftInorderStart = inStart;
        int leftInorderEnd = delimeterIndex;
        int rightInorderStart = delimeterIndex + 1;
        int rightInorderEnd = inEnd;

        // 切割后序遍历
        int leftPostorderStart = postStart;
        int leftPostorderEnd = leftPostorderStart + (delimeterIndex - leftInorderStart);
        int rightPostorderStart = leftPostorderEnd;
        int rightPostorderEnd = postEnd - 1;

        root.left = splitOrder(inorder, leftInorderStart, leftInorderEnd, postorder, leftPostorderStart, leftPostorderEnd);
        root.right = splitOrder(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);
    
        return root;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

解决了后序和中序遍历序列构造二叉树,使用该思路还可以解决前序和中序遍历序列构造二叉树的问题。题目链接:105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

该题代码如下:

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder.length == 0 || inorder.length == 0){
            return null;
        }
        return splitOrder(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }
    public TreeNode splitOrder(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd){
        if (preStart == preEnd){return null;}
        // 前序是第一个元素为根元素
        int preValue = preorder[preStart];
        TreeNode root = new TreeNode(preValue);

        int delimeterIndex = 0;
        for (delimeterIndex = 0; delimeterIndex < inorder.length; delimeterIndex++){
            if (inorder[delimeterIndex] == preValue){break;}
        }

        // 划分中序遍历
        int leftInorderStart = inStart;
        int leftInorderEnd = delimeterIndex;
        int rightInorderStart = delimeterIndex + 1;
        int rightInorderEnd = inEnd;

        // 划分前序遍历
        int leftPreorderStart = preStart + 1;
        int leftPreorderEnd = leftPreorderStart + (delimeterIndex - leftInorderStart);
        int rightPreorderStart = leftPreorderEnd;
        int rightPreorderEnd = preEnd;

        root.left = splitOrder(preorder, leftPreorderStart, leftPreorderEnd, inorder, leftInorderStart, leftInorderEnd);
        root.right = splitOrder(preorder, rightPreorderStart, rightPreorderEnd, inorder, rightInorderStart, rightInorderEnd);

        return root;
    }
}

总结

今天的题目都可以理解思路,但是将思路转换为代码后,会因为一堆小错误导致无法得到预期结果,像是递归函数的停止条件代码写错导致递归提前结束,还有嵌套List的结果有误之类的问题。但是经过排查和解决后,对这些容易出问题的地方就留了个心眼,其实也是蛮好的。

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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
发出的红包

打赏作者

DonciSacer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值