236. 二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
class Solution {
TreeNode *P = nullptr;
TreeNode *Q = nullptr;
TreeNode *Found = nullptr;
bool traverse(TreeNode* TN) {
bool L = false, R = false, S = false;
if (TN->left)
L = traverse(TN->left);
if (TN ->right)
R = traverse(TN->right);
if (TN == P || TN == Q)
S = true;
if (L && R || L && S || R && S) {
Found = TN;
return false;
}
return L || R || S;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
P = p;
Q = q;
traverse(root);
return Found;
}
};
好像写得有点复杂了。
二刷
class Solution {
TreeNode *Target{};
bool Traverse(TreeNode *Root, TreeNode *P, TreeNode *Q) {
if (!Root)
return false;
bool This = (Root == P) || (Root == Q);
bool Left = Traverse(Root->left, P, Q);
bool Right = Traverse(Root->right, P, Q);
if (This && Left || This && Right || Left && Right) {
Target = Root;
// No need to manually stop propagating, since two diverging paths will
// eventually merge into an only way.
// return false;
return true;
}
return This || Left || Right;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
Traverse(root, p, q);
return Target;
}
};
这个模拟解法确实很直观,而且其实也有剪枝,但是好像有点太复杂了……递归还有一种写法就非常非常优雅:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// The return value can ONLY be one of p, q, and nullptr.
if (!root || root == p || root == q)
return root;
TreeNode *Left = lowestCommonAncestor(root->left, p, q);
TreeNode *Right = lowestCommonAncestor(root->right, p, q);
if (Left && Right)
return root;
return Left ?: Right;
}
};
这整个解法的精华就在开头的 if 上。它的条件语句保证了「但凡返回的不是 nullptr,就只能是 p 和 q 的其中一个」。所以,下面的条件判断也就顺理成章:如果 Left && Right,那就说明都找到了,把 root 向上传递直到冲出递归为止;如果只找到一个,那么 Left ?: Right 会向上传递已经找到的那一个,然后交由上层来继续判断。
注意,第二种递归解法其实用到了一个隐含性质:二叉树从根节点到任意节点的路径是唯一的。所以,除了那唯二两条路径能够使得我们返回非 nullptr 的值以外,剩下的任何路径都会返回 nullptr,这也就意味着完全不用考虑所谓「什么时候该停止传递」的问题。实际上,你会发现我自己的那种写法在进入分支后返回 true 还是 false 都是无所谓的。
存储前驱节点的写法就比较朴素,其实也是模拟的思路,不过注意一下,因为我们要记录的是前驱,所以递归不动点有些许区别。
class Solution {
// Each val is unique. So we can use that as key here.
std::unordered_map<int, TreeNode *> Ancestors;
std::unordered_map<int, bool> Visited;
void FillAncestors(TreeNode *Root) {
Visited[Root->val];
if (Root->left) {
Ancestors[Root->left->val] = Root;
FillAncestors(Root->left);
}
if (Root->right) {
Ancestors[Root->right->val] = Root;
FillAncestors(Root->right);
}
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
FillAncestors(root);
// XXX Possible p == root.
// while (Ancestors[p->val]) {
while (p) {
Visited[p->val] = true;
// In fact the access here also default constructs.
p = Ancestors[p->val];
}
while (!Visited[q->val])
q = Ancestors[q->val];
return q;
}
};
此外也注意一下我写错的那一行。我的本意是通过 operator[] 自动构造一个 nullptr 给全局根节点,但没想到 p 有可能就是根节点。不过事实证明其实完全没必要做这个操作,因为 p = Ancestors[p->val]; 也有自动副作用的。
这里两个哈希表都用 int 当作键是因为题目说明了 val 值是互不相同的。
三刷
class Solution {
TreeNode *Result_;
bool Traversal(TreeNode *Root, TreeNode *P, TreeNode *Q) {
if (!Root)
return false;
// if (Root == P || Root == Q);
// return true;
bool Left = Traversal(Root->left, P, Q);
bool Right = Traversal(Root->right, P, Q);
// Post-order visit.
bool This = (Root == P || Root == Q);
if (This && Left || This && Right || Left && Right)
Result_ = Root;
return This || Left || Right;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
Traversal(root, p, q);
return Result_;
}
};
我靠这个我三刷的时候竟然没写出来?其实写错了之后在改的时候思路有一部分是对的,比如说实际上这题要用的是后序遍历,因为我们需要获得子树的全部信息之后才能进行正确的判定;
但是,同时也应该注意到This在这里的特殊性——This == true并不是这个问题的不动点,因为会有p或q自身为祖先的情况,所以我们真的还是要针对这个情况进行独立判定。最后写出来的逻辑就像上面这样,可能重点还是要记忆一下。
而且这个写法最有趣的还是在于,不用提前终止遍历,因为有This && ...这一长串的谓词,保证了只在一个时刻该谓词是成立的,所以也不用担心被重复赋值。这应该也是拜后序遍历所赐。