经典算法——二叉搜索树的公共祖先节点

介绍

二叉搜索树的公共祖先节点是指:对于给定的两个节点p、q,向上寻找离二者最近的公共节点。

public class Node<T extends Comparable<T>> {
	T value;
	Node<T> left;
	Node<T> right;
	public Node() {}
	public Node(T t) { value = t; }
}

bst树
如图

  • 2和4的公共祖先节点是3
  • 2和6的公共祖先节点是5
  • 6和7的公共祖先节点是7

需要注意的是,如果p是q的父节点,则二者的公共祖先节点是p,反之亦然。

解决办法

方式一

因为是搜索树,所以每个节点保存的值肯定不一样,所以我们可以遍历整棵树,记录从根节点到p的路径,根节点到q的路径

  1. 判断p、q是否满足父子关系,如果是,返回其中的父节点;
  2. 逆序的遍历一个路径的节点,同另一路径依次比较,若找到相同的节点,该节点即为公共祖先节点;
  3. 其他情况返回null

例如要判断4和7的公共祖先节点

4的路径:5 3 4
7的路径:5 7

首先判断4和7不是父子关系,然后开始遍历路径做比较
路径比较
橙色块代表找到了第一个相同的节点,故4和7的公共祖先节点是5。

public class Node<T extends Comparable<T>> {
	T value;
	Node<T> left;
	Node<T> right;
	public Node() {}
	public Node(T t) { value = t; }
}
//----------------------------------
public Node getLCA(Node<T> root, Node<T> p, Node<T> q) {
	//如果具有父子关系,返回父节点
	if(p.left == q || p.right == q) return p;
	if(q.left == p || q.right == p) return q;
	//查找路径
	List<Node<T>> pPath = new ArrayList<>();
	List<Node<T>> qPath = new ArrayList<>();
	dfs(pPath, root, p);
	dfs(qPath, root, q);
	//比较路径节点,找到公共节点
	return getLCA(pPath, qPath);
}
/**
* @path 记录从根节点到目标的路径节点
* @node 当前节点
* @aim  目标节点(p | q)
* @return 如果找到了目标节点,返回true
*/
private boolean dfs(List<Node<T>> path, Node<T> node, Node<T> aim) {
	if(node == null) return false;
	path.add(node);
	if(node == aim) return true;
	if(dfs(path, node.left, aim)) return true;
	if(dfs(path, node.right, aim)) return true;
	path.remove(path.size() - 1);
	return false;
}
private Node getLCA(List<Node<T>> pPath, List<Node<T>> qPath) {
	for(int i = pPath.size() - 2; i >=0; i--) {
		Node node = pPath.get(i);
		for(int j = qPath.size() - 2; j >= 0; j--) {
			if(node == qPath.get(j)) return node;
		}
	}
	return null;
}

这种方式最坏的情况下需要扫描整棵树,还需要另个集合存路径,之后还要逐个比较路径的节点值,时空花费都不小,我们来看第二种方式。

方式二

我们换一种思路,还是上面这棵树,从根节点出发,到达任意节点x,如果p、q的值都比x小,就走x的左子树;如果p、q的值都比x大,就走x的右子树。
左子树方向
拿上图为例,如果p、q都比x小,那么x一定是p、q的公共祖先节点,但x并不是最近的公共祖先节点,最近的公共祖先节点一定在x的左子树上。

第二种情况,如果p、q一个比x大,一个比x小呢?
公共祖先节点
如果是这样的话,那x恰好就是他俩的公共祖先节点了,稍加思考就会发现,x就是p、q的最近公共祖先节点。

第三种情况,p、q具有父子关系。
直接父子关系
当x = p | q时,p、q既不在x的一边,也不在x的两侧,那直接返回x就好了。这种情况的处理方式和第二种情况处理方式一致,都是直接返回x。

public class Node<T extends Comparable<T>> {
	T value;
	Node<T> left;
	Node<T> right;
	public Node() {}
	public Node(T t) { value = t; }
}
//----------------------------------
public Node getLCA(Node<T> root, Node<T> p, Node<T> q){
	if(root == null) return null;
	int pResult = root.value.compareTo(p.value);
	int qResult = root.value.compareTo(q.value);
	//p、q在x的同一侧
	if(pResult > 0 && qResult > 0){
		return getLCA(root.left, p, q);
	}else if(pResult < 0 && qResult < 0){
		return getLCA(root.right, p, q);
	}else{
		//p、q在x的两侧 || x = p | q
		return root;
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值