一、题目描述
【题型一】输入一个树的两个结点,限定该树为二叉搜索树,求这两个结点的最低公共祖先(最低父亲结点)
【题型二】输入一个树的两个结点,该树为普通树,求这两个结点的最低公共祖先(最低父亲结点),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
梦想还是要有的,万一实现了呢~~~~ヾ(◍°∇°◍)ノ゙~~~~~~~~~~~~~~~~~