题目来源
题目描述
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
}
};
题目解析
树形DP
对于二叉树中的任意一个节点root,以及两个节点p、q,p、q的最低公共祖先分为两种情况。
(1)与root无关,即最低公共祖先不是root
- p、q在左树中某个点汇聚了
- p、q在右树中某个点汇聚了
- p、q在左树和右树中不全
(2)与root有关,即最低公共祖先是root
- 左树中找到p、q中的一个,右树找到另一个
- root是p,在左树或右树中找到q
- root是q,在左树或右树中找到p
也就是对于每一个root,它需要分别从它的左右子树中得到如下信息:
- 找到p了吗?
- 找到q了吗?
- 如果两者都找到了,那么其公共祖先是多少
因此定义出一个Info
当得到信息之后,整合出当前节点的Info:
- 如果curr == p,或者
left->findP == true
,或者right->findP == true
时,则curr->findP = true
- 如果curr == q,或者
left->findQ == true
,或者right->findQ == true
时,curr->findQ = true
- 对于curr->ans:
- 如果
left->ans != null
则curr->ans = left->ans
- 如果
right->ans != null
则curr->ans = right->ans
(left->ans与right->ans不可能同时不为空) - 否则,如果当前发现 findP && findQ,则curr->ans = curr
- 如果
class Solution {
struct Info{
bool findP;
bool findQ;
TreeNode *ans;
Info(bool fP, bool fQ, TreeNode *an){
findP = fP;
findQ = fQ;
ans = an;
}
};
std::shared_ptr<Info> process(TreeNode* root, TreeNode* p, TreeNode* q){
if(root == nullptr){
return std::make_shared<Info>(false, false, nullptr);
}
auto leftInfo = process(root->left, p, q);
auto rightInfo = process(root->right, p, q);
bool findP = (root == p) || leftInfo->findP || rightInfo->findP;
bool findQ = (root == q) || leftInfo->findQ || rightInfo->findQ;
TreeNode* ans = nullptr;
if (leftInfo->ans != nullptr) {
ans = leftInfo->ans;
} else if (rightInfo->ans != nullptr) {
ans = rightInfo->ans;
} else {
if (findP && findQ) {
ans = root;
}
}
return std::make_shared<Info>(findP, findQ, ans);
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr){
return root;
}
return process(root, p, q)->ans;
}
};
层次遍历
- 层次遍历一次:使用一个map将各节点的父节点存储下来:key = 当前节点,value = 当前节点的父节点 。 注意: 如果为根节点,那么它的父节点是null
- 初始化一个栈s1:将p的路径存储下来,比如p=6时,栈为:6 5 3
- 初始化第二个栈s2:将q的路径存储下来,比如q=1时,栈为:1 2
- 初始化一个pre节点,指向null,初始化一个size = Math.min(s1.szie, s2.szie)
- 循环(size)弹出栈,并更新pre,直到第一个不相同的节点,那么pre就是要找到节点
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr){
return root;
}
std::map<TreeNode*, TreeNode*> map;
map[root] = nullptr;
std::queue<TreeNode *> queue;
queue.emplace(root);
while (!queue.empty()){
int size = queue.size();
for (int i = 0; i < size; ++i) {
TreeNode *top = queue.front(); queue.pop();
if(top->left != nullptr){
queue.emplace(top->left);
map[top->left] = top;
}
if(top->right != nullptr){
queue.emplace(top->right);
map[top->right] = top;
}
}
}
// p、q 为不同节点且均存在于给定的二叉树中
std::stack<TreeNode *>stack1;
TreeNode *key = p;
stack1.push(key);
while (map[key] != nullptr){
stack1.push(map[key]);
key = map[key];
}
std::stack<TreeNode *>stack2;
key = q;
stack2.push(key);
while (map[key] != nullptr){
stack2.push(map[key]);
key = map[key];
}
int size = std::min(stack1.size(), stack2.size());
TreeNode *pre = nullptr;
for (int i = 0; i < size; ++i) {
TreeNode *treeNode1 = stack1.top(); stack1.pop();
TreeNode *treeNode2 = stack2.top(); stack2.pop();
if(treeNode1 == treeNode2){
pre = treeNode2;
}else{
break;
}
}
return pre;
}
};
优化:其他我们可以不用遍历全部的树,只需要得到p和q的路径就可以了
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr){
return root;
}
std::map<TreeNode*, TreeNode*> parent ;
parent [root] = nullptr;
std::queue<TreeNode *> queue;
queue.emplace(root);
while (!queue.empty()){
int size = queue.size();
for (int i = 0; i < size; ++i) {
TreeNode *top = queue.front(); queue.pop();
if(top->left != nullptr){
queue.emplace(top->left);
parent[top->left] = top;
}
if(top->right != nullptr){
queue.emplace(top->right);
parent [top->right] = top;
}
}
}
// p、q 为不同节点且均存在于给定的二叉树中
std::set<TreeNode *>ancestors ;
while (p != nullptr){
ancestors.emplace(p);
p = parent[p];
}
while (!ancestors.count(q)){
q = parent[q];
}
return q;
}
};
另外写法:
class Solution {
void fillParentMap(TreeNode* root, std::map<TreeNode *, TreeNode *> &parent){
if(root->left != nullptr){
parent[root->left] = root;
fillParentMap(root->left, parent);
}
if(root->right != nullptr){
parent[root->right] = root;
fillParentMap(root->right, parent);
}
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr){
return root;
}
std::map<TreeNode* , TreeNode* > parentMap;
parentMap[root] = nullptr;
fillParentMap(root, parentMap);
std::set<TreeNode *>p_set;
TreeNode *cur = p;
p_set.emplace(cur);
while (parentMap[cur] != nullptr){
cur = parentMap[cur];
p_set.emplace(cur);
}
cur = q;
while (!p_set.count(cur)) {
cur = parentMap[cur];
}
return cur;
}
};
递归
首先,要想通过递归来实现,就需要先确定临界条件,那么临界条件是什么呢?换句话说,临界条件就是递归中能够直接返回的特殊情况:
- 第一点则是最常见的“判空”,判断根结点是否是空节点,如果是,那么肯定就可以马上返回了,这是一个临界条件;
- 再来考虑题意,在以root为根结点的树中找到p结点和q结点的最近公共祖先,那么特殊情况是什么呢?很显然,特殊情况就是根结点就等于q结点或p结点的情况,想一下,如果根结点为二者之一,那么根结点就必定是最近公共祖先了,这时直接返回root即可。由此看来,这道题就一共有三种特殊情况,root == q 、root == p和root==null,这三种情况均直接返回root即可。
根据临界条件,实际上可以发现这道题已经被简化为查找以root为根结点的树上是否有p结点或者q结点,如果有就返回p结点或q结点,否则返回null。
这样一来其实就很简单了,从左右子树分别进行递归,即查找左右子树上是否有p结点或者q结点,就一共有4种情况:
- 左子树和右子树均找没有p结点或者q结点;(这里特别需要注意,虽然题目上说了p结点和q结点必定都存在,但是递归的时候必须把所有情况都考虑进去,因为题目给的条件是针对于整棵树,而递归会到局部,不一定都满足整体条件)
- 左子树上能找到,但是右子树上找不到,此时就应当直接返回左子树的查找结果;
- 右子树上能找到,但是左子树上找不到,此时就应当直接返回右子树的查找结果;
- 左右子树上均能找到,说明此时的p结点和q结点分居root结点两侧,此时就应当直接返回root结点了。
综上也不难写出结果了:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || root == p || root == q){
return root;
}
TreeNode *left = lowestCommonAncestor(root->left, p, q);
TreeNode *right = lowestCommonAncestor(root->right, p, q);
if (left != nullptr && right != nullptr){
return root;
}
if (left == nullptr){
return right;
}
if (right == nullptr){
return left;
}
return nullptr;
}
};
也可以这样写:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || root == p || root == q){
return root;
}
TreeNode *left = lowestCommonAncestor(root->left, p, q);
if (left != nullptr && left != q && left != p){
return left;
}
TreeNode *right = lowestCommonAncestor(root->right, p, q);
if (left != nullptr && right != nullptr){
return root;
}
if (left == nullptr){
return right;
}
if (right == nullptr){
return left;
}
return nullptr;
}
};
进阶:如果查询两个节点的最近公共祖先的操作十分频繁,想让单条查询的查询时间减少
可以先预处理建立一种记录,一行执行每次查询时就可以完全根据记录查询。
设计:用一张哈希表,对二叉树的每个节点记录其父节点(就是上面层次遍历的写法【待整理】)
举个例子,对于如下二叉树
其哈希表M如下:
key是二叉树的一个节点,value代表其对应的父节点。只用遍历一次二叉树,这张表就可以建立好,以后每次查询就可以根据这张哈希表进行。
如果想要查询节点4和节点8的最近公共祖先,那么:
- 把包括节点4在内的所有节点4的祖先放到另一种哈希表A中,A表示节点4到头结点这条路径上所有节点的集合。为{节点4,节点2,节点1}
- 然后我们使用全局的哈希表,从节点8开始往上逐渐移动到头结点(可能的路径:{节点8,节点7,节点3,节点1})
- 首先是节点8,发现不再A中,继续上移
- 然后是节点7,发现不再A中,继续上移
- 然后是节点3,发现不再A中,继续上移
- 然后是节点1,在A中,那么节点1就是节点1和节点8的最近公共祖先
class Record{
std::map<TreeNode *, TreeNode *> map;
void setMap(TreeNode *head){
if(head == nullptr){
return;
}
if(head->left != nullptr){
map.insert({head->left, head});
}
if(head->right != nullptr){
map.insert({head->right, head});
}
setMap(head->left);
setMap(head->right);
}
public:
explicit Record(TreeNode *head){
if(head != nullptr){
map.insert({head, nullptr});
}
}
TreeNode *query(TreeNode *o1, TreeNode *o2){
std::set<TreeNode *>path;
while (map.count(o1)){
path.insert(o1);
o1 = map[o1];
}
while (!path.count(o2)){
o2 = map[o2];
}
return o2;
}
};