Day16 力扣二叉树 :513.找树左下角的值|112. 路径总和|106.从中序与后序遍历序列构造二叉树

Day16 力扣二叉树 :513.找树左下角的值|112. 路径总和|106.从中序与后序遍历序列构造二叉树

513.找树左下角的值

本地递归偏难,反而迭代简单属于模板题, 两种方法掌握一下

题目链接/文章讲解/视频讲解:https://programmercarl.com/0513.%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.html

第一印象:

想直接层序遍历,找到最下面一层的第一个就行了。但是代码随想录里还是先教对于这道题更复杂的递归法了,我先学学8. 好久没写了,层序遍历都忘了,等会复习一下8

看完题解的思路:

原来层序遍历的写法就是迭代法,这道题的递归又涉及了回溯,我觉得我对回溯有一点理解了: 回到上一层的时候,深度也是回到上一层的深度。

实现的困难:

实现起来没什么困难,使用了全局变量来做

感悟:

层序遍历忘记了,理解了一点点的回溯。

代码:

class Solution {
    private int Deep = 0;
    private int result = 0;
    public int findBottomLeftValue(TreeNode root) {
        if (root == null) return -1;
        findLeftValue(root, 1);
        return result;
    }

    private void findLeftValue(TreeNode node, int depth) {
        if (node.left == null && node.right == null) {
            if (depth > Deep) {
                Deep = depth;
                result = node.val;
            }
        }
        //左
        if (node.left != null) {
            depth++;
            findLeftValue(node.left, depth);
            //回溯
            depth--;
        }
        //右
        if (node.right != null) {
            depth++;
            findLeftValue(node.right, depth);
            //回溯
            depth--;
        }

    }
}

112. 路径总和

本题 又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解

  1. 路径总和,和 113. 路径总和ii 一起做了。 优先掌握递归法。

题目链接/文章讲解/视频讲解:https://programmercarl.com/0112.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.html

第一印象:

这道题看一眼就感觉有回溯的味,加一下孩子节点,看看Sum是否满足要求,不满足的话退回到父节点的Sum,就是回溯的过程了,看一下题解吧。

看完题解的思路:

悟了,但是实现上有一些细节。

实现的困难:

终止条件是遇到叶子节点的时候,sum是不是0,是则有这样的路径返回true,反之没有返回false。

当左右递归的时候要先看 node.left 是否为 null,否则在终止条件会出现 null.left 的情况报错。

回溯的地方没什么困难,但是

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

path(node.left, sum)外要套一层 if 判断,如果左子树找到了这样的路径接受了一个 true 的返回,就要把这个向上返回,最终返回到根节点。
之前没有遇到这样的操作,一般都是直接path(node.left, sum),可能是因为他们的返回值是 void吧,有待研究。

最后我在第一次写的时候,调用的函数里我写的是 return path(root, targetSum); 这是错的,因为 path 函数做的事情是以 node 为根的子树有没有满足 sum 的一条路径,那么在计算第一个结点的时候,sum就应该加上这个节点的数值。

我觉得会犯这个错误是因为做这道题的思路是用target去减,就会觉得传入的应该是target啊,但其实如果用不断去加的思路去做,传入的应该是 root和root.val ,而不是root和0.

举一个最简单的例子,只有一个节点 2, target也是 2. 那么在进行判断的时候 sum == 0, 传入target的话就不符合了,直接返回false了。

最后我还把终止条件的判断条件 左右孩子都是null写成了 两个左孩子,操

感悟:

整体思路是对的,但是还不能自己全写出来,而且有些细节也犯错,细想还觉得模糊。

代码:


```java
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;
        //这里注意一开始传入的要减去root的val。因为函数内的逻辑是先看sum是不是0,再看左右孩子
        //函数的功能是看一个节点的左、右子树有没有这样的路径,sum应该包括根节点的val
        //思考只有一个根节点2的情况,target也是2的情况
        return path(root, targetSum - root.val);
    }

    private boolean path(TreeNode node, int sum) {
        if (node.left == null && node.right == null && sum == 0) return true;
        if (node.left == null && node.right ==null && sum != 0) return false;
        //左
        if (node.left != null) {
            sum -= node.left.val;
            if (path(node.left, sum)) {
                return true;
            }
            sum += node.left.val;
        }
        //右
        if (node.right != null) {
            sum -= node.right.val;
            if (path(node.right, sum)) {
                return true;
            }
            sum += node.right.val;
        }
        return false;
    } 
}

113. 路径总和 II

第一印象:

饿了,先去吃个披萨吧,最近做的梦都没印象。

好了吃饭披萨已经第二天了,这个题要收集所有路径,不会不会,看题解,我看题解的意思 这两道题我应该学会什么时候有无 返回值

看完题解的思路:

思路看懂了,就是记录一下一条路径,最后添加到结果res里,如果不符合要求,这个路径也要回溯。思路上明白,但是实现起来遇到了问题,也不是完全明白吧,按照对回溯朦胧的感觉反正写出来了。

实现遇到的困难:

我最开始在res里直接添加的一条路径list, 这样就是错的。
在这里插入图片描述

而写成这样就对了
在这里插入图片描述

我觉得就是一个语法问题,但我没弄懂,还在等待回答。

回答:
是Java的那个对象引用的问题,在这里插入图片描述
在这里插入图片描述
简单来说就是他们都指向了一片内存区域。list 和 res.get 都只是引用了这片区域。所以每次list改变区域内容。res.get获得的内容也是改变之后的内容

感悟:

题其实不难,我想了一下但是没敢写,我觉得是代码实现能力有待提高啊。

代码:

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res;
        List<Integer> list = new ArrayList<>();

        list.add(root.val);
        path(root, targetSum - root.val, res, list);
        return res;

    }

    private void path(TreeNode node, int sum, List<List<Integer>> res, List<Integer> list) {
        //如果遇到了叶子节点,路径和满足target,把这个路径加入
        if (node.left == null && node.right == null && sum == 0) {
            res.add(new ArrayList<>(list));
            return;
        }
        //如果遇到了叶子节点,但路径和不满足target,直接返回
        if (node.left == null && node.right == null && sum != 0) {
            return;
        }
        //左
        if (node.left != null) {
            sum -= node.left.val;
            list.add(node.left.val);
            path(node.left, sum, res, list);
            sum += node.left.val;
            list.remove(list.size() - 1);
        }

        //右
        if (node.right != null) {
            sum -= node.right.val;
            list.add(node.right.val);
            path(node.right, sum, res, list);
            sum += node.right.val;
            list.remove(list.size() - 1);
        }
    }
}

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

本题算是比较难的二叉树题目了,大家先看视频来理解。

106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树 一起做,思路一样的

题目链接/文章讲解/视频讲解:https://programmercarl.com/0106.%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.html

第一印象:

他说比较难,那我直接看视频哈哈

看完题解的思路:

过程听懂了,但感觉实现在代码上比较困难,我先对着答案写一遍理解一下吧。

会了

首先明确这道题的思路,来看一下一共分几步:

第一步:如果数组大小为零的话,说明是空节点了。

第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

第五步:切割后序数组,切成后序左数组和后序右数组

第六步:递归处理左区间和右区间

这里明确区间不变的规则:左闭右开

根据后序找到根节点,去切割中序,获得左右子树的两部分。那怎么根据左右子树在后序序列中再去切呢?中序数组大小一定是和后序数组的大小相同的。所以拿左子树的长度去切一下后续序列,剩下的就是右子树的部分了。

再一个我觉得难的,就是左右递归时的参数,要慢慢细想一下。
我第一次写成了:
root.right = findNode(inorder, index + 1, inEnd, postorder, postBegin + lenOfLeft + 1, postEnd);

应该是root.right = findNode(inorder, index + 1, inEnd, postorder, postBegin + lenOfLeft, postEnd - 1);

这个问题造成了stackoverflow报错,这里记录一下。

问题:
map.get() 为什么会报错java.lang.StackOverflowError at line 556,java.base/java.util.HashMap.get[流泪]
在这里插入图片描述
在这里插入图片描述

答案:
stackoverflow一般是函数调用层数过多, 对 应该是递归逻辑的问题 和map没啥关系

实现遇到的困难:

为了快速地在中序序列中找到切割的点,用HashMap给中序序列存起来,以val为key,下标为value。
我是照着敲的,这道题对我现在比较难。

代码:

class Solution {

    Map<Integer, Integer> map;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        map = new HashMap<>();
        // 用map保存中序序列的数值对应位置
        for (int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        //左闭右开
        return findNode(inorder, 0, inorder.length, postorder, 0, postorder.length);

    }

    public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {
        //左闭右开
        //如果区间就不对,那么就没有节点,返回空树
        if (inBegin >= inEnd || postBegin >= postEnd) return null;
        //通过map找到中序的分割点
        int index = map.get(postorder[postEnd - 1]);
        TreeNode root = new TreeNode(inorder[index]);
        //左子树长度
        int lenOfLeft = index - inBegin;
        root.left = findNode(inorder, inBegin, index, postorder, postBegin, postBegin + lenOfLeft);
        root.right = findNode(inorder, index + 1, inEnd, postorder, postBegin + lenOfLeft, postEnd - 1);
        return root;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值