面试题50:求二叉树中两个节点的最近公共祖先

xiaoxiao2021-02-28  94

在数据结构中,二叉树是一个比较复杂的数据结构。在面试的时候,很多面试官喜欢通过二叉树来考察一个程序猿的逻辑、代码等基本功。最近正好在看《剑指offer》,并且学习了二叉树的一些相关知识。

在《剑指offer》的第50题,求二叉树中两个节点的最近公共祖先。关于这个题,其实刚看到题目的时候,容易顺着已有的思路忽略题目埋下的坑。首先我们要清楚这棵二叉树是怎样的一棵树,是搜索二叉树?还是只是平常的二叉树?如果是平常的二叉树,它有指向父节点的指针吗?……下来,我们一一分析每一种情况。

1.搜索二叉树

如果当前的树是一棵搜索二叉树,那么题目将会变得简单。搜索二叉树的节点分布是有规律的,如果我们要查找的节点一个在根节点的左边,一个在根节点的右边,那么毫无疑问根节点就是最近的公共祖先。如果不是,我们可以将这种思路递归下去,需要查找最大的节点都小于根节点,那么肯定在左边。右边同理。

理清思路,代码实现就简单了。

Node* FindSameParent(Node* k1, Node* k2) { K min = 0; K max = 0; if ((k1->_key) > (k2->_key)) { min = k2->_key; max = k1->_key; } else { min = k1->_key; max = k2->_key; } Node* cur = _root; while (cur) { if (max >= cur->_key && min <= cur->_key) return cur; if (max < cur->_key) cur = cur->_left; if (min>cur->_key) cur = cur->_right; } return NULL; }

2.普通的树

如果是一棵普通的树,我们需要考虑其特殊情况,如果它有指向父节点的指针,那么这个题就可以转换成我们之前学习的链表。如果没有父节点,我们就需要对整个树进行遍历去找这个节点。

a.有父节点

如果当前这棵树是有父节点,那么我们就可以顺着根节点一直遍历到我们需要查找的节点, 把这个路径保存到链表中,就变成了寻找两个链表的公共节点。

b.无父结点

如果没有父节点,那我们就只能对这个树进行遍历,遍历到一个节点的时候,判断这两个节点其中一个是否存在,如果存在就立即返回。主要是采用递归的思路一次判断。如果一个节点即有第一个需要查找的节点,也有第二个,那么它就是我们所求的节点。因为是递归,所以查找到的节点就是最近的。

这个思路并不难,下来看代码实现。

Node* _FindSameParent(Node* root, Node* k1, Node* k2) { if (root == NULL) return NULL; if (root == k1 || root == k2) return root; Node* _k1 = _FindSameParent(root->_left, k1, k2); Node* _k2 = _FindSameParent(root->_right, k1, k2); if (_k1 && _k2) return root; return _k1 ? _k1 : _k2; }

以上的思路是我参考《剑指offer》一步一步进行分析的,其实如果这个一个普通的树,我们用上面的思路去分析,再用代码实现是比较复杂的。比如第一个转换成求链表的公共节点,我们都知道链表增删查改的时间复杂度为O(n);第二个用递归,如果这棵树的比较大,堆栈的开销又太大,而且递归的次数较多,时间也比较慢。

在我用链表解决第一种情况时,查看STL的函数时。看到栈的时候,发现这两种情况都可以用栈解决,并且栈的查找时间复杂度为O(1)。这样对整个查找的情况即简单化,而且效率也高。

我们现在用两个栈去保存我们遍历过的路径,然后保证两个栈的大小是一样的,然后进行出栈操作,这样也可以找到最近的公共节点。

这里就需要一个函数来得到路径,在查找路径一定要明白:如果在左子树查找成功,那么一定不存在在右子树(这里我们不考虑树中有相同的节点)。如果查找的路径不对,一定要记得最后把该节点pop出,否则栈里的元素就存在问题了。

bool _GetPath(Node* root, Node* k, stack<Node*>& s) { if (root == NULL) return false; if (k == root) { s.push(root); return true; } s.push(root); if(_GetPath(root->_left, k, s)) return true; if(_GetPath(root->_right, k, s)) return true; s.pop(); return false; }

查找的代码实现如下。

Node* FindSameParent(Node* k1, Node* k2) { if (_root == NULL) return NULL; stack<Node*> s1; stack<Node*> s2; _GetPath(_root, k1, s1); _GetPath(_root, k2, s2); while (s1.size() > s2.size()) { s1.pop(); } while (s1.size() < s2.size()) { s2.pop(); } while (s1.top() != s2.top()) { s1.pop(); s2.pop(); } return s1.top(); }

写完这道题再来看题目,这道题本身并不难,难得是我们如何考虑到出现的情况,如何把未知的问题转变成我们学过的知识。在写代码前,一定要先分析可能出现的情况,要跳出自己的惯性思维方式,客观去理解看待问题。

转载请注明原文地址: https://www.6miu.com/read-49079.html

最新回复(0)