其他分享
首页 > 其他分享> > 236 二叉树的最近公共祖先

236 二叉树的最近公共祖先

作者:互联网

LeetCode 第236题 二叉树的最近公共祖先 做题记录

题目描述

在这里插入图片描述

我的解法

思路

  1. 遍历过程中记录节点p、q的所有祖先节点(按深度从小到大按顺序添加)
    cur.left/cur.right 就代表着这父子关系,可以将cur加入到记录的链结构中,同时涉及到回溯。

(1)参数:访问节点,p或q,祖先列表 ;返回值:boolean
(2)终止条件:当节点为null时,返回false,当节点为q或p时,返回ture
(3)将当前节点加入祖先列表,对其左节点和右节点进行递归。递归结束后进行一次回溯,将祖先列表尾部的一个节点弹出。

  1. 获取到两个节点的祖先顺序表后,只要比遍历寻找最小的一个就可以了

对应Java代码

在这里插入图片描述

class Solution {
    public List<TreeNode> ancestor = new LinkedList<TreeNode>();
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(p == null || q == null) {return null;}
        traversal(root, p);
        TreeNode[] ancestorP = new TreeNode[ancestor.size()];
        for (int i = 0; i < ancestorP.length; i++) {
            ancestorP[i] = ancestor.get(i);
        }
        ancestor.clear();
        traversal(root, q);
        TreeNode[] ancestorQ = new TreeNode[ancestor.size()];
        for (int i = 0; i < ancestorQ.length; i++) {
            ancestorQ[i] = ancestor.get(i);
        }
        for(int i = ancestorP.length - 1; i >= 0 ; i--){
            for(int j = ancestorQ.length - 1; j >= 0 ; j--){
                if(ancestorP[i].val == ancestorQ[j].val){
                    return ancestorP[i];
                }
            }
        }
        return null;
    }
    public boolean traversal(TreeNode cur, TreeNode target){
        // 1.修改处1
        ancestor.add(cur);
        if(cur == null) {return false;}
        if(cur.val == target.val) {return true;}
        if(traversal(cur.left, target) == true) {return true;}
        ancestor.remove(ancestor.size() - 1);
        if(traversal(cur.right, target) == true) {return true;}
        ancestor.remove(ancestor.size() - 1);
        return false;
    }
}

编码过程中主要解决的几个问题

  1. 递归过程中使用ancestor.remove(ancestor.size() - 1);回溯时没有考虑到空节点提前退出的情况,这时候没有节点入队列依旧进行了回溯。
    修改方法就是在进行终结条件判断前把节点入队列(即使是null)
  2. ancestorP和ancestorQ一开始使用了列表的浅拷贝List<TreeNode> ancestorP = ancestor 这种实际上是赋值了引用,导致ancestor修改后,P和Q会同时变化,两者总是一致的。故用数组的方式进行存储。

复杂度分析

时间复杂度:O(n²) 对树进行了两次遍历,每次时间复杂度都是O(n) 但是后续对两个数组查找公共值的情况,最坏情况是O(n²)
空间复杂度:O(n)

更优解法

此部分转载于公众号代码随想录

思路

  1. 二叉树如何自底向上查找呢?
    遇到这个题目首先想到的是能自底向上查就好了,这样就能找到公共祖先了。
    回溯的过程就是从低到上,后序遍历就是天然的回溯过程,最先处理的一定是叶子节点。

  2. 如何判断一个节点是节点q和节点p的公共祖先呢?
    如果找到一个节点,发现左子树出现节点p,右子树出现节点q,或者相反,那么该节点就是节点p和q的最近公共祖先。

递归三部曲:

搜索一条边的写法:

if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

搜索整个树写法;

left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;

而本题对left和right的逻辑处理:
1.「如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解」
2.「如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然」。
完整流程图如下:
在这里插入图片描述

对应Java代码

在这里插入图片描述

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) {return root;}
        TreeNode left = lowestCommonAncestor(root.left, p , q);
        TreeNode right = lowestCommonAncestor(root.right, p , q);
        if(left != null && right != null) {return root;}
        else if(left != null && right == null){return left;}
        return right;
    }
}

总结

「那么我给大家归纳如下三点」:

  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式。
  2. 在回溯的过程中,必然要遍历整颗二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。

标签:right,TreeNode,祖先,二叉树,236,ancestor,root,节点,left
来源: https://blog.csdn.net/qq_34332616/article/details/115678542