这是LeetCode上的一道题,让我们先来看一看题目:
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.
According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”
_______3______ / ___5__ ___1__ / \ / 6 _2 0 8 / 7 4
For example, the lowest common ancestor (LCA) of nodes 5
and 1
is 3
. Another example is LCA of nodes 5
and 4
is 5
, since a node can be a descendant of itself according to the LCA definition.
简而言之就是已知二叉树,已知二叉树中的两个节点,求这两个节点最近的公共祖先。
同时在代码部分,题目给出了所要求的数据结构,我们可以注意到这一数据结构中,我们只能直接找到这一个节点的左右孩子,但是并不能直接找到他的双亲。
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */
基于这样的数据结构,对于没有学过算法或者学过一些算法不过比较渣的同学(比如我。。。),可能的第一反应就是从根节点开始逐层向下遍历,判断每一个节点是否是所求的公共祖先,然后再找到这些符合条件的节点中最低层的,这种方法显然是十分低效的,因为以每一个节点为根节点的子树都被遍历了很多很多遍。于是为了优化这种算法,我们很自然地想到能否通过调用递归函数来储存中间信息,只对树进行一次遍历解决这个问题。使用这种方法时我们首先需要考虑两个问题:树的遍历方法是先根遍历中根遍历还是什么别的?递归函数的返回值应该储存什么信息?
为了解决这两个问题,我们回归到最近共同祖先这个概念本身。若所求节点是已知节点的最近共同祖先,则说明两点,首先这个节点一定是已知节点的共同祖先,同时这个节点的所有后代(例如左孩子,右孩子)都不是已知节点的共同祖先。因此我们可以总结出该问题的两种不同情况,第一种情况是p,q两个节点分别在所求节点的左子树,右子树中;第二种情况是所求节点本身就是p或q节点。
应对第一种情况,我们发现递归函数的返回值需要能够反映一个节点是否是p或q节点的祖先即可,若一个节点的左右孩子都是p或q节点的祖先,则该节点就是p和q的最近共同祖先。同时我们还需要先对根节点进行特判,判断其是否就是p或q节点,则这时不用再遍历该节点的子树了。经过这些分析,我们决定采取先根遍历的形式,先判断某节点是否是p或q节点,至于递归函数的返回值,如果某节点是p或q的祖先,返回该节点指针,否则返回NULL。
实现这种思路的代码如下:
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(!root) return NULL; if(root == p || root == q) return root; TreeNode* L = lowestCommonAncestor(root->left, p, q); TreeNode* R = lowestCommonAncestor(root->right, p, q); if(L && R) return root; return L ? L : R; } };
下面我们来分析这个代码的正确性。首先,若p,q两个节点分别在所求节点的左子树,右子树中,则该所求节点是整个二叉树中唯一一个左右节点返回值都不是NULL的节点;若所求节点本身即是p或q节点,则在该节点处会返回该节点的指针,而该遍历该节点的所有祖先节点的返回值都是该节点指针。这确保了这种算法的正确性。
最后总结一下,这段代码应用了深度搜索的思想和先根遍历的遍历方法,时间复杂度为O(n),形式上也很有美感。感觉Lowest Common Ancestor of a Binary Tree这道题目本身虽然不复杂,但却很能说明一些问题。