最近公共祖先 LCA
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
倍增法
倍增法:binary lifting。
它能够使线性的处理转化为对数级的处理,大大地优化时间复杂度。常用于RMQ 问题和LCA问题。
“倍增”与“二进制划分”两个思想相互结合,降低了求解很多问题的时间与空间复杂度。
基本用法如下:倍增主要用途是为了查找单调数据组中某一数值。
比如:在一个数组a {2,5,7,11,19} 中查找最大的小于12的数字。
(1)朴素做法:从第一个数开始,一个一个往后枚举,查找。
(2)二分做法:每次将数列分割一半判断,并且进一步查找子区间。
(3)倍增做法:设定一个增长长度 p 和已确定长度 l,现在要确定 a[l+p] 是否满足条件,若满足条件(比12小),则p成2倍增长;否则 p 缩小范围(试着缩小范围判断条件)。
倍增算法一般比较稳定,时间 O(logn)。
问题解决思路
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
解题思路如下:
- 查找两个指定节点在二叉树中的father路径
- 对两个路径的公共长度,采用倍增法查找
节点代码如下:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
查找节点的father路径
List<TreeNode> getNodeFatherPath(TreeNode curNode, TreeNode target, List<TreeNode> path) {
path.add(curNode);
// 找到目标节点,返回
if (curNode.val == target.val) {
return path;
}
// 左节点存在
if (Objects.nonNull(curNode.left)) {
List<TreeNode> result = getNodeFatherPath(curNode.left, target, path);
if (!result.isEmpty()) {
return result;
}
}
if (Objects.nonNull(curNode.right)) {
List<TreeNode> result = getNodeFatherPath(curNode.right, target, path);
if (!result.isEmpty()) {
return result;
}
}
path.remove(curNode);
return new ArrayList<>();
}
倍增查找LCA
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 查找节点路径
List<TreeNode> pPath = getNodeFatherPath(root, p, new ArrayList<>());
List<TreeNode> qPath = getNodeFatherPath(root, q, new ArrayList<>());
int len = pPath.size(); // 两个节点路径的公共长度
if (len > qPath.size()) {
len = qPath.size();
}
// 如果初始两个节点在一个路径,直接返回
if (pPath.get(len - 1).val == qPath.get(len - 1).val) {
return pPath.get(len - 1);
}
while (true) {
int number = getNumber(len); // 2的number次方小于len
while (true) {
int skipStep = (int) Math.pow(2, number); // 需要跳的层级数
int index = len - skipStep - 1; // 跳之后的节点层级
if (pPath.get(index).val == qPath.get(index).val) { // 节点值相同,表示重合,继续跳
number--;
} else if (pPath.get(index - 1).val == qPath.get(index - 1).val) { // 节点值不同,但是父节点相同,表示查找成功
return pPath.get(index - 1);
} else { // 缩短长度,继续跳
len = index + 1;
break;
}
}
}
}
/**
* 计算倍增时,从2的n次方开始 中的n
* 也就是 最接近target的2的次幂
* @param target
* @return
*/
int getNumber(int target) {
int start = 1;
for (int i = 0; i < 31; i++) {
start *= 2;
if (start > target) {
return i - 1;
}
}
return 0;
}