层序遍历 + 找到最后一行的第一个值
思路: 确定一个变量res 不停地把每一层的第一个值*放到变量里,这样最后一个一定是最后一行的数值。
*:很小的地方需要注意,因为size是作为他是否进入循环的标准 ,他会在每一次poll之后减1,所以如果用的while循环还需要用另外一个变量保存这个size 作为判定该node是否是第一个
这个题目和昨天的记录路径很像。
首先递归三部曲:
- 判定停止条件: 本身为null的时候 说明走到了子节点下面,但是还没找到符合条件的路径,返回false即可. 如果只有一个节点,这样root.left/ right 就为空,单独拿出来判定(判定是否为0)
- 单层判定逻辑: 如果走到了最后的节点,就可以进行停止的判定:减去自身的值,看是否等于0.
- 向下递归:这里有一个回溯的需求:因为之后右子树还需要该值,所以递归返回回来之后,要把减去的值再加回来。至于什么时候需要回溯,把卡哥小助手的原话贴在这里,觉得说的很直观,也很有用,自己要多多练习才能更实际的掌握这句话!
用不用回溯 取决于修改后的变量传入下一层函数后 本层函数还需不需要它了 是需要它改变后的值还是原值 一般要原值就回溯 要修改后的值通过定义函数返回值来得到
对于我自己来说,回溯有点像 rollback的感觉, 回到上一层把处理好的数据再改回来。再开始探索新的路线。 具体到代码就是
....前面的处理逻辑
//如果把回溯的过程写出来
if (root.left != null){
//进入下一层的时候 减去了自身的数值
targetSum -= root.val;
boolean check = hasPathSum(root.left, targetSum);
if (check) return true;
//之后右子树还要用所以要把数值在进去,然后再在右子树逻辑中剪掉
targetSum += root.val;
}
//简略版如下:可以看到进入到左边的值。 和右边的值是一样的。间接的完成了递归
boolean leftRes = hasPathSum(root.left, targetSum - root.val);
boolean rightRes = hasPathSum(root.right, targetSum - root.val);
105: 从前序与中序遍历序列构造二叉树
根据遍历性质, preorder 的数组是中 左 右。第一个一定是 root, 并且后面的一定跟着的是左子树的root,
eg:
preorder = [3,9,20,15,7],
inorder = [9,3,15,20,7]
第一次遍历,3 一定是整个树的根节点。通过这个在中序中找到左右子树的范围找到范围之后,分别进行左右子树的递归。这样再次调用的时候,index 会继续往下一个到9,而这个9也是左子树的下一个root以此类推。
递归调用三要素:
- 停止条件: 因为是一直再不停的切割找root。有些像二分的判定, 如果找到 Ind已经不合理了 即无法构成区间(StartInd >= EndIndex)左闭右开所以有等于。
- 单层递归: 一个个找root, 定义为root节点,左递归,右递归
- 最后返回一开始的root节点即可
优化成为O(N): 对Preorder 的值去Inorder 找index,可以用一个map 记录,就避免了每次都要重新弄去Inorder 的数组里找Index
简化输入参数版本:
Inorder 的起始和截止都需要 这没有办法,因为每次都要找rootInd,他就是左子树的截止,右子树的开始,所以一定需要作为参数输入。
preorder 中只用到了StartInd, 并且他是一直递增的,所以可以直接在每次递归里递增,就不用作为参数的输入了,否则的话,记得在left 子树中 +1 right 子树中是需要加左子树的Length而不是也加1,( 虽然很简单的错误但是我还是犯了,哈哈)
106.从中序与后序遍历序列构造二叉树
可以说和105十分相似了,只不过这里是从最后一个往前遍历,找root.
通过这两个,大概总结一下:
中序遍历的目的是通过另外一个数组找到的root, 知道左右的值
另外一个序 的目的是为了找root
Debug 树结构的小技巧:
在递归函数里第一行就打印输入值(防止return null 的那个截止层没来得及打出来),看每一个有没有输入参数就错误的问题。
知道每一行打印出来的是对应的哪一层哪一边的树,可以在草稿纸上画出你所建立的树对应的位置打印的值