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

学习目标:

60天训练营打卡计划!

层序遍历法–迭代法
前后中序遍历–递归法

递归三部曲:

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑

学习内容:

513.找树左下角的值

  • 方法一:层序遍历法(分清深度和最后的左下角的值!)
  • 方法二:迭代遍历法
    • 最好重新定义所谓的类,在其中增加最大深度和最大深度的左叶子节点数值
      通过每次传入当前的层深度的递归和回溯来求解当前树的最大深度
//  层序遍历
class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> que = new LinkedList<>();
        int size = -1;

        que.add(root);
        // 该val就是最后的返回结果
        int val = root.val;
        while(!que.isEmpty()){
            size = que.size();
            // 确定每层的第一个值
            int flag = 1;
            while(size-- > 0){
                TreeNode node = que.remove();
                if(node.left != null) que.add(node.left);
                if(node.right != null) que.add(node.right);
                // 我们想要的结果一定在这个范围中。
                if(node.left == null && node.right == null && flag == 1){
                    val = node.val;
                    flag--;
                }
            }
        }

        return val;

    }
}


class Solution {
    // 最大深度
    private int maxDepth = -1;
    // 最大深度最左侧的叶子结点的值
    private int value = 0;

    public int findBottomLeftValue(TreeNode root) {
        traversal(root,0);
        return value;
    }
    // 此处传的depth是为了确定当前的node节点所在的深度
    private void traversal(TreeNode node, int depth){
        if(node == null) return ;
        if(node.left == null && node.right == null){
            if(depth > maxDepth){
                maxDepth = depth;
                value = node.val;
            }
        }
        // 隐式地体现递归和回溯
        if(node.left != null )  traversal(node.left, depth + 1);
        if(node.right != null)  traversal(node.right, depth + 1);
        return;
    }
}

112. 路径总和

  • 适合递归法做,和257. 二叉树的所有路径整体的逻辑很相似,要体现出递归和回溯的过程。
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
    	// 递归中没有对根节点的处理,所以就加到主程序里。
        if(root == null) return false;
        targetSum -= root.val;
        return traversal(root,targetSum);
    }
    
    // 返回值为boolean是因为如果某条路已经满足题意就可以结束寻找了!
    // count是用于计数的,判断这条路是否符合题意。
    // 第一步:确定递归的返回值和参数
    boolean traversal(TreeNode node, int count) {
        // 已经遍历到叶子节点了,结束!
        if(node.left == null && node.right == null && count == 0)  return true;
        if(node.left == null && node.right == null && count != 0)  return false;

        if(node.left != null){
            count -= node.left.val;
            if(traversal(node.left, count))  return true;
            count += node.left.val;
        }

        if(node.right != null){
            count -= node.right.val;
            if(traversal(node.right, count))  return true;
            count += node.right.val;
        }

        // 执行到这里一定是没有找到
        return false;
    }
}

113. 路径总和 Ⅱ

  • 112. 路径总和整体的逻辑很相似,要体现出递归和回溯的过程。
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> tmp = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        if(root == null)  return res;
        targetSum -= root.val;
        tmp.add(root.val);
        traversal(root, targetSum);
        return res;
    }

    // 先找递归的返回值和参数
    // 参数一定是树节点和总的数目
    // 返回值可以不设,将其定义为类的一个参数
    void traversal(TreeNode node ,int count){
        // 结束条件
        if(node.left == null && node.right == null){
            if(count == 0)
                // 这里为什么要新建?**因为add()添加的是引用。**
                // 如果直接将tmp添加到res中,后续对tmp的修改也会影响已经添加到res中的路径。
                // 通过创建一个新的ArrayList实例,可以保留路径的快照,防止修改影响已经存储的路径。
                res.add(new ArrayList<>(tmp));
            return;
        }  
        // 单层递归逻辑
        if(node.left != null){
            tmp.add(node.left.val);
            count -= node.left.val;
            traversal(node.left, count);
            tmp.remove(tmp.size() - 1);
            count += node.left.val;
        }
        if(node.right != null){
            tmp.add(node.right.val);
            count -= node.right.val;
            traversal(node.right, count);
            tmp.remove(tmp.size() - 1);
            count += node.right.val;
        }

    }
}

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

  • 思路比较清晰。

  • 第一次把根节点赋值赋错了,TreeNode root = new TreeNode(postorder[postorder.length - 1]);

  • 按照0的位置赋值的。

  • 此处使用了Arrays.copyOfRange(inorder, 0, index);来切割字符串。

    • 好像蠢了,其实不需要物理切割,只需要指定新的数组的头和尾就行。
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(postorder == null || postorder.length == 0)   return null;
        TreeNode root = new TreeNode(postorder[postorder.length - 1]);
        if(postorder.length == 1)
            return root;
        int index = 0;
        for(index = 0; index < inorder.length; index++){
            if(inorder[index] == root.val)
                break;
        }
        if(index == inorder.length)
            return root;
        int[] leftin;
        int[] rightin = null;;
        int[] leftpost;
        int[] rightpost = null;
        // 切割数组,左闭右开
        leftin = Arrays.copyOfRange(inorder, 0, index);
        if(index + 1 < inorder.length)
            rightin = Arrays.copyOfRange(inorder, index + 1, inorder.length);
        leftpost = Arrays.copyOfRange(postorder, 0, index);
        if(index  < postorder.length - 1)
            rightpost = Arrays.copyOfRange(postorder, index, postorder.length - 1);

        root.left = buildTree(leftin, leftpost);
        root.right = buildTree(rightin, rightpost);

        return root;
    }
}


// 只用一个数组的操作:
class Solution {
    // 左闭右开
    private TreeNode buildTreeLimit(int[] inorder, int[] postorder, 
    int instart, int inend, int poststart, int postend){
        if(postend == 0 || inend == 0 || inend - instart == 0 || postend - poststart == 0){
            return null;
        }
        TreeNode root = new TreeNode(postorder[postend - 1]);
        if(postend - poststart == 1)   return root;
        int index = instart;
        for(index = instart; index < inend; index++){
            if(inorder[index] == root.val)
                break;
        }
        // 有个误区:对于后序和中序遍历来说:
        // 只能说后序遍历的左子树长度等于中序遍历的左子树长度(间隔)
        // 同理,后序遍历的右子树长度等于中序遍历的右子树长度(间隔)
        // 左子树  
        root.left = buildTreeLimit(inorder, postorder, instart, index,
        poststart, poststart + index - instart);
        // 右子树
        root.right = buildTreeLimit(inorder, postorder, index + 1, inend, 
        poststart + index - instart , postend - 1);
        return root;
    }

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(postorder == null || postorder.length == 0)
            return null;
        return buildTreeLimit(inorder, postorder, 0, inorder.length, 0, postorder.length);
    }
}

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

  • 与106思路一致!
class Solution {
    // 左闭右开
    private TreeNode buildTreeLimit(int[] preorder, int[] inorder, 
    int prestart, int preend, int instart, int inend) {
        if(preend == 0|| inend == 0 || preend - prestart == 0 || inend - instart == 0)
            return null;
        TreeNode root = new TreeNode(preorder[prestart]);
        if(inend - instart == 1)   return root;
        int index = instart;
        for(index = instart; index < inend; index++){
            if(inorder[index] == root.val)
                break;
        }
        // 构造左子树
        root.left = buildTreeLimit(preorder, inorder, 
        prestart + 1, prestart + 1 +index - instart, instart, index);
        // 构造右子树
        root.right = buildTreeLimit(preorder, inorder, 
        prestart + 1 +index - instart, preend, index + 1, inend);

        return root;
    } 

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length == 0)  return null;
        return buildTreeLimit(preorder, inorder, 0, preorder.length, 0, inorder.length);
    }
}

学习时间:

  • 上午两小时,下午一个半小时,整理文档半小时。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值