二叉树是用来训练递归的好题目。
你要相信你函数的定义(因为这是你自己写的根据当前节点所做的所有操作,那递归调用当然可以信赖)。写好这个函数签名也是很有意义的工作,一般题目给的够用了,但是有时候返回的数据其实是不够看的,或者方法参数给的也不够用,这时候就需要我们自定义,比如1373.二叉搜索子树的最大键值和(困难),这题我们采用后续遍历的方法,需要的参数很多,所以返回值用的是一个数组,当然你用的爽了,自己这个节点也要写出来对应的确定返回值方法,不能光用不给,没这个道理,递归也不能用。
函数的作用也需要好好斟酌,比如返回最近公共祖先,明面上是返回最近公共祖先节点,但是你调用此方法的时候,想过对左右节点调用,最近公共祖先没有在左节点,左子树下只有一个值怎么办,你也要返回呀,所以遇到值为p或者q的时候也要返回,也对应root节点为p或者q的时候,直接返回。
关于函数签名再多说两句,以二叉搜索树的合法性为例,其参数除了root,还有min和max,这些在第一个节点和最后一个都没用到,第一个节点没有父节点,min、max都是null,而最后一个本身为null,也就直接返回了,那我考虑节点的功能,因为第一个和最后一个一般比较具有代表性,在这时候就不够了,需要考虑其他节点了,这种想法要记着。你考虑的是节点的功能完备,不要局限于特定节点带来的好处而思维受限,想想正常的节点也挺好的。
- 写出base case(思路主要在最后一个方法调用的节点(傻傻的走完全程结束条件)以及第一个节点正常需要的操作,此外考虑是否需要提前结束递归,也可能不用傻傻的到最后一个节点,走完全程才结束,有可能需要提前结束(比如二叉树的序列化与反序列化,读到#符号,就可以return了))。首先思路来到递归方法调用的最后一个节点,写出整个方法的终止条件,不能让他无休止的调用下去。有时候也要思路回到第一个节点,先序遍历,直接写出这个节点需要的操作,他并不依赖后续left、right的结果;有时候写需不需要调用递归,比如寻找最近公共祖先,如果这个节点等于p或者q,根本不需要调用后续的递归方法,直接提前结束了。
- 这时候开始调用递归方法,相信你的递归,你自己写的方法,只要够完备,对这个节点管用,那对下一个也管用
- 写出后续遍历的操作,此时你已经有了左右子节点的返回数据,直接对此进行操作。
236.二叉树的最近公共祖先
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
if(root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left == null){
return right;
}
if(right == null){
return left;
}
return root;
}
}