前引:
树可以分为好几种:普通树,二叉树(二叉链,三叉链),二叉搜索树等等,今天我们,讨论的的问题可就和就几种树有关。
我们先易后难,讨论二叉搜索树!
且所有的输入满足
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的树中。
二叉搜索树篇:
题解:
由于二叉搜索树是有序的,左子树全部小于根节点,右子树全部大于根节点。
倘若我们要查找p,q节点的公共祖先。其中根节点的值等于p节点的值或者等于q节点的值,根就是我们要查找的公共祖先。
例如:p为5,q为2;当我们查找到2的时候,就可以停止;由于我们从树根节点遍历下来,且p,q节点均存在于树中,我们就可以认为p一定存在于q的子树中。
当根节点的值大于p和q的值时,我们就可以只遍历左子树, 同样,当根节点的值小于p和q的值时,我们就可以只遍历右子树;
倘若一个比根节点的值大,一个比根节点的值要小,则肯定一个在左子树,一个在右子树,当前根节点就是我们要找的最近公共祖先 。
整理思路:
- 当根节点的值大于p和q的值时,我们就可以只遍历左子树
- 当根节点的值小于p和q的值时,我们就可以只遍历右子树;
- 否则,当前根节点就是我们要找的最近公共祖先 。
下面看我们的代码实现:
代码实现:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==nullptr)
return nullptr;
if(p->val>root->val&&q->val>root->val)
return lowestCommonAncestor(root->right,p,q);
if(p->val<root->val&&q->val<root->val)
return lowestCommonAncestor(root->left,p,q);
return root;
}
二叉树:三叉链结构:
什么是三叉链结构呢?
就是每个子结点都有一个指针,指向父节点。
所以三叉链结构,解决这个问题十分简单。我们只需要在树中找到对应的节点,在通过指向父节点的指针找到最近公共祖先。其实,这有点在两个链表中寻找公共节点的意思。
由于题库中没有找到这个题,代码就不实现了。
二叉树,普通树
题解:
二叉树和普通树都没有什么特点,所以我们只需要遍历树中的每个节点,倘若我们要查找p,q节点的公共祖先。其中根节点的值等于p节点的值或者等于q节点的值,根就是我们要查找的公共祖先。倘若一个比根节点的值大,一个比根节点的值要小,则肯定一个在左子树,一个在右子树,当前根节点就是我们要找的最近公共祖先 。
代码实现:
//函数功能:在以root为根节点的树中查找是否存在node节点
bool findnode(TreeNode* root,TreeNode* node)
{
if(root==nullptr)
return false;
if(root->val==node->val)
return true;
return findnode(root->left,node)||findnode(root->right,node);
}
//寻找公共祖先
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
//其中根节点的值等于p节点的值或者等于q节点的值,根就是我们要查找的公共祖先
if(root==p||root==q)
return root;
bool pleft=findnode(root->left,p);
bool pright=findnode(root->right,p);
bool qleft=findnode(root->left,q);
bool qright=findnode(root->right,q);
//一个在左子树,一个在右子树,当前根节点就是我们要找的最近公共祖先 。
if((pleft&&qright)||(pright&&qleft))
return root;
//全在左子树,递归到左子树
if(pleft&&qleft)
return lowestCommonAncestor(root->left,p,q);
//全在右子树,递归到右子树
if(pright&&qright)
return lowestCommonAncestor(root->right,p,q);
return nullptr;
}
到这里,我们本文所讲的寻找公共祖先篇已经结束了。
但是在最后一普通树、二叉树一篇中,我们可以看一下时间复杂度是多少呢? O(N)?其实我们遍历一棵树,时间复杂度也就是O(N);但是我们做了许多重复的工作?
我们再看一下这张图:
比如我们要查找的节点为7 和4;我们首先判断以3为根节点的树,遍历完左子树和右子树,发现全在左子树;在判断以5为根节点的树,遍历完左子树和右子树,发现全在右子树。(其实到这里我们已经发现了,6 2 7 4 我们已经遍历了两遍) 直到我们找到7和4.
由于做了重复的工作 第一个N第二次N/2。。。。。。我们可以认为是N^2了。
那可以改善吗?
我们可以借鉴三叉链方法:空间换时间,用容器将路径保存,再寻找最近的公共节点。
下面给出代码实现:
效率有所提高的代码实现:
//需要特别注意的一点是需要一个保留路径的栈,还需要一个栈,找到节点后,将信息保存;因为本函数使用
递归用一个栈无法保存信息。
void findnode(TreeNode* root,TreeNode* node,stack<TreeNode*>& tmp,stack<TreeNode*>& s)
{
if(root==nullptr)
return ;
else
{
tmp.push(root);
if(root->val==node->val)
{
s=tmp;
}
findnode(root->left,node,tmp,s);
findnode(root->right,node,tmp,s);
tmp.pop();
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==p||root==q)
return root;
stack<TreeNode*> tmp;
stack<TreeNode* > pstack;
stack<TreeNode*> qstack;
findnode(root,p,tmp,pstack);
findnode(root,q,tmp,qstack);
if(pstack.size()>qstack.size())
{
while(pstack.size()!=qstack.size())
{
pstack.pop();
}
}
if(pstack.size()<qstack.size())
{
while(pstack.size()!=qstack.size())
{
qstack.pop();
}
}
while(!pstack.empty()&&!qstack.empty()&&pstack.top()->val!=qstack.top()->val)
{
pstack.pop();
qstack.pop();
}
if(pstack.empty()||qstack.empty())
return nullptr;
return pstack.top();
}
本篇博文到这里就结束了,谢谢大家的观看!
你们的 【三连】 是给Qyuan最大的肯定!
↓ ↓ ↓
注:如果本篇博客有任何错误和建议,欢迎伙伴们留言,你快说句话啊!
如果需要练习的小伙伴,可以打开下方链接:
https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/
https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/