题目介绍
描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
解题思路:
递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果。
写树相关的算法,简单说就是,先搞清楚当前 root 节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。
二叉树题目的一个难点在于如何通过题目的要求思考出每一个节点需要做什么
二叉树解题策略
一 递归 二 队列 + 迭代 (层次遍历) 三 栈 + 迭代 (非递归遍历) 四 其它
三种基本的遍历方式,都可以用递归来实现。写递归算法的时候,需要注意递归退出条件以及递归操作的表达。
这题和235有所不同,235是儿茶搜索树,左边都被根节点小,右边都比根节点大,但本题是二叉树
自己的解法实现
def lowestCommonAncestor3(self, root, p, q):
if not root: return None # 叶子节点,直接返回空节点,也即root
if p == root or q == root: # 如果p、q中有一个是当前节点root,则直接返回当前节点
return root # 返回了节点p或者q
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right: # 如果左右子树的返回值都不为空,此时root为p、q的最近公共祖先,返回root
return root
return left or right
网上比较优秀的解法
解法一
解题思路: 祖先的定义: 若节点 p 在节点 root 的左(右)子树中,或 p=root ,则称 root 是 pp 的祖先
最近公共祖先的定义: 设节点 root 为节点 p,q 的某公共祖先,若其左子节点 root.left 和右子节点 root.right 都不是 p,q 的公共祖先,则称 root 是 “最近的公共祖先” 。
根据以上定义,若 root 是 p,q 的 最近公共祖先 ,则只可能为以下情况之一:
p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中); p=root ,且 qq 在 root 的左或右子树中; q=root ,且 pp 在 root 的左或右子树中;
def lowestCommonAncestor(self, root, p, q):
if not root or root == p or root == q: return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left: return right
if not right: return left
return root
解法二
利用二叉搜索树的性质,即第235题的方法,将每个节点对应一个值,用字典存储起来再访问。
def lowestCommonAncestor2(self, root, p, q):
self.value = 0
_dic = {}
def inorder(node):
if not node: return
inorder(node.left)
self.value += 1
_dic[node] = self.value
inorder(node.right)
def dfs(node, p, q):
if not node: return
if _dic[node] > max(_dic[p], _dic[q]):
return dfs(node.left, p, q)
if _dic[node] < min(_dic[p], _dic[q]):
return dfs(node.right, p, q)
return node
inorder(root)
return dfs(root, p, q)
相关知识总结和思考
相关知识:
BFS:广度/宽度优先。其实就是从上到下,先把每一层遍历完之后再遍历一下一层。
可以使用Queue的数据结构。我们将root节点初始化进队列,通过消耗尾部,插入头部的方式来完成BFS。
二叉搜索树(BST)的特性:
- 若它的左子树不为空,则所有左子树上的值均小于其根节点的值
- 若它的右子树不为空,则所有右子树上的值均大于其根节点的值
- 它的左右子树也分别为二叉搜索树
递归与迭代的区别
递归:重复调用函数自身实现循环称为递归; 迭代:利用变量的原值推出新值称为迭代,或者说迭代是函数内某段代码实现循环;