查找二叉树子节点的最近共同父节点

查找二叉树子节点的最近共同父节点


给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

image

实例1

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。

实例2

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

分析

对于二叉树来讲,由于左右子树指针的存在,使得正常情况下的自上而下遍历显得比较简单,而下而上的查找并不那么容易,所以一种直观的思维就是从根节点开始遍历,直到找到节点 p p p,记录路径数组为 p a t h _ p path\_p path_p,同理找到根节点到节点 q q q的路径数组 p a t h _ q path\_q path_q,只要能够找到两个路径组中最到的 i n d e x index index,使得 p a t h _ p [ i n d e x ] = = p a t h _ q [ i n d e x ] path\_p[index]==path\_q[index] path_p[index]==path_q[index],则 i n d e x index index对应的节点即为最近共同父节点。

实现

基于上述思考,尝试使用数组来进行路径存储。

struct Path {
	int capacity; //容量
	int occupy; // 实际占用量
	struct TreeNode **result;// 存储的路径节点
};
void addElement(struct Path *path, struct TreeNode* node) {	
	if (path->occupy == path->capacity) {
	// 扩容
		path->capacity *= 2;
		path->result = realloc(path->result, sizeof(struct TreeNode *) * path->capacity);
	}
	path->result[path->occupy++] = node;
}

struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
	struct Path *path_p = malloc(sizeof(struct Path));
	path_p->capacity = 50;
	path_p->occupy = 0;
	int size = sizeof(struct TreeNode *) * path_p->capacity;
	path_p->result = malloc(size);
	memset(path_p->result, 0 , size);
	struct TreeNode *current = root;
	while(current->val != p->val) {
		addElement(path_p, current);
		if (current->val > p->val) {
			current = current -> left;
		} else {
			current = current -> right;
		}
	}
    addElement(path_p, current);

    current = root;
	struct Path *path_q = malloc(sizeof(struct Path));
	path_q->capacity = 50;
	path_q->occupy = 0;
	size = sizeof(struct TreeNode *) * path_q->capacity;
	path_q->result = malloc(size);
	memset(path_q->result, 0 , size);
	while(current->val != q->val) {
		addElement(path_q, current);
		if (current->val > q->val) {
			current = current -> left;
		} else {
			current = current -> right;
		}
	}
    addElement(path_q, current);
	
	struct TreeNode *node = NULL;
	for(int pIndex = path_p->occupy - 1; pIndex >= 0; pIndex--) {
		for(int qIndex = path_q->occupy - 1; qIndex >= 0; qIndex--) {
		if(path_p->result[pIndex] == path_q->result[qIndex]) {
			return path_p->result[pIndex];
		    }
		}
	}
    return NULL;
}

算法复杂度

  • 时间复杂度:最坏的情况下,二叉搜索树变成了一个类似于链表的结构,而 p , q p,q p,q是在最底端的两个节点那么搜索 p , q p,q p,q节点的时间复杂度都可以达到 n n n( n n n为树中节点个数),时间复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度:同样最坏的情况下,需要使用开辟跟节点数相同的数组空间来存储节点路径,所以空间复杂度也为 O ( n ) O(n) O(n).

其他算法

对于上述算法来讲需要遍历两次树结构来获取跟节点到指定节点的路径,然后倒叙获取路径数组中第一个相同节点即可最近父节点.但事实上,可以尝试将两次查找合并在一起,对于当前节点 c u r r e n t current current,无非有三种情况:

  • current->val > p->val && current->val > q->val,则说明p,q均在current的左子树上,此时取current = current->left;
  • current->val < p->val && current->val < q->val,则说明p,q均在current的右子树上,此时取current = current->right;
  • 最后一种情况,要么current就是p或者q节点之一,要么p,q分别在current的左右子树上.也就是要查找的最近父节点。实现起来发给这个样子:
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
    struct TreeNode* ancestor = root;
    while (true) {
        if (p->val < ancestor->val && q->val < ancestor->val) {
            ancestor = ancestor->left;
        } else if (p->val > ancestor->val && q->val > ancestor->val) {
            ancestor = ancestor->right;
        } else {
           return ancestor
        }
    }
    return NULL;
}

这样就可以将时间复杂度降低为 O ( n ) O(n) O(n),同时空间复杂度也将为常数 O ( 1 ) O(1) O(1).

题目升级

如果题目中的树只是一颗普通的二叉树,那么最近父节点该怎么查找?
其实尝试将结果分类,会发现无外乎以下情况:

  • p,q结点分布在当前结点两侧或者当前结点就是p或者q之一,那么根结点就是最近父节点;
  • p,q结点在当前结点的左子树上,那么最近父结点肯定是第一个查询到的p或者q;
  • p,q结点分布在当前结点右子树上,那么那么最近父结点肯定是第一个查询到的p或者q;
    这样就可以使用递归进行查找:
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
   if (!root) return NULL;
   if (root->val == p->val || root->val == q->val ) return root; 
   struct TreeNode *left = lowestCommonAncestor(root->left, p, q);
   struct TreeNode *right = lowestCommonAncestor(root->right, p, q);
   if (left && right) return root;
   return left ? left : right;
}

同样最坏的情况是,二叉树退化成了一个类似于单链表的结构,p,q两个节点就在表的末端最后两个节点,这样的话,时间复杂度也会变为 O ( n ) O(n) O(n);不消耗额外的空间。

二叉树是一种非常重要的数据结构,它是由节点组成的树形结构,每个节点最多有两个子节点二叉树子系统是指在计算机科学中,用于实现二叉树的一组数据结构和算法。以下是二叉树子系统的一些基本概念和操作: 1. 二叉树的基本概念 - 节点:二叉树中的每个元素称为节点。 - 根节点:二叉树的顶部节点称为根节点。 - 叶子节点:没有子节点的节点称为叶子节点。 - 父节点子节点:每个节点都有一个父节点和零个或两个子节点。 - 深度:从根节点到某个节点的路径长度称为该节点的深度。 - 高度:从某个节点到其子树中最远叶子节点的路径长度称为该节点的高度。 2. 二叉树的遍历方式 - 前序遍历:先访问根节点,然后递归地遍历左子树和右子树。 - 中序遍历:先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。 - 后序遍历:先递归地遍历左子树和右子树,最后访问根节点。 - 层次遍历:按照从上到下、从左到右的顺序遍历每个节点。 3. 二叉树的操作 - 插入节点:在二叉树中插入一个新节点。 - 删除节点:从二叉树中删除一个节点。 - 查找节点:在二叉树查找一个节点。 - 计算叶子节点数:计算二叉树中叶子节点的数量。 - 计算总节点数:计算二叉树中所有节点的数量。 - 计算深度:计算二叉树的深度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值