50-求树中两个结点的最低公共祖先(多种解法扩展)

一、题目描述

【题型一】输入一个树的两个结点,限定该树为二叉搜索树,求这两个结点的最低公共祖先(最低父亲结点)

【题型二】输入一个树的两个结点,该树为普通树,求这两个结点的最低公共祖先(最低父亲结点),ps:树中每个节点有parent(三叉链)

【题型三】输入一个树的两个结点,该树为普通树,求这两个结点的最低公共祖先(最低父亲结点)限定条件:该树不存在指向父结点的指针

二、解题思路

【题型一】

二叉搜索树的特点是:左结点值<根结点值<右结点值
根据这个特点,已知两个结点后,可从根结点开始遍历树,
1)如果当前结点的值均大于两个结点,则最低公共祖先在当前结点的左边,走左子树
2)如果当前结点的值均小于两个结点,则最低公共祖先在当前结点的右边,走右子树
3)如果当前结点的值在两个结点之间(可包含两个结点中的一个),则该结点就是最低公共祖先
如上递归

【题型二】

如果该树的结点中存在指向父结点的指针,那么这道题就转换成了求两个链表的第一个公共结点
            A
          /   \
        B     C
      /     \
    D        E
   / \      /  |  \
 F   G  H  I  J
 比如输入的两个结点分别是F和H,因为它们都有指向父结点的指针,因此F可以根据父结点形成一个
 链表:F->D->B->A,
 同理:H->E->B->A,
 那么F和H的最低公共祖先就是这两个链表的第一个公共结点B

【题型三】

            A
          /   \
         B     C
        /   \
     D       E
   /   \    /  |  \
 F   G  H  I  J
【举例】如上图的树,求树中结点H和F的最低公共祖先,找结点H的思路如下:
1)遍历此树(前序遍历),用链表保存到达H的遍历路径,例如:
    a)遍历到A,把A放入路径中;
    b)遍历到B,把B放入路径中;
    c)遍历到D,把D放入路径中;
    d)遍历到F,把F放入路径中,此时路径为A->B->D->F,F已经没有子结点了,因此这条路径不可能
    到达H,把F从结点中删除,变成A->B->D
    e)遍历到G,把G放入路径中,此时路径为A->B->D->G,G已经没有子结点了,因此这条路径不可能
    到达H,把G从结点中删除,变成A->B->D,D已经没有子结点了,但还没找到H,把D从路径中删除,
    路径变成A->B
    f)遍历到E,把E放入路径中,此时路径为A->B->E
    h)遍历到H,已经到达目标结点,路径A->B->E->H就是到达H的路径
2)遍历此树(前序遍历),用链表保存到达F的遍历路径
3)找到两条路径后:
    a)到达H的路径:A->B->E->H
    b)到达F的路径:A->B->D->F
    问题变成找两个链表最后一个公共结点,就是要找的最低公共祖先

三、解题算法

【题型一】二叉搜索树中两个结点最低公共祖先的查找

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 一次遍历,不记录遍历路径。时间复杂度o(klog(n)),最坏O(n),空间复杂度O(1) 
        result = root
        while root:
            if p.val > root.val and q.val > root.val:
                root = root.right
                result = root
            elif p.val < root.val and q.val < root.val:
                root = root.left
                result = root
            else:
                break
        return result

【题型二】普通树(带有指向父结点指针)中两个结点最低公共祖先的查找

/*************************************
author:tmw
date:2018-8-14
*************************************/
#include <stdio.h>
#include <stdlib.h>

/**假定该树有5个结点,还有一个指向父亲结点的指针**/
typedef struct TreeNode
{
    char data;
    struct TreeNode* child1;
    struct TreeNode* child2;
    struct TreeNode* child3;
    struct TreeNode* child4;
    struct TreeNode* child5;
    struct TreeNode* father;
}TreeNode;

/** findLastCommonFatherNode
 @param TreeNode* root 提供的树
 @param TreeNode* node1 待找的第一个结点
 @param TreeNode* node2 待找的第二个结点
**/
TreeNode* findLastCommonFatherNode( TreeNode* root, TreeNode* node1, TreeNode* node2 )
{
    if( root == NULL || node1 == NULL || node2 == NULL ) return NULL;

    /**分别构建两个结点到根节点的链表,并分别记录链表长度**/
    TreeNode* list1 = node1;
    TreeNode* list2 = node2;
    int len1 = 0;
    int len2 = 0;

    //构建list1
    while( list1 != root )
    {
        list1 = list1->father;
        len1++;
    }
    //构建list2
    while( list2 != root )
    {
        list2 = list2->father;
        len2++;
    }

    /**转化成找两个链表的第一个公共结点**/
    int gap = abs(len1-len2);
    //当len1大于len2时,让list1先走len1-len2步
    if( len1 > len2 )
    {
        int step = gap;
        while( step > 0 )
        {
            list1 = list1->father;
            step--;
        }
    }
    //当len2大于len1时,则list2先走len2-len1步
    if( len2 > len1 )
    {
        int step = gap;
        while( step > 0 )
        {
            list2 = list2->father;
            step--;
        }
    }
    //准备完毕后,开始找公共结点
    while( list1 != list2 )
    {
        list1 = list1->father;
        list2 = list2->father;
    }

    return list1;
}

【题型三】不含指向父结点的普通树中,两个结点最低公共祖先的查找

方法1:递归-记录从根节点到目标节点的路径

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # 分别存储从父节点到p和q节点的有效路径path_p和path_q
        # 遍历两个路径,找到最后一个相同的结点即为所求最近公共祖先
        
        # 存储从根节点到目标节点的有效路径
        def get_node_path(node, target_node, path):
            # 走到叶子节点了还没找到target_node,返回false
            if not node:
                return False
            path.append(node)
            # 找到了目标节点
            if node.val == target_node.val:
                return True
            
            # 开始对root的左孩子部分和右孩子部分进行递归查找
            if (get_node_path(node.left, target_node, path) or get_node_path(node.right, target_node, path)):
                return True
            # 返回递归前的状态
            path.pop()
        
        path_p = []
        path_q = []
        get_node_path(root, p, path_p)
        get_node_path(root, q, path_q)

        # # 遍历两个路径,找到最后一个相同的结点即为所求最近公共祖先
        common_node = None
        for (node1, node2) in zip(path_p, path_q):
            if node1.val == node2.val:
                common_node = node1
        return common_node

方法2:递归 -返回当前节点 node 到 目标节点 target_node 的路径,没有则返回空

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:

        # 返回当前节点 node 到 目标节点 target_node 的路径,没有则返回空
        def get_node_path(node, target_node):
            if not node:
                return []
            if node.val == target_node.val:
                return [node]
            
            left_child_path = get_node_path(node.left, target_node)
            if left_child_path:
                return [node] + left_child_path
            right_child_path = get_node_path(node.right, target_node)
            if right_child_path:
                return [node] + right_child_path
            
        
        path_p = get_node_path(root, p)
        path_q = get_node_path(root, q)

        # # 遍历两个路径,找到最后一个相同的结点即为所求最近公共祖先
        common_node = None
        for (node1, node2) in zip(path_p, path_q):
            if node1.val == node2.val:
                common_node = node1
        return common_node

方法3:递归 - 同时返回当前节点 node 到 目标节点 p 和 q 的路径,没有则返回空

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:

        # 同时返回当前节点 node 到 目标节点 p 和 q 的路径,没有则返回空
        def get_node_path(node, p, q):
            p_path = []
            q_path = []
            if node.val == p.val:
                p_path = [node]
            if node.val == q.val:
                q_path = [node]
            if node.left:
                p_l_path, q_l_path = get_node_path(node.left, p, q)
                if p_l_path:
                    p_path = [node] + p_l_path
                if q_l_path:
                    q_path = [node] + q_l_path
            if node.right:
                p_r_path, q_r_path = get_node_path(node.right, p, q)
                if p_r_path:
                    p_path = [node] + p_r_path
                if q_r_path:
                    q_path = [node] + q_r_path
            
            return p_path, q_path

        path_q = []
        path_p = []
        path_p, path_q = get_node_path(root, p, q)

        # # 遍历两个路径,找到最后一个相同的结点即为所求最近公共祖先
        common_node = None
        for (node1, node2) in zip(path_p, path_q):
            if node1.val == node2.val:
                common_node = node1
        return common_node

  方法4:后序遍历 - 递归 - 不存储路径

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # 后序遍历 - 递归 - 不存储路径
        # 从叶子节点开始向上找,传递三个状态:
        # 1)节点q是否为当前节点的孩子节点
        # 2)节点p是否为当前节点的孩子节点
        # 3)当前节点是否为最近公共祖先,若是,则直接返回,不用上传状态,不是则向上传递状态

        # 后序遍历判断p、q是否为 node 节点的孩子
        def dfs(node, p, q):
            # dfs边界判定
            if not node:
                return False, False, None
            is_child_q = False
            is_child_p = False
            nearest_node = None

            # 左
            l_p, l_q, nearest_node = dfs(node.left, p, q)
            # 如果左子树已经找到公共节点,状态不用向上传递了,直接返回这个公共节点
            if nearest_node:
                return False, False, nearest_node

            # 右
            r_p, r_q, nearest_node = dfs(node.right, p, q)
            # 如果右子树已经找到公共节点,状态不用向上传递了,直接返回这个公共节点
            if nearest_node:
                return False, False, nearest_node

            # 当前节点就为q/p
            # 当前节点孩子节点为q/p,将孩子节点为 q/p 的状态上传
            if node.val == p.val or l_p or r_p:
                is_child_p = True
            if node.val == q.val or l_q or r_q:
                is_child_q = True

            # 如果q和p都为当前节点的孩子节点,直接返回当前节点(有答案就返回,状态不用上传了)
            if is_child_p and is_child_q:
                return is_child_p, is_child_q, node

            return is_child_p, is_child_q, nearest_node

 方法5:后序遍历 - 非递归 - 存储从根节点到目标节点的路径

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # 后序遍历 - 非递归 - 存储从根节点到目标节点的路径
        # 借用栈实现非递归后序遍历      
        node = root
        stack = []
        path_p = []
        path_q = []
        prenode = None
        while node or stack:
            while node:
                stack.append(node) # 根
                # stack_tmp.append(node)

                # 若当前节点为q/p, 则用path_q/path_p存储路径
                if node.val == p.val:
                    path_p = stack.copy()
                elif node.val == q.val:
                    path_q = stack.copy()
                if path_p and path_q:
                    common = None
                    for (node1, node2) in zip(path_p, path_q): 
                        if node1.val == node2.val:
                            common = node1
                    return common
                node = node.left  # 左

            node = stack.pop()
            # 叶子节点 or 该节点右子树部分已经遍历过了 -> 将该路径从stack中pop出来
            if not node.right or node.right == prenode:
                prenode = node
                node = None
            # 该节点不是叶子节点且该节点的右子树还没有被遍历过
            else:
                stack.append(node)
                node = node.right   # 右
        return None

  

                                                           梦想还是要有的,万一实现了呢~~~~ヾ(◍°∇°◍)ノ゙~~~~~~~~~~~~~~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值