这是一道企业面试中,经常会被问到的面试题目。
在网上看到一些此题的实现,其中有两种方法是比较适合编程的。本项目的源代码,请点击这里下载。
方法一:
此方法是根据二叉树的DFS查找并标记祖先,根据递归出栈的原理,找到公共祖先。
其主要代码如下:
1 #include <iostream> 2 #include <list> 3 #include "LinerLCA.h" 4 5 //DFS左右子树,查找pNode是否存在 6 /*@param pRoot 根节点 7 /*@param pNode 需要查找的节点 8 /*@param path pNode所存在的路径 9 */ 10 bool GetNodePath(BinaryTreeNode* pRoot, BinaryTreeNode* pNode, list<BinaryTreeNode*>& path) 11 { 12 if(pRoot == pNode) 13 return true; 14 15 path.push_back(pRoot); 16 17 bool found = false; 18 19 if (pRoot->m_pLeft != NULL) 20 { 21 found = GetNodePath(pRoot->m_pLeft, pNode, path); 22 } 23 24 if (pRoot->m_pRight != NULL && !found) 25 { 26 found = GetNodePath(pRoot->m_pRight, pNode, path); 27 } 28 29 if(!found) 30 path.pop_back(); 31 32 return found; 33 } 34 //遍历path1、path2,找到最后面的公共节点 35 BinaryTreeNode* GetLastCommonNode(list<BinaryTreeNode*>& path1, list<BinaryTreeNode*>& path2) 36 { 37 list<BinaryTreeNode*>::const_iterator iterator1 = path1.begin(); 38 list<BinaryTreeNode*>::const_iterator iterator2 = path2.begin(); 39 40 BinaryTreeNode* pLast = NULL; 41 42 while(iterator1 != path1.end() && iterator2 != path2.end() && *iterator1 == *iterator2) 43 { 44 pLast = *iterator1; 45 iterator1++; 46 iterator2++; 47 } 48 49 return pLast; 50 } 51 //函数入口 52 BinaryTreeNode* GetLastCommonParent(BinaryTreeNode* pRoot, BinaryTreeNode* pNode1, BinaryTreeNode* pNode2) 53 { 54 if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL) 55 return NULL; 56 57 list<BinaryTreeNode*> path1; 58 GetNodePath(pRoot, pNode1, path1); 59 60 list<BinaryTreeNode*> path2; 61 GetNodePath(pRoot, pNode2, path2); 62 63 return GetLastCommonNode(path1, path2); 64 }
方法二:
此方法是在一本书里看到的,其主要思想是,先遍历二叉树,找到从根到两个节点的两条路径,然后再求解出两条路径的第一个公共交点,为最低共同祖先。
主要代码如下:
1 #include "RecursionLCA.h" 2 3 /*递归计算根节点与node1、node2的关系 4 * @param root 要查找节点的根节点 5 * @param node1 节点一 6 * @param node2 节点二 7 * @param restult 共同父节点结果的引用 8 * @param parent 根节点的父节点 9 */ 10 bool LCA(BinaryTreeNode* root, BinaryTreeNode* node1, BinaryTreeNode* node2, BinaryTreeNode*& restult, BinaryTreeNode* parent) 11 { 12 if (!root) 13 { 14 return false; 15 } 16 17 bool falg1 = LCA(root->m_pLeft, node1, node2, restult, root); 18 bool falg2 = LCA(root->m_pRight, node1, node2, restult, root); 19 //在节点的两个子树上 20 if (falg1 && falg2) 21 { 22 //只有确定在两棵子树上时,根节点为共同父节点 23 restult = root; 24 return true; 25 } 26 //其中的一个节点是根节点 27 if (root->m_nValue == node1->m_nValue || root->m_nValue == node2->m_nValue) 28 { 29 if (falg1 || falg2) 30 { 31 restult = parent; 32 } 33 return true; 34 } 35 return falg1 || falg2; 36 } 37 //函数入口 38 BinaryTreeNode* RecurLCA(BinaryTreeNode* root, BinaryTreeNode* node1, BinaryTreeNode* node2) 39 { 40 BinaryTreeNode* restult = NULL; 41 LCA(root, node1, node2, restult, NULL); 42 return restult; 43 }
测试数据如下:
测试结果:
都可通过。
小结:
方法一:比较直观,在查找节点的同时,还做了标记,一气呵成,想到此方法要求分析能力较强。
方法二:思想比较简单,逻辑清晰,但空间复杂度较多。
在时间效率上,两者相差不多,方法一应为只有递归操作,省去了开辟额外空间的开销,效率略高。
读者如有更好的方法,欢迎交流、指正。