题目描述
描述:设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
解题思路
思路1:最直观的想法是,遍历二叉树,分别记录从根节点到p的路径数组path1和从根节点到q的路径数组path2,然后遍历path1和path2,找到其最后一个相同的节点,其即为p和q的最近公共祖先。注意,遍历从根节点到某一节点的路径使用的是前序遍历,因为路径从根节点开始。其中需要使用一个标记变量flag来标记当前路径是否完成,当当前节点为目标节点时其不需要再继续遍历,故我们需要设置边界条件即当flag为true则直接返回。由于先序遍历是根左右,故先遍历左边,那如果我们的目标在右边,前面有数据压入但其不符合要求,故我们需要使用回溯,即当前节点压入数组后,如果其左右子树遍历均结束,且flag仍未被标记为true,则表明当前节点不在路径中,故应该将当前节点弹出。
vector<TreeNode*> path1;
vector<TreeNode*> path2;
bool flag=false;
void dfs(TreeNode* root,TreeNode* target,vector<TreeNode*> &path)
{
if(root==NULL||flag==true) return;
path.push_back(root);
if(root==target)
{
flag=true; //标记访问结束
return;
}
dfs(root->left,target,path);
dfs(root->right,target,path);
if(!flag) path.pop_back(); //没找到才弹出
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
dfs(root,p,path1);
flag=false;
dfs(root,q,path2);
TreeNode* res=NULL;
for(int i=0,j=0;i<path1.size()&&j<path2.size();i++,j++)
{
if(path1[i]==path2[j])
res=path1[i];
}
return res;
}
思路2:上述方法算是暴力求解,但是题目中要求不得将其他节点存储在另外的数据结构中,故我们需要考虑有无其他方法来实现。我们可以在整棵树上进行遍历并判断!lowestCommonAncestor表示在以root为根节点的二叉树上寻找p和q的最近公共祖先,那么left表示在以root的左子树为根节点的二叉树上寻找p和q的最近公共祖先,right表示在以root的右子树为根节点的二叉树上寻找p和q的最近公共祖先,如果找到p返回p,如果找到q返回q,如果找到空返回空,这时我们就判断left和right,如果left为空则表示左子树没有即返回right,如果right为空则表示右子树没有即返回left,反之left和right均为空则表示左右子树均没有即返回root啦!注意递归逻辑思考过程!
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
if(root==NULL) return NULL;
// 找到p则返回p
if(root==p) return p;
// 找到q则返回q
if(root==q) return q;
//寻找以root左子树为根节点的p和q的公共祖先
TreeNode* left=lowestCommonAncestor(root->left,p,q);
//寻找以root右子树为根节点的p和q的公共祖先
TreeNode* right=lowestCommonAncestor(root->right,p,q);
//左子树未找到返回右子树
if(!left)
return right;
//右子树未找到返回左子树
if(!right)
return left;
return root; //都未找到返回root
}
总结:在想递归的时候,可以先想当前函数是干什么的,然后怎么将其分解为在左右子树上,然后先想第一层,再举个例子就明白了。