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. 路径总和
本题 又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解
- 路径总和,和 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.从前序与中序遍历序列构造二叉树 一起做,思路一样的
第一印象:
他说比较难,那我直接看视频哈哈
看完题解的思路:
过程听懂了,但感觉实现在代码上比较困难,我先对着答案写一遍理解一下吧。
会了
首先明确这道题的思路,来看一下一共分几步:
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
这里明确区间不变的规则:左闭右开
根据后序找到根节点,去切割中序,获得左右子树的两部分。那怎么根据左右子树在后序序列中再去切呢?中序数组大小一定是和后序数组的大小相同的。所以拿左子树的长度去切一下后续序列,剩下的就是右子树的部分了。
再一个我觉得难的,就是左右递归时的参数,要慢慢细想一下。
我第一次写成了:
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;
}
}