题目
给定一棵二叉搜索树(Binary Search Tree,简称 BST),找出两个给定节点的最近公共祖先(Lowest Common Ancestor,简称 LCA)。
根据 LCA 的维基百科定义:“在一个树中同时拥有 v, w 作为后代的最深的节点,我们定义一个节点也是其自己的后代。”
_______6______
/ \
___2__ ___8__
/ \ / \
0 _4 7 9
/ \
3 5
示例:
节点 2、8 的 LCA 是 6。
节点 2、4 的 LCA 是 2,因为根据定义,一个节点也可以是自己的后代。
难度:容易
编程语言:C++
分析
程序框架为:
|
|
我的思路是:
- 如果 v, w 是相同,那么 LCA 就是 v,否则到第 2 步
- 判断 v 是否 w 的祖先节点(或相反),具体做法是:
2.1. 求出 v, w 的深度
2.2. 假设求出的 v 深度为 2,w 深度为 4
2.3. 沿 w 往上走 (4-2) 层,到达 w 的父节点的父节点 w’
2.4. 此时 v, w’ 深度相同,判断 v, w’ 是否相同,如果是,那么 v, w 的 LCA 就是 v,否则到第 3 步 - v, w’ 的深度相同(这里假设 v 的深度比 w 小),v, w’ 每往上走一步(即回溯到父节点),判断两个父节点是否相同,相同则返回,不相同则继续往上走,直到相同则返回。
伪代码如下:
|
|
好了,接下来 depth 函数要怎么写?我的思路是,使用一个 map<TreeNode*, TreeNode*>
,key 为节点,value 为父节点,保存每个节点的父结点,那么往上回溯就方便了。然后从 root 开始,把 left, right 的父节点保存到 map 中,一直往下递归。在生成 map 后,往上回溯就能求出 depth。因为每个节点只能有一个父节点,所以满足 map 一一映射的属性。
|
|
好了, 接下来 traverseUpward 函数:
|
|
代码如下:
|
|
提交到 Leetcode,Accepted! :),运行时间为 112ms。
后续
再重新看了一遍题目,才发现求二叉搜索树(BST)而不是二叉树(BT)的 LCA。我们看看二叉搜索树的维基百科定义:
- 任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
根据 BST 的定义,计算 LCA(v, w) 的算法就简单多了。LCA(v, w) 是从根节点开始(包括根节点)往下的第一个满足 node->val 在 [v->val, w->val] 区间的节点。
我们回看示例给出的 BST:
_______6______
/ \
___2__ ___8__
/ \ / \
0 _4 7 9
/ \
3 5
示例一
3、5 的最近公共祖先是 4,再往上的公共祖先是 2、6(一直到根节点)。那我们分析一下整条公共祖先链 6 -> 2 -> 4。
- 以 6 为根节点,3、5 都是在左子树,左子树所有节点值都小于根节点值,不满足 node->val 在 [v->val, w->val] 区间
- 以 2 为根节点,3、5 都是在右子树,右子树所有节点值都大于根节点值,不满足 node->val 在 [v->val, w->val] 区间
- 以 4 为根节点,3、5 分别是左、右子树,满足 node->val 在 [v->val, w->val] 区间
- 所以 4 是 3、5 的 LCA
示例二
我们再看看 LCA(4, 7),4、7 的最近公共祖先是 6,满足 node->val 在 [v->val, w->val] 区间。
示例三
我们再看看 LCA(2, 4),2、4 的最近公共祖先是 2,再往上的公共祖先是 6(一直到根节点)。那我们分析一下整条公共祖先链 6 -> 2。
- 以 6 为根节点,2、4 都是在左子树,左子树所有节点值都小于根节点值,不满足 node->val 在 [v->val, w->val] 区间
- 以 2 为根节点,满足 node->val 在 [v->val, w->val] 区间
抽象一点来说,v, w 可以分为三种情况:
- v == w,同一个节点,满足 v->val 和 w->val 在 [v->val, w->val] 区间
- v 是 w 的父节点,满足 v->val 在 [v->val, w->val] 区间
w 是 v 的父节点,满足 w->val 在 [v->val, w->val] 区间 - v, w 分别在最近公共祖先 lca 的左、右子树中,满足 lca->val 在 [v->val, w->val] 区间
- 从 lca 往上回溯,得到其父节点 lcaParent,那么 lca 就是 lcaParent 的左/右子节点。
- 如果 lca 是 lcaParent 的左子节点,那么 lcaParent->val 大于 lca->val,也大于 v->val 和 w->val,不满足 lcaParent->val 在 [v->val, w->val] 区间,所以 lcaParent 不是 LCA(v, w),lcaParent 再往上回溯同理。
- 如果 lca 是 lcaParent 的右子节点,那么 lcaParent->val 小于 lca->val,也小于 v->val 和 w->val,不满足 lcaParent->val 在 [v->val, w->val] 区间,所以 lcaParent 不是 LCA(v, w),lcaParent 再往上回溯同理。
代码
综上:LCA(v, w) 是从根节点开始(包括根节点)往下的第一个满足 node->val 在 [v->val, w->val] 区间的节点。
伪代码如下:
|
|
代码如下:
|
|
提交到 Leetcode,Accepted! :),运行时间为 44ms。