LeetCode 235/236 LCA最近公共祖先问题 递归题解,再次深入理解DFS、左右子树同时查找。
235. 二叉搜索树的最近公共祖先#
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
利用BST的性质遍历#
BST
的一个关键性质是:
1
|
root.left.val < root.val < root.right.val
|
借由这个性质,查找LCA
,可以分为如下情况:
- root在p、q中间:root为lca
- root在p、q左侧:root转移到root.left
- root在p、q右侧:root转移到root.right
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while (true) {
if (root.val > p.val && root.val > q.val) {
root = root.left;
continue;
}
if (root.val < p.val && root.val < q.val) {
root = root.right;
continue;
}
return root;
}
}
|
复杂度分析#
整个过程最多遍历完整个树:复杂度O(n)
。
使用while(true)
替换了递归,无额外空间占用,空间复杂度O(1)
。
236. 二叉树的最近公共祖先#
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
DFS同时向左右子树查找#
同时去左右子树上匹配p、q:
- root已经是某个节点,说明root就是两个节点的LCA;
- 左右子树均有匹配节点,说明公共祖先在root上;
- 只有左子树匹配到了p、q之一,说明右子树上没有p、q祖先;
- 只有右子树匹配到了p、q之一,说明左子树上没有p、q祖先;
当然,如果题干给定可能无LCA
的入参,那么左右子树的dfs
戳到底,返回的就是null
。
此类题目,我觉得理解到同时DFS左右子树
为精髓。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
static class DFS {
/**
* 同时去左右子树上匹配p、q
* 1. root已经是某个节点,说明root就是两个节点的LCA;
* 2. 左右子树均有匹配节点,说明公共祖先在root上;
* 3. 只有左子树匹配到了p、q之一,说明右子树上没有p、q祖先;
* 4. 只有右子树匹配到了p、q之一,说明左子树上没有p、q祖先;
* <p>
* 当然,如果题干给定可能无LCA的入参,那么左右子树的dfs戳到底,返回的就是null。
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return dfs(root, p, q);
}
/**
* 从root树上找p或者q,找到则返回对应节点
* root树包含了:左右子树
*/
private TreeNode dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
// 已经戳到底了
return null;
}
// 当前遍历节点已经找到了
if (root.val == p.val || root.val == q.val) {
return root;
}
// 左子树是否有对应节点?
TreeNode leftFound = dfs(root.left, p, q);
// 右子树是否有对应节点?
TreeNode rightFound = dfs(root.right, p, q);
if (leftFound != null && rightFound != null) {
// 左右子树均有匹配节点,说明公共祖先在root上
return root;
}
if (leftFound != null) {
// 只有左子树匹配到了p、q之一,说明右子树上没有p、q祖先
return leftFound;
}
// 只有右子树匹配到了p、q之一,说明左子树上没有p、q祖先
return rightFound;
}
}
|
复杂度分析#
整个过程最多遍历完整个树:复杂度O(n)
。
递归主要为栈的开销,最差情况下树退化为链表,空间复杂度O(n)
。