【力扣一刷】代码随想录day18 ( 513.找树左下角的值、112. 路径总和、113.路径总和ii、106.从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树 )

本文介绍了五种与二叉树相关的算法问题,包括在二叉树中寻找左下角值的方法、路径总和的两种递归解决方案以及利用中序和后序遍历来构造二叉树。
摘要由CSDN通过智能技术生成

目录


【513.找树左下角的值】 中等题

【112. 路径总和】简单题

【113.路径总和ii】中等题

【106.从中序与后序遍历序列构造二叉树】中等题

【105.从前序与中序遍历序列构造二叉树】中等题


【513.找树左下角的值】 中等题

注意:最底层最左边节点的值不一定是左节点的值,也有可能是右节点的值。

方法一  层序迭代(很简单)

思路:记录每一层遍历的第一个节点(i = 0的节点)的val

/**
 * 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) {
        // 层序迭代(前提:至少有一个节点,即 root != null)
        Deque<TreeNode> queue = new ArrayDeque<>();
        TreeNode cur = root;
        queue.offer(cur);
        int res = cur.val;
        while (!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                cur = queue.poll();
                if (cur.left != null) queue.offer(cur.left);
                if (cur.right != null) queue.offer(cur.right);
                if (i == 0) res = cur.val; // 记录每行从左边数来的第一个节点的val
            }
        }
        return res;
    }
}

方法二  递归 - 前序遍历

思路:

1、确定参数和返回值

  • 传入当前节点及其所在深度,目的是为了记录叶子节点的最大深度,不需要返回值,直接更改res成员变量。

2、确定终止条件

  • if (root == null) return;

3、确定单层递归逻辑

  • 如果当前节点是叶子节点且深度最深,则记录该叶子节点的val。(此时的叶子节点肯定是该层最左边的叶子节点)
  • 继续遍历左右节点(找的是最左边的值,所以必须先遍历左节点,再遍历右节点)
/**
 * 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 {
    int res;
    int maxDepth = 0;
    public int findBottomLeftValue(TreeNode root) {
        res = root.val;
        getBottomLeft(root, 1);
        return res;
    }

    // 1、确定参数和返回值:传入当前节点及其所在深度,目的是为了记录叶子节点的最大深度,不需要返回值,直接更改res成员变量
    public void getBottomLeft(TreeNode root, int depth){
        // 2、确定终止条件
        if (root == null) return;

        // 3、确定单层递归逻辑:如果当前节点是叶子节点且深度最深,则记录该叶子节点的val。(此时的叶子节点肯定是该层最左边的叶子节点)
        if (root.left == null && root.right == null && depth > maxDepth){
            res = root.val;
            maxDepth = depth;
        }

        // 继续遍历左右节点(找的是最左边的值,所以必须先遍历左节点,再遍历右节点)
        getBottomLeft(root.left, depth + 1);
        getBottomLeft(root.right, depth + 1);
    }
}


【112. 路径总和】简单题

方法一  递归 - 前序遍历(按照【257. 二叉树的所有路径】的思路)

思路:

1、确定参数和返回值

  • 传入当前节点和遍历到该节点前的路径之和

2、确定终止条件

  • if (root == null) return;

3、确定单层递归逻辑

  • 如果当前节点为叶子节点,且路径之和 = targetSum,则修改结果为true
  • 继续遍历左右节点


注意:

  • 需要用成员变量存储targetSum,否则在递归方法中无法访问targetSum
  • 如果没有路径的和 = targetSum,则res未被修改,返回默认值false
/**
 * 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 {
    boolean res = false;
    int target;
    public boolean hasPathSum(TreeNode root, int targetSum) {
        target = targetSum;  // 需要用成员变量存储targetSum,否则在递归方法中无法访问targetSum
        findTargetSum(root, 0);
        return res;  // 如果没有路径的和 = targetSum,则res未被修改,返回默认值false
    }

    // 1、确定参数和返回值:传入当前节点和遍历到该节点前的路径之和
    public void findTargetSum(TreeNode root, int sum){
        // 2、确定终止条件
        if (root == null) return;

        // 3、确定单层递归逻辑
        // 如果当前节点为叶子节点,且路径之和 = targetSum,则修改结果为true
        if (root.left == null && root.right == null && sum + root.val == target) res = true;

        // 继续遍历左右节点
        findTargetSum(root.left, sum + root.val);
        findTargetSum(root.right, sum + root.val);
    }


}

方法二  递归 - 前序遍历(直接利用原来的hasPathSum实现递归)

关键:确定单层递归逻辑

  • A、如果当前节点为叶子节点,判断路径之和是否为targetSum,由于用的是减法,则判断减完该节点的val后是否为0
  • B、如果左节点返回的是true,证明当前节点所在路径就是true,直接返回true
  • C、如果右节点返回的是true,证明当前节点所在路径就是true,直接返回true

思路版

/**
 * 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 boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;

        // 确定单层逻辑
        // A、如果当前节点为叶子节点,判断路径之和是否为targetSum,由于用的是减法,则判断减完该节点的val后是否为0
        if (root.left == null && root.right == null) return targetSum - root.val == 0;
        // B、如果左节点返回的是true,证明当前节点所在路径就是true,直接返回true
        if (hasPathSum(root.left, targetSum - root.val)) return true;
        // C、如果右节点返回的是true,证明当前节点所在路径就是true,直接返回true
        if (hasPathSum(root.right, targetSum - root.val)) return true;
    }
}

简约版

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;

        if (root.left == null && root.right == null) return targetSum - root.val == 0;
        return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }
}


【113.路径总和ii】中等题

区别:与【257. 二叉树的所有路径】这道题的区别在于,257题使用字符串存储路径,字符串是不可变类型,当字符串变量作为形参,即使在函数体内修改字符串变量的值,也不会影响实参的值。但113这题,传入的是List变量,当在函数体内更改值时,会影响实参的值。因此,需要遍历完左节点之后,恢复当前节点的路径,再遍历右节点,再恢复当前节点的路径。

思路:关键是确认当前递归逻辑(路径 -> 前序遍历)

  • 1、更新当前层的剩值和参数
  • 2、如果当前节点为叶子节点,且当前路径符合条件,则复制当前路径并存入。
  • 3、继续遍历左右节点,使用回溯恢复当前节点对应的路径

注意:当将path加入到paths时,由于List是引用类型,在Java中存储的是引用的地址。在遍历过程中,path会不断被修改并添加到paths中,但实际上paths中存储的都是同一个path对象的引用。因此,在add的时候,要利用构造器将path复制一份,再存入paths。

/**
 * 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 {
    List<List<Integer>> paths = new ArrayList<>();  // 存储所有路径之和=targetSum的路径
    List<Integer> path = new ArrayList<>(); // 存储从根节点到当前遍历节点的路径
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        getPathSum(root, targetSum);
        return paths;
    }

    public void getPathSum(TreeNode root, int targetSum){
        if (root == null) return;

        // 更新当前层的剩值和参数
        targetSum = targetSum - root.val;
        path.add(root.val);
        
        // 如果当前节点为叶子节点,且当前路径符合条件,则复制当前路径并存入
        // System.out.println(targetSum);
        if (root.left == null && root.right == null && targetSum == 0) {
            // 必须重新构建List,再存入paths,否则paths中存储的都是同一个path的引用,值相同
            paths.add(new ArrayList<>(path)); 
        }

        // 继续遍历左右节点
        getPathSum(root.left, targetSum);
        if (root.left != null) path.remove(path.size() - 1);  // 回溯
        getPathSum(root.right, targetSum);
        if (root.right != null) path.remove(path.size() - 1);  // 回溯
    }
}


【106.从中序与后序遍历序列构造二叉树】中等题

思路:

1、确定参数和返回值:利用递归构造二叉树,传入中序遍历和后序遍历数组,返回根节点

2、确定终止条件:如果后序遍历数组长度为0,证明根节点是null,直接返回null即可

3、确定单层递归逻辑:

  • A. 找根节点,后序遍历(左右中)数组的最后一个元素就是根节点
  • B. 根据根节点切割中序遍历数组和后序遍历数组,分别得到左右子树各自的中序遍历数组和后序遍历数组
    • a. 遍历中序数组,查找root节点的val对应的索引idx
    • b. 根据idx切割中序遍历数组和后序遍历数组
      • - 获取左子树的中序遍历数组和后序遍历数组
      • - 获取右子树的中序遍历数组和后序遍历数组
  • C、递归获取根节点的左右节点

方法一  利用原来的buildTree方法实现递归(自己写的)

/**
 * 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 TreeNode buildTree(int[] inorder, int[] postorder) {
        // 2、确定终止条件
        if (postorder.length == 0) return null;

        // 3、确定单层递归逻辑
        // A. 找根节点:后序遍历(左右中)数组的最后一个元素就是根节点
        TreeNode root = new TreeNode(postorder[postorder.length - 1]);
        // System.out.println(root.val); 
        
        // B. 根据根节点切割中序遍历数组和后序遍历数组,分别得到左右子树各自的中序遍历数组和后序遍历数组
        // 遍历中序数组,查找root节点的val对应的索引idx
        int idx = -1;
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i] == root.val) {
                idx = i;
                break;
            }  // inOrder中存储的都是各异的元素,才可以找到root节点对应的真正索引
        }
        // 根据idx切割中序遍历数组和后序遍历数组
        // - 获取左子树的中序遍历数组和后序遍历数组
        int[] leftInorder = new int[idx];
        int[] leftPostorder = new int[idx];
        for (int i = 0; i < leftInorder.length; i++){
            leftInorder[i] = inorder[i];
            leftPostorder[i] = postorder[i];
        }

        // - 获取右子树的中序遍历数组和后序遍历数组
        int[] rightInorder = new int[inorder.length - 1 - idx];
        int[] rightPostorder = new int[rightInorder.length];
        for (int i = 0; i < rightInorder.length; i++){
            rightInorder[i] = inorder[idx + 1 + i];  // 中序的右子树需要避开右节点
            rightPostorder[i] = postorder[idx + i];  //后序的右子树不需要避开root节点,root节点在最后面
        }

        // C. 递归传入,构造二叉树
        root.left = buildTree(leftInorder, leftPostorder);
        root.right = buildTree(rightInorder, rightPostorder);
        return root;
    }
}

缺点:需要额外O(n)的空间存储切割后的中序遍历数组和后序遍历数组,而且每次切割还需要O(n)的时间复杂度存储。

方法二  利用自定义的方法递归(优化版)

优化:

  • 利用HashMap加快索引查询,key为节点的val,value为节点在中序遍历数组的索引值。
  • 传入中序和后序遍历数组的开始索引和结束索引,不需要额外的数组存储。
/**
 * 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 {
    Map<Integer, Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        // 创建哈希表,加速根节点val值的索引查找
        for (int i = 0; i < inorder.length; i++){
            map.put(inorder[i], i);
        }
        return buildTreeByIdx(inorder, 0, inorder.length, postorder, 0, postorder.length);

    }
    // 1、确定参数和返回值,参数传入区间和索引,要求索引对应左闭右开原则,返回二叉树的根节点
    public TreeNode buildTreeByIdx(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd){
        // 2、确定终止条件(原则是左闭右开,如果开始和结束相等,则证明区间为空,返回null)
        if (postEnd == postBegin) return null;

        // 3、确定单层递归逻辑
        TreeNode root = new TreeNode(postorder[postEnd - 1]);
        int idx = map.get(root.val);

        root.left = buildTreeByIdx(inorder, inBegin, idx,
                                   postorder, postBegin, postBegin + idx - inBegin);
        root.right = buildTreeByIdx(inorder, idx + 1, inEnd, 
                                    postorder, postBegin + idx - inBegin, postEnd - 1);
        return root;

    }
}

缺点:在构造左右子树时,传入的索引很容易出错,注意左子树区间的长度是idx - inBegin。


【105.从前序与中序遍历序列构造二叉树】中等题

方法  利用自定义的方法递归

思路:跟上一题【106.从中序与后序遍历序列构造二叉树】的方法二思路基本一样,区别在于该题中【根节点是前序遍历数组的第一个元素】。注意:切割的索引很容易写错。

/**
 * 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 {
    // 创建哈希表,加速根节点val的索引查询
    Map<Integer, Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++){
            map.put(inorder[i], i);
        }
        return buildTreeByIdx(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }

    public TreeNode buildTreeByIdx(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd){
        // 根据左闭右开原则,当开始索引和结束索引相等时,证明前序遍历数组为空,直接返回null
        if (preBegin == preEnd) return null;

        // 构造根节点
        TreeNode root = new TreeNode(preorder[preBegin]);  // 前序遍历(中左右)的第一个节点就是根节点

        // 获取根节点对应的索引
        int idx = map.get(root.val);

        // 按根节点的索引切割中序和后序数组,构造左子树和右子树
        root.left = buildTreeByIdx(preorder, preBegin + 1, preBegin + 1 + idx - inBegin, inorder, inBegin, idx);
        root.right = buildTreeByIdx(preorder, preBegin + 1 + idx - inBegin, preEnd, inorder, idx + 1, inEnd);
        return root;
    }
}
  • 25
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值