【代码训练营】day18 513.找树左下角的值 & 112、113路径总和1、2 & 105、106.从中序与后序(前序)遍历序列构造二叉树

所用代码 java

找树左下角的值 LeetCode 513

题目链接:找树左下角的值 LeetCode 513 - 中等

思路

层序遍历,每次遍历时把每一层的第一个值保留就行了

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        int num = 0;
        Deque<TreeNode> deque = new ArrayDeque<>();
        if (root != null) deque.offer(root);
        while (!deque.isEmpty()){
            int size = deque.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = deque.poll();
                // 每一层的第一个元素保留(即为最左边的结点) 
                if (i == 0) num = node.val;
                if (node.left != null) deque.offer(node.left);
                if (node.right != null) deque.offer(node.right);
            }
        }
        return num;
    }
}

递归:首先深度要最深,其次为最左边的叶子结点

class Solution {
    int maxDeep = Integer.MIN_VALUE;
    int value = 0;
    public int findBottomLeftValue(TreeNode root) {
        traversal(root,0);
        return value;
    }public void traversal(TreeNode node, int deep){
        if (node == null) return;
        // 遍历到叶子结点就结束
        if (node.left == null && node.right == null) {
            if (deep > maxDeep){
                maxDeep = deep;
                value = node.val;
            }
        }// 左 traversal(node.left, deep + 1)
        if (node.left != null){
            deep++;
            traversal(node.left, deep);
            // 回溯的处理逻辑在递归函数的后面
            deep--;
        }// 右 traversal(node.right, deep + 1)
        if (node.right != null){
            deep++;
            traversal(node.right,deep);
            deep--;
        }}
}

总结

本题层序比较简单,递归主要考虑到回溯,每次回溯时深度要减1

递归为何传递时traversal(node.left, deep + 1)有回溯?

可以看到deep每次+1时就进行了一个递归,栈就堆一层,当我们返回最上层的deep=3的结果时,就进行deep=2的操作,然后又继续往下去回溯,此时的deep值就是原来的结果

无标题-2023-01-26-1538.png

路径总和 LeetCode 112

题目链接:路径总和 LeetCode 112 - 简单

思路

把所有路径搜集到一个list里面,然后对比有没有target的值,这题感觉和二叉树的所有路径类似

我自己写的,比较冗余:

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;
        List<Integer> list = new ArrayList<>();
        traversal(root, list, 0);
//        System.out.println("size = " + list.size()); 所以才看了下list的长度
        for (int i : list) {
//            System.out.println("i=" + i); 之前误判只有两个值,其实以及返回了
            if (i == targetSum) return true;
        }
        return false;
    }public void traversal(TreeNode node, List<Integer> list, int sum){
        // 叶子结点返回
        if (node.left == null && node.right == null) {
//            System.out.println("到达结点:" + node.val);
            // 到达叶子结点就加上叶子结点的值
            sum += node.val;
            list.add(sum);
            return;
        }
        // 左
        if (node.left != null){
            sum += node.val;
            traversal(node.left,list,sum);
            // 回溯,需要减去该结点的值
            sum -= node.val;
        }
        // 右
        if (node.right != null){
            sum += node.val;
            traversal(node.right,list,sum);
            sum -= node.val;
        }
    }
}

改进后的代码:

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // 避免递归函数传入空结点
        if (root == null) return false;
        // 由于没有减根结点,所以要提前减去
        return traversal(root, targetSum - root.val);
    }public boolean traversal(TreeNode node, int target){
        System.out.println("target = " + target);
        // 返回情况两种:到达叶子结点后,target是否被减为0了
        // 我们不需要遍历所有结点,遇到正确的就可以一直向上返回true
        if (node.left == null && node.right == null && target == 0){
            return true;
        }
        if (node.left == null && node.right == null && target != 0){
            return false;
        }// 左 - 因为前面没有判断左右孩子结点是否存在,所有要在这里判断
        //if (traversal(node.left, target - node.left.val)) return true;
        if (node.left != null){
            // 存在左子树就减去 左子树 的值,后面再加回来
            target -= node.left.val;
            // 如果左孩子传回来的值是ture,就直接向上返回true
            if (traversal(node.left, target)) return true;
            // 否则就回溯,然后向右递归,把减掉的值加上
            target += node.left.val;
        }// 右 - 同理  if (traversal(node.right, target - node.right.val)) return true;
        if (node.right != null){
            target -= node.right.val;
            if (traversal(node.right, target)) return true;
            target += node.right.val;
        }
        // 没有目标值刚好减完的情况,就返回false
        return false;
    }
}

更精简的代码:

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;
        // 直到最后判断targetSum剩下的值是否等于叶子结点的值
        if (root.left == null && root.right == null) {
            return targetSum == root.val;
        }
        // 一开始减去的是根结点的值
        // 后面是递归和回溯过程的加减结点值
        return hasPathSum(root.left, targetSum - root.val) ||
                hasPathSum(root.right, targetSum - root.val);
    }
}

总结

递归的话包含着回溯的过程,一开始学习的时候可以把整个过程写完,没必要写太精简的,多理解然后再浓缩。

路径总和 LeetCode 113

题目链接:路径总和 LeetCode 113 - 中等

思路

和上面一个题的方法应该是一样的,需多传一个path来接受路径值

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        if (root == null) return res;
        List<Integer> path = new ArrayList<>();
        path.add(root.val);
        traversal(root, path, targetSum - root.val);
        return res;
    }public void traversal(TreeNode node, List<Integer> path, int target){
        if (node.left == null && node.right == null && target == 0){
//            System.out.println("path = " + path);
            // 需要新建一个list去存值,以免存的引用变量path会改变
            // List<Integer> list = new ArrayList<>(path);
            List<Integer> list = new ArrayList<>();
            // 复制path里面的所有值到list
            list.addAll(path);
            // 若直接写res.add(path),上面res的值是[[5],[5]]
            // 由于我们添加的path是path的引用(地址值),后面path再改变的话res里面的path也会改变
            res.add(list);
//            System.out.println("res = " + res);
            return;
        }
        if (node.left == null && node.right == null && target != 0){
            return;
        }//左
        if (node.left != null){
            target -= node.left.val;
            path.add(node.left.val);
            traversal(node.left, path, target);
            target += node.left.val;
            path.remove(path.size() - 1);
        }//右
        if (node.right != null){
            target -= node.right.val;
            path.add(node.right.val);
            traversal(node.right, path, target);
            target += node.right.val;
            path.remove(path.size() - 1);
        }
    }
}

总结

本题是在上次的基础上进行的拓展,多添加了一个变量,可以直接写在参数里面调用。要注意的是每次找到的path路径需重新new一个list来存储,可以直接List<Integer> list = new ArrayList<>(path);这样进行list的copy,比addAll要方便。

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

题目链接:

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

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

思路

知道怎么构造二叉树,但是写不出来代码


中序 + 后序

  1. 后序数组为0,空结点
  2. 后序数组最后一个元素为结点元素
  3. 寻找中序数组位置作切割点
  4. 切中序数组
  5. 切后序数组
  6. 递归处理左区间右区间

根据卡哥思路一次过,太棒了!!这思路太清晰了。视频地址:中序 + 后序构建二叉树

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        return traversal(inorder,postorder);
    }
    public TreeNode traversal(int[] inorder, int[] postorder){
        if (postorder.length == 0) return null;
        int rootValue = postorder[postorder.length - 1];
        TreeNode root = new TreeNode(rootValue);
        // 当遍历到叶子结点时,就把该叶子结点向上传递
        // 或者只有一个结点时,也返回。
        if (postorder.length == 1) return root;// 3、寻找切割位置 - 利用中序
        int index = 0;
        for (index = 0; index < inorder.length; index++) {
            if (inorder[index] == rootValue) break;
        }// 4、切中序数组,根据根结点的位置切割 -- copyOfRange 左闭右开
        // Arrays.copyOfRange(要拷贝的数组,起始索引位置,终止索引位置)
        int[] leftInorder = Arrays.copyOfRange(inorder, 0, index);
        int[] rightInorder = Arrays.copyOfRange(inorder, index + 1, inorder.length);// 5、切后序数组,根据中序的左中序长度切割
        int[] leftPostorder = Arrays.copyOfRange(postorder, 0, leftInorder.length);
        int[] rightPostorder = 
            Arrays.copyOfRange(postorder, leftPostorder.length, postorder.length-1);// 6、递归处理左区间和右区间
        root.left = traversal(leftInorder, leftPostorder);
        root.right = traversal(rightInorder, rightPostorder);// 返回二叉树的根结点
        return root;
    }
}

按照前面这个思路的话,可以写出 中序 + 前序的过程

  1. 前序数组为0,空结点
  2. 前序数组的第一个元素为结点元素
  3. 寻找中序数组的位置作为切割点
  4. 切割中序数组
  5. 切割前序数组
  6. 递归处理左区间和右区间
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return traversal(preorder, inorder);
    }public TreeNode traversal(int[] preorder, int[] inorder){
        // 1、前序数组为空,空结点
        if (preorder.length == 0) return null;
        // 2、前序数组第一个元素为结点元素
        int rootValue = preorder[0];
        TreeNode root = new TreeNode(rootValue);
        // 只有一个元素或者遍历到了根结点就返回该结点
        if (preorder.length == 1) return root;// 3、寻找切割点 -- 通过中序数组
        int index = 0;  // index后面切割会用到,所以定义在外边
        for (index = 0; index < inorder.length; index++) {
            if (rootValue == inorder[index]) break;
        }// 4、切割中序数组
        int[] leftInorder = Arrays.copyOfRange(inorder, 0, index);
        int[] rightInorder = Arrays.copyOfRange(inorder, index + 1, inorder.length);// 5、切割前序数组
        int lenLeft = leftInorder.length;
        int[] leftPreorder = Arrays.copyOfRange(preorder, 1, lenLeft + 1);
        int lenRight = leftPreorder.length;
        int[] rightPreorder = 
            Arrays.copyOfRange(preorder, 1 + lenRight, preorder.length);// 6、左右区间分别递归
        root.left = traversal(leftPreorder, leftInorder);
        root.right = traversal(rightPreorder, rightInorder);return root;
    }
}

总结

本题主要是有点麻烦,需考虑的事情比较多,细节处理也多,还要有非常缜密的思维,该题可以说是非常的难了。

另外一种使用索引的方式来进行构造二叉树(前序为例):

使用这种方法我们就不需要创建新的数组,而直接利用数组的索引,使其找到正确的创建二叉树的根结点值,每次只需要传入数组左右区间的位置,然后以此递归

class Solution {
    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 traversal(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }

    public TreeNode traversal(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd){
        // 1、若前序数组为空(使用索引操作),则返回null
        if (preEnd - preBegin <= 0) return null;
        // 2、创建结点 ==> 若只有还剩一个元素则返回(只有一个结点、递归到叶子结点)
        TreeNode root = new TreeNode(preorder[preBegin]);
        // 由于前序和中序一样,所以只用判断前序就行了
        if (preEnd - preBegin == 1) return root;
        // 3、找到中序中结点的索引 -- 前序数组的第一个数
        int indexValue = map.get(preorder[preBegin]);

        // 4、找到切割中序的索引位置 -- 统一左闭右开
        int leftInorderBegin = inBegin;
        int leftInorderEnd = indexValue;
        int rightInorderBegin = indexValue + 1;
        int rightInorderEnd = inEnd;

        // 5、切割前序索引位置
        int leftPreorderBegin = preBegin + 1;
        int leftPreorderEnd = leftPreorderBegin + (leftInorderEnd - leftInorderBegin);
        int rightPreorderBegin = leftPreorderEnd;
        int rightPreorderEnd = preEnd;

        // 左右递归构建二叉树,这里接受前面返回的root(孩子结点)
        root.left = traversal(preorder, leftPreorderBegin, leftPreorderEnd, inorder, leftInorderBegin, leftInorderEnd);
        root.right = traversal(preorder, rightPreorderBegin, rightPreorderEnd, inorder, rightInorderBegin, rightInorderEnd);

        return root;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值