目录
题目链接: 236. 二叉树的最近公共祖先 - 力扣(LeetCode)
题目描述:
方法一(递归法):
方法二(非递归后序遍历法)
定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//如果为空值,直接返回
if (root == nullptr) return root;
//只要找到一个节点是p或者q就可以直接返回
if (root == p || root == q) return root;
//如果curleft或curright非空,说明找到了p或者q或者题目所要求的最近公共祖先节点
//也有可能是curleft和curright都非空,这说明什么节点都没找到,返回nullptr
TreeNode* curleft = lowestCommonAncestor(root->left, p, q);
TreeNode* curright = lowestCommonAncestor(root->right, p, q);
//如果左子树和右子树中存在着p和q,说明当前的root就是最近的公共祖先
if (curleft != nullptr && curright != nullptr) return root;
//如果有一边的子树不为空,另一边的子树为空,就返回不为空的子树
else if (curleft != nullptr) return curleft;
else return curright; //最后这一种情况,也包含了两边的子树都为空的情况,那么这一句一样可以返回nullptr
}
};
这题想了好久还是不会,思路是有思路,但是一些细节问题如何解决想了好久都没有办法,最后还是看了题解,看完题解的感觉就是这道题必须对树有一定的理解才行,看上去简单、实际解题时难、最后看完题解又有恍然大悟原来如此的感觉!
该题解的思路为:若节点root是p或者q就可以直接返回root;若root的左子树和右子树中存在着p和q,就说明root是最近的公共祖先,返回root;若root有一个子树为空,就返回另外一个子树;若root的两个子树都为空,就返回nullptr。
以上的写法为何是正确的?以下图为例:
①如何保证找到的公共祖先一定是最近的公共祖先?
例如我们要在上图中寻找6和4的公共祖先,那么在节点5可以知道5的左子树中存在6,右子树中存在4,于是返回公共祖先5;注意,对于上层的根节点来说,p和q以及他们的公共祖先一定是在同一棵树上,即对于图中的3而言,因为6、4、5都在其左子树上,所以其左子树返回的是非空节点,而显然3的右子树必定不存在p或者q,所以其右子树返回的肯定是nullptr。因此依据以下这两条语句,能够保证找到的公共祖先一定是最近的公共祖先:
//如果有一边的子树为空,就直接返回另外一边
if (!curleft) return curright;
if (!curright) return curleft;
②假设p或者q其中一个就是公共节点,那么一旦遇到root==p || root==q就直接返回了,这样一定正确吗?
例如我们要早上图中找2和4的公共祖先,当5递归其右子树时在4之前首先就遇到了2,由于以下这条语句就直接返回了2:
//只要找到一个节点是p或者q就可以直接返回
if (root == p || root == q) return root;
但是与问题①类似,对于上层的根节点来说,p和q以及他们的公共祖先一定是在同一棵树上,由于我们要寻找的2和4都在5的右子树中,所以5的左子树必定返回的是nullptr,而由于右子树返回的是2,所以说明2一定就是2和4的最近公共祖先。
在非递归后序遍历中,当访问到某个节点时,栈中的所有元素均为该结点的祖先,利用这一特性可以解决寻找最近公共祖先的问题。
代码虽然看起来复杂,但逻辑不难,就是在非递归后序遍历的代码基础上,添加两个栈,然后在原先访问结点的地方进行操作。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector sta1, sta2; //sta1作为主栈,sta2作为辅助栈,因为需要遍历栈中的所有值,所以这里的栈不能用stack
int top1 = -1, top2 = -1; //分别为sta1和sta2的栈顶指针
bool flag = false; //flag为true时表示找到了p或q的其中一个,为false时表示都未找到
TreeNode* cur = root, * pre = nullptr;
//非递归后序遍历
while (cur != nullptr || top1 >= 0)
{
while (cur != nullptr)
{
sta1.push_back(cur); //vector未声明大小时,只能用push_back添加元素
top1++;
cur = cur->left;
}
cur = sta1[top1];
if (cur->right != nullptr && pre != cur->right)
{
cur = cur->right;
}
else
{
//第二次找到p或者q时(第二次的代码要放在第一次前面写,不然第一次的代码将flag变为了true之后就直接进入第二次的代码了)
if ((cur == p || cur == q) && flag == true)
{
//这时主栈中的序列和辅助栈中的序列分别是从根节点到达p和q的路径,也是他们的各自的祖先
//因此从后往前匹配,第一个匹配成功的结点就是最近公共祖先
for (int i = top1; i >= 0; i--)
{
for (int j = top2; j >= 0; j--)
{
if (sta1[i] == sta2[j])
{
return sta1[i];
}
}
}
}
//当第一次找到p或者q时
if ((cur == p || cur == q) && flag == false)
{
//将主栈中的元素都复制到辅助栈中
for (int i = 0; i <= top1; i++)
{
sta2.push_back(sta1[i]);
top2++;
}
flag = true;
}
//与普通的数组不同,这里还需要弹出旧元素,不然下次的push_back操作会依然将新元素放到旧元素后面
sta1.pop_back();
top1--;
pre = cur;
cur = nullptr;
}
}
return nullptr;
}
};