日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

程序员面试题精选100题(48)-二叉树两结点的最低共同父结点[数据结构]

發(fā)布時間:2025/3/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 程序员面试题精选100题(48)-二叉树两结点的最低共同父结点[数据结构] 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

題目:二叉樹的結(jié)點定義如下:

struct?TreeNode

{

????int?m_nvalue;

????TreeNode* m_pLeft;

????TreeNode* m_pRight;

};

輸入二叉樹中的兩個結(jié)點,輸出這兩個結(jié)點在數(shù)中最低的共同父結(jié)點。

分析:求數(shù)中兩個結(jié)點的最低共同結(jié)點是面試中經(jīng)常出現(xiàn)的一個問題。這個問題至少有兩個變種。

第一變種是二叉樹是一種特殊的二叉樹:查找二叉樹。也就是樹是排序過的,位于左子樹上的結(jié)點都比父結(jié)點小,而位于右子樹的結(jié)點都比父結(jié)點大。我們只需要從根結(jié)點開始和兩個結(jié)點進(jìn)行比較。如果當(dāng)前結(jié)點的值比兩個結(jié)點都大,則最低的共同父結(jié)點一定在當(dāng)前結(jié)點的左子樹中。如果當(dāng)前結(jié)點的值比兩個結(jié)點都小,則最低的共同父結(jié)點一定在當(dāng)前結(jié)點的右子樹中。

第二個變種是樹不一定是二叉樹,每個結(jié)點都有一個指針指向它的父結(jié)點。于是我們可以從任何一個結(jié)點出發(fā),得到一個到達(dá)樹根結(jié)點的單向鏈表。因此這個問題轉(zhuǎn)換為兩個單向鏈表的第一個公共結(jié)點。我們在本面試題系列的第35題討論了這個問題。

現(xiàn)在我們回到這個問題本身。所謂共同的父結(jié)點,就是兩個結(jié)點都出現(xiàn)在這個結(jié)點的子樹中。因此我們可以定義一函數(shù),來判斷一個結(jié)點的子樹中是不是包含了另外一個結(jié)點。這不是件很難的事,我們可以用遞歸的方法來實現(xiàn):

/ // If the tree with head pHead has a node pNode, return true. // Otherwise return false. / bool HasNode(TreeNode* pHead, TreeNode* pNode) {if(pHead == pNode)return true;bool has = false;if(pHead->m_pLeft != NULL)has = HasNode(pHead->m_pLeft, pNode);if(!has && pHead->m_pRight != NULL)has = HasNode(pHead->m_pRight, pNode);return has; }

我們可以從根結(jié)點開始,判斷以當(dāng)前結(jié)點為根的樹中左右子樹是不是包含我們要找的兩個結(jié)點。如果兩個結(jié)點都出現(xiàn)在它的左子樹中,那最低的共同父結(jié)點也出現(xiàn)在它的左子樹中。如果兩個結(jié)點都出現(xiàn)在它的右子樹中,那最低的共同父結(jié)點也出現(xiàn)在它的右子樹中。如果兩個結(jié)點一個出現(xiàn)在左子樹中,一個出現(xiàn)在右子樹中,那當(dāng)前的結(jié)點就是最低的共同父結(jié)點。基于這個思路,我們可以寫出如下代碼:

/ // Find the last parent of pNode1 and pNode2 in a tree with head pHead / TreeNode* LastCommonParent_1(TreeNode* pHead, TreeNode* pNode1, TreeNode* pNode2) {if(pHead == NULL || pNode1 == NULL || pNode2 == NULL)return NULL;// check whether left child has pNode1 and pNode2bool leftHasNode1 = false;bool leftHasNode2 = false;if(pHead->m_pLeft != NULL){leftHasNode1 = HasNode(pHead->m_pLeft, pNode1);leftHasNode2 = HasNode(pHead->m_pLeft, pNode2);}if(leftHasNode1 && leftHasNode2){if(pHead->m_pLeft == pNode1 || pHead->m_pLeft == pNode2)return pHead;return LastCommonParent_1(pHead->m_pLeft, pNode1, pNode2);}// check whether right child has pNode1 and pNode2bool rightHasNode1 = false;bool rightHasNode2 = false;if(pHead->m_pRight != NULL){if(!leftHasNode1)rightHasNode1 = HasNode(pHead->m_pRight, pNode1);if(!leftHasNode2)rightHasNode2 = HasNode(pHead->m_pRight, pNode2);}if(rightHasNode1 && rightHasNode2){if(pHead->m_pRight == pNode1 || pHead->m_pRight == pNode2)return pHead;return LastCommonParent_1(pHead->m_pRight, pNode1, pNode2);}if((leftHasNode1 && rightHasNode2)|| (leftHasNode2 && rightHasNode1))return pHead;return NULL; }

接著我們來分析一下這個方法的效率。函數(shù)HasNode的本質(zhì)就是遍歷一棵樹,其時間復(fù)雜度是O(n)n是樹中結(jié)點的數(shù)目)。由于我們根結(jié)點開始,要對每個結(jié)點調(diào)用函數(shù)HasNode。因此總的時間復(fù)雜度是O(n2)

我們仔細(xì)分析上述代碼,不難發(fā)現(xiàn)我們判斷以一個結(jié)點為根的樹是否含有某個結(jié)點時,需要遍歷樹的每個結(jié)點。接下來我們判斷左子結(jié)點或者右結(jié)點為根的樹中是否含有要找結(jié)點,仍然需要遍歷。第二次遍歷的操作其實在前面的第一次遍歷都做過了。由于存在重復(fù)的遍歷,本方法在時間效率上肯定不是最好的。

前面我們提過如果結(jié)點中有一個指向父結(jié)點的指針,我們可以把問題轉(zhuǎn)化為求兩個鏈表的共同結(jié)點。現(xiàn)在我們可以想辦法得到這個鏈表。我們在本面試題系列的第4題中分析過如何得到一條中根結(jié)點開始的路徑。我們在這里稍作變化即可:

/ // Get the path form pHead and pNode in a tree with head pHead / bool GetNodePath(TreeNode* pHead, TreeNode* pNode, std::list<TreeNode*>& path) {if(pHead == pNode)return true;path.push_back(pHead);bool found = false;if(pHead->m_pLeft != NULL)found = GetNodePath(pHead->m_pLeft, pNode, path);if(!found && pHead->m_pRight)found = GetNodePath(pHead->m_pRight, pNode, path);if(!found)path.pop_back();return found; }

由于這個路徑是從跟結(jié)點開始的。最低的共同父結(jié)點就是路徑中的最后一個共同結(jié)點:

/ // Get the last common Node in two lists: path1 and path2 / TreeNode* LastCommonNode (const std::list<TreeNode*>& path1,const std::list<TreeNode*>& path2 ) {std::list<TreeNode*>::const_iterator iterator1 = path1.begin();std::list<TreeNode*>::const_iterator iterator2 = path2.begin();TreeNode* pLast = NULL;while(iterator1 != path1.end() && iterator2 != path2.end()){if(*iterator1 == *iterator2)pLast = *iterator1;iterator1++;iterator2++;}return pLast; }

有了前面兩個子函數(shù)之后,求兩個結(jié)點的最低共同父結(jié)點就很容易了。我們先求出從根結(jié)點出發(fā)到兩個結(jié)點的兩條路徑,再求出兩條路徑的最后一個共同結(jié)點。代碼如下:

/ // Find the last parent of pNode1 and pNode2 in a tree with head pHead / TreeNode* LastCommonParent_2(TreeNode* pHead, TreeNode* pNode1, TreeNode* pNode2) {if(pHead == NULL || pNode1 == NULL || pNode2 == NULL)return NULL;std::list<TreeNode*> path1;GetNodePath(pHead, pNode1, path1);std::list<TreeNode*> path2;GetNodePath(pHead, pNode2, path2);return LastCommonNode(path1, path2); }

這種思路的時間復(fù)雜度是O(n),時間效率要比第一種方法好很多。但同時我們也要注意到,這種思路需要兩個鏈表來保存路徑,空間效率比不上第一個方法。

本文已經(jīng)收錄到《劍指Offer——名企面試官精講典型編程題》一書中,有改動,書中的分析講解更加詳細(xì)。歡迎關(guān)注。

博主何海濤對本博客文章享有版權(quán)。網(wǎng)絡(luò)轉(zhuǎn)載請注明出處http://zhedahht.blog.163.com/。整理出版物請和作者聯(lián)系。

總結(jié)

以上是生活随笔為你收集整理的程序员面试题精选100题(48)-二叉树两结点的最低共同父结点[数据结构]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。