1、二叉搜索樹第k大個結點(面試54)
- 題目描述
給定一顆二叉樹,找出第K大的節點,這個第K大的節點是從小到大的第K個節點。例如:如下圖中,按節點大小排序,第三大節點的值爲9。
- 思路
二叉搜索樹的中序遍歷就是排序的,所以用中序遍歷,每一次中間的時候判斷是否等於k即可。
- 代碼實現
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
};
BinaryTreeNode *KthNodeCore(BinaryTreeNode* root, unsigned int k){
BinaryTreeNode *target= nullptr;
if(root->m_pLeft!= nullptr){
target=KthNodeCore(root->m_pLeft,k);
}
if(target== nullptr){
if(k==1){
target==root;
}
--k;
}
if(target->m_pRight!= nullptr && target== nullptr){
target=KthNodeCore(root->m_pRight,k);
}
return target;
}
BinaryTreeNode *KthNode(BinaryTreeNode* root, unsigned int k){
if(root== nullptr || k==0){
return nullptr;
}
return KthNodeCore(root,k);
}
2.0、 從上往下打印二叉樹(面試32)
- 題目描述
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。
- 思路
實際考察樹的層次遍歷(廣度優先遍歷)算法,從上往下遍歷二叉樹,本質上就是廣度優先遍歷二叉樹。
10
/ \
6 14
/\ /\
4 8 12 16
用按層遍歷二叉樹的方法,(1)按層遍歷的順序決定首先應該打印根節點,所以從根節點10開始分析。
(2)打印完根節點10,要打印它的左右子節點6,14。所以在遍歷到左右子節點時,先將它們裝入容器中,此時容器內存了6,14
(3) 先打印左節點6,此時6的左右子節點4,8也進入了容器,容器內有3個元素,14,4,8
(4)從容器內取出14並打印,然後將14的左右子節點12,16存入容器中,此時容器中有4個元素,按順序是4,8,12,16
(5)容器中剩餘的元素是葉子結點,無左右節點,按順序從容器中依次取出並打印即可。
綜上分析,這種先進先出的容器,是隊列,所以我們利用隊列做臨時存儲器來完成打印。操作步驟圖示:
原文鏈接:https://blog.csdn.net/orangefly0214/article/details/83743664
- 代碼實現
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
};
void PrintFromTopToBottom(BinaryTreeNode *pTreeRoot)
{
if(pTreeRoot== nullptr){
return ;
}
deque<BinaryTreeNode*> dequeTreeNode;
dequeTreeNode.push_back(pTreeRoot);
while(!dequeTreeNode.empty()){
BinaryTreeNode *pNode=dequeTreeNode.front();
dequeTreeNode.pop_front();
cout<<pNode->m_nValue<<" ";
if(pNode->m_pLeft){
dequeTreeNode.push_back(pNode->m_pLeft);
}
if(pNode->m_pRight){
dequeTreeNode.push_back(pNode->m_pRight);
}
}
cout<<endl;
}
2.1、二叉樹打印成多行(面試32)
- 題目描述
按照之字形順序打印二叉樹。
10
/ \
6 14
/\ /\
4 8 12 16
10
14 6
4 8 12 16
- 思路
用一個隊列保存將要打印的節點。這裏我們需要兩個變量:一個變量表示當前層中還沒打印的節點個數;下一個變量表示下一層中節點的數目。
- 代碼實現
void Print(BinaryTreeNode *pRoot)
{
if(pRoot== nullptr){
return ;
}
deque<BinaryTreeNode*> nodes;
nodes.push_back(pRoot);
int nextLevel=0;
int toBePrinted=1;
while(!nodes.empty()){
BinaryTreeNode *pNode=nodes.front();
nodes.pop_front();
--toBePrinted;
cout<<pNode->m_nValue<<" ";
if(pNode->m_pLeft){
nodes.push_back(pNode->m_pLeft);
++nextLevel;
}
if(pNode->m_pRight){
nodes.push_back(pNode->m_pRight);
++nextLevel;
}
if(toBePrinted==0){
cout<<endl;
toBePrinted=nextLevel;
nextLevel=0;
}
}
}
2.2、按之字形順序打印二叉樹(面試32)
- 題目描述
按照之字形順序打印二叉樹。
10
/ \
6 14
/\ /\
4 8 12 16
10
6 146
4 8 12 16
- 思路
按之字形打印二叉樹需要兩個棧,我們在打印某個棧時,把下一個節點保存到相應的棧中。如果保存的爲奇數層,先保存左節點,在保存右子節點;如果保存的是偶數層,先保存右子節點,在保存左子節點。
- 代碼實現
void Print1(BinaryTreeNode *pRoot)
{
if(pRoot== nullptr){
return ;
}
stack<BinaryTreeNode*> levels[2];
int current=0;
int next=1;
levels[current].push(pRoot);
while(!levels[0].empty() ||!levels[1].empty()){
BinaryTreeNode *pNode=levels[current].top();
levels[current].pop();
cout<<pNode->m_nValue<<" ";
if(current==0){
if(pNode->m_pLeft!= nullptr){
levels[next].push(pNode->m_pLeft);
}
if(pNode->m_pRight!= nullptr){
levels[next].push(pNode->m_pRight);
}
}else{
if(pNode->m_pRight!= nullptr){
levels[next].push(pNode->m_pRight);
}
if(pNode->m_pLeft!= nullptr){
levels[next].push(pNode->m_pLeft);
}
}
if(levels[current].empty()){
cout<<endl;
current=1-current;
next=1-next;
}
}
}
3、數據流中位數(面試41)
- 題目描述
獲取數據流中的中位數
- 思路
用一個最大堆實現左邊的數據容器,最小堆實現右邊的容器。往堆中插入數據的時間爲O(logn),獲得中位數的時間爲O(1)。
具體實現如下:
首先,要保證數據平均分配到兩個堆中,所以兩個堆的數目只差不超過1。爲了實現平橫,可以在數目是偶數時,把新數據插入到最小堆,否則插入最大堆;
其次,把數據插入最大堆,接着把最大堆的最大數插入最小堆。由於最終插入最小堆的數字是最大堆的最最大數子,這樣就保證最大堆的數字都大於最下堆的數字。其他情況類似。
- 代碼實現
template <typename T>
class DynamicArray{
public:
void Insert(T num){
if(((min.size()+max.size())&1)==0){
if(max.size()>0 && num<max[0]){
max.push_back(num);
push_heap(max.begin(),max.end(),less<T>());
num=max[0];
pop_heap(max.begin(),max.end());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(),max.end(),greater<T>());
}else{
if(min.size()>0 && min[0]<num){
min.push_back(num);
push_heap(min.begin(),min.end(),greater<T>());
num=min[0];
pop_heap(min.begin(),min.end(),greater<T>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(),max.end(),less<T>());
}
}
T GetMedian(){
int size=min.size()+max.size();
if(size==0){
cout<<("No numbers are available")<<endl;
exit(0);
}
T median=0;
if(size&1==1){
median=min[0];
}else{
median=(min[0]+max[0])/2;
}
return median;
}
private:
vector<T> min;
vector<T> max;
};
4、二叉樹中和爲某一值的路徑(面試34)
- 題目描述
輸入一個二叉樹,打印二叉樹中節點值的和爲輸入整數的所有路徑。
- 思路
對於樹的遍歷一般就是深度遍歷和廣度遍歷下四種中的一種,從根節點往下找到葉節點形成一條路徑,若是和爲給定的值這個路徑便是需要找的路徑,從根節點到葉節點邊訪問邊相加判斷可以採用先序遍歷。
(1)若根節點的值大於給定的值或者根節點爲空,則清空路徑;
(2)若根節點的值等於給定的值,需要判斷當前節點是否爲葉子節點,若爲葉子節點,則是需要找的一條路徑,若不是,將保存的節點全部清空。
(3)若根節點的和小於給定的值,則分別去訪問其左右子樹。
- 代碼實現
void FindPath(BinaryTreeNode *pRoot,int exceptedSum,
vector<int> &path,int currentSum){
currentSum+=pRoot->m_nValue;
path.push_back(pRoot->m_nValue);
bool isLeaf=pRoot->m_pRight== nullptr && pRoot->m_pLeft== nullptr;
if(currentSum==exceptedSum && isLeaf){
for(auto it=path.begin();it!=path.end();++it){
cout<<*it<<" ";
}
cout<<endl;
}
if(pRoot->m_pLeft!= nullptr){
FindPath(pRoot->m_pLeft,exceptedSum,path,currentSum);
}
if(pRoot->m_pRight!= nullptr){
FindPath(pRoot->m_pRight,exceptedSum,path,currentSum);
}
path.pop_back();
}
void FindPath(BinaryTreeNode *pRoot,int exceptedSum){
if(pRoot== nullptr){
return ;
}
vector<int> path;
int currentSum=0;
FindPath(pRoot,exceptedSum,path,currentSum);
}
5、重建二叉樹(面試7)
- 題目描述
輸入一個搜索二叉樹的前序和中序遍歷,請重建該二叉樹。
- 思路
若前序或者中序序列爲空或者長度不等則返回空;創建根節點,前序序列的一個結點爲根節點,在中序序列中找到根節點位置,可以分別得到左、右子樹的前序和中序序列,然後遞歸重建左、右子樹;使用輔助空間,保存被分割的前序和中序序列。
- 代碼實現
BinaryTreeNode* ConstructCore(int *startPreOrder,int *endPreOrder,
int *startInOrder,int *endInOrder)
{
int rootValue=startPreOrder[0];
BinaryTreeNode* root=new BinaryTreeNode();
root->m_nValue=rootValue;
root->m_pLeft=root->m_pRight= nullptr;
if(startPreOrder==endPreOrder){
if(startInOrder==endInOrder && *startPreOrder==*startInOrder){
return root;
}else{
throw std::exception();
}
}
int *rootInOrder=startInOrder;
while(rootInOrder<=endInOrder && *rootInOrder!=rootValue){
++rootInOrder;
}
if(rootInOrder==endInOrder && *rootInOrder!=rootValue){
throw std::exception();
}
int leftLength=rootInOrder-startInOrder;
int *leftPreoderEnd=startPreOrder+leftLength;
if(leftLength>0){
root->m_pLeft=ConstructCore(startPreOrder+1,leftPreoderEnd,
startInOrder,rootInOrder-1);
}
if(leftLength<endPreOrder-startPreOrder){
root->m_pRight=ConstructCore(leftPreoderEnd+1,endPreOrder,
rootInOrder+1,endInOrder);
}
return root;
}
BinaryTreeNode* Construct(int *preOrder,int *inOrder,int length)
{
if(preOrder== nullptr || inOrder== nullptr || length<=0){
return nullptr;
}
return ConstructCore(preOrder,preOrder+length-1,
inOrder,inOrder+length-1);
}
6、樹的子結構(面試26)
- 題目描述
輸入兩顆二叉樹A和B,判斷B是否爲A的子結構。
- 思路
要查找樹A中是否存在和樹B結構一樣的子樹,思路是第一步:先在樹A中查找與根結點的值一樣的結點,這實際就是樹的先序遍歷,當樹A和樹B爲空時,定義相應的輸出。如果樹A某一結點的值和樹B頭結點的值相同,則調用findnext,做第二步判斷。第二步是判斷樹A中以R爲根結點的子樹是不是和樹B具有相同的結構,使用遞歸的方法考慮:如果結點R和樹B的根結點不相同則返回false,如果相同,則遞歸判斷它們的左右結點的值是不是相同。遞歸終止條件是我們到達樹A或者樹B的葉結點。
- 代碼實現
bool Equal(double num1,double num2)
{
if (num1-num2>-0.0000001 && num1-num2<0.0000001){
return true;
}else{
return false;
}
}
bool DoesTreeHaveTree(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
if(pRoot2== nullptr){
return true;
}
if(pRoot1== nullptr){
return false;
}
if (!Equal(pRoot1->m_dbValue,pRoot1->m_dbValue)){
return false;
}
return DoesTreeHaveTree(pRoot1->m_pLeft,pRoot2->m_pLeft)&&
DoesTreeHaveTree(pRoot1->m_pRight,pRoot2->m_pRight);
}
bool HasSubtree(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
bool result=false;
if(pRoot1!= nullptr && pRoot2!= nullptr){
if(Equal(pRoot1->m_dbValue,pRoot2->m_dbValue)){
}
if(!result){
result=HasSubtree(pRoot1->m_pLeft,pRoot2);
}
if(!result){
result=HasSubtree(pRoot1->m_pRight,pRoot2);
}
}
return result;
}
7、二叉樹的鏡像(面試27)
- 題目描述
輸入一個二叉樹,輸出其鏡像。
- 思路
先前序遍歷這顆樹的每個節點,如果遍歷到的節點有子節點,就交換它的兩個子節點。當交換玩所有非葉節點的左、右子節點,就得到了樹的鏡像。
- 代碼實現
void MirrorRecursively(BinaryTreeNode *pNode)
{
if(pNode== nullptr){
return ;
}
BinaryTreeNode *pTmp= pNode->m_pLeft;
pNode->m_pLeft=pNode->m_pRight;
pNode->m_pRight=pTmp;
if(pNode->m_pLeft){
MirrorRecursively(pNode->m_pLeft);
}
if(pNode->m_pRight){
MirrorRecursively(pNode->m_pRight);
}
}
8、二叉搜素樹的後序遍歷序列(面試33)
- 題目描述
輸入一個二叉樹,判斷是否爲某二叉搜索樹後序遍歷的結果。
- 思路
在後序遍歷得到的序列中,最後一個數字是樹的根結點的值。數組中前面的數字可以分兩部分:第一部分是左子樹節點的值,它們都比根結點的值小;第二部分是右子樹節點的值,它們都比根結點的值大;根據根結點的值劃分開左子樹節點和右子樹節點,用同樣的方法分別處理左子樹和右子樹。
- 代碼實現
bool VerifySquenceOfBST(int sequence[],int length)
{
if(sequence== nullptr||length<=0){
return false;
}
int root=sequence[length-1];
//在二叉樹中左子樹節點的值均小於根節點
int i=0;
for(;i<length-1;++i){
if(sequence[i]>root){
break;
}
}
//二叉樹中右子樹均大於根節點
int j=i;
for(;j<length-1;++j){
if(sequence[j]<root){
return false;
}
}
//判斷左子樹是否爲二叉搜索樹
bool left=true;
if(i>0){
left=VerifySquenceOfBST(sequence,i);
}
//判斷右子樹是否爲二叉搜索樹
bool right=true;
if(j<length-1){
right=VerifySquenceOfBST(sequence+i,length-i-1);
}
return left&&right;
}
9、二叉搜索樹與雙向鏈表(面試36)
- 題目描述
輸入一個二叉搜索樹,將其轉化爲排序的雙向鏈表。要求不能創建節點,只能在書中的節點中進行調整。
- 思路
1.根據二叉搜索樹和題目要求的特點(二叉搜索樹左<根<右,題目要求要轉換爲排序的雙向鏈表)使用中序遍歷算法遍歷樹的每個節點(中序遍歷的特點是按照從小到大的順序遍歷二叉樹每個節點)
2.聲明一個永遠指向鏈表尾結點的指針pLastNodeInList 和 一個在每次新的函數中都指向當前待插入鏈表節點的當前節點指針pCur,主函數調用排序函數
3.遞歸調用排序函數直至找到最左子節點
4.將當前節點插入鏈表,並更新尾結點指針pLastNodeInList
5.遞歸處理右子樹
- 代碼實現
void ConvertNode(BinaryTreeNode *pNode,BinaryTreeNode **pLastNodeInList){
if(pNode== nullptr){
return ;
}
BinaryTreeNode *pCurrent=pNode;
if(pCurrent->m_pLeft!= nullptr){
ConvertNode(pCurrent->m_pLeft,pLastNodeInList);
}
pCurrent->m_pLeft=*pLastNodeInList;
if(*pLastNodeInList!= nullptr){
(*pLastNodeInList)->m_pRight=pCurrent;
*pLastNodeInList=pCurrent;
if(pCurrent->m_pRight!= nullptr){
ConvertNode(pCurrent->m_pRight,pLastNodeInList);
}
}
}
BinaryTreeNode *Convert(BinaryTreeNode *pRootOfTree){
BinaryTreeNode *pLastNodeInList= nullptr;
ConvertNode(pRootOfTree,&pLastNodeInList);
//pLastNodeInList指向雙向鏈表的爲節點
BinaryTreeNode *pHeadOflist=pLastNodeInList;
while(pHeadOflist!= nullptr &&pHeadOflist->m_pLeft!= nullptr){
pHeadOflist=pHeadOflist->m_pLeft;
}
return pHeadOflist;
}
10、二叉樹的深度(面試55)
- 題目描述
輸入一個二叉樹,求該樹的深度。
- 思路
(1)如果一棵樹只有一個結點,它的深度爲1。
(2)如果根結點只有左子樹而沒有右子樹,那麼樹的深度應該是其左子樹的深度加1;同樣如果根結點只有右子樹而沒有左子樹,那麼樹的深度應該是其右子樹的深度加1。
(3)如果既有右子樹又有左子樹,那該樹的深度就是其左、右子樹深度的較大值再加1。
- 代碼實現
int TreeDepth(BinaryTreeNode* pRoot){
if(pRoot== nullptr){
return 0;
}
int left=TreeDepth(pRoot->m_pLeft);
int right=TreeDepth(pRoot->m_pRight);
return max(left,right)+1;
}
11、平衡二叉樹(面試55)
- 題目描述
輸入一個二叉樹,判斷該樹是否爲平衡二叉樹。
- 思路
後序遍歷二叉樹,先分別統計左右子樹的深度,再判斷樹是否平衡。
- 代碼實現
bool IsBalanced1(BinaryTreeNode* root,int* pDepth){
if(root== nullptr){
*pDepth=0;
return true;
}
int left,right;
if(IsBalanced1(root->m_pLeft,&left)&&IsBalanced1(root->m_pRight,&right)){
int diff=left-right;
if(diff<=1 && diff>=-1){
*pDepth=1+max(left,right);
return true;
}
}
return false;
}
bool IsBalanced(BinaryTreeNode* root){
int depth=0;
return IsBalanced1(root,&depth);
}
12、二叉樹的下一個節點(面試8)
- 題目描述
輸入一個二叉樹和其中一個節點,如何找出中序遍歷的下一個節點。
- 思路
根據中序遍歷的特點:左根右。可分爲三種情況:1. 一個節點有右子樹,那麼它的下一個節點就是它的右子樹的最左子節點。2. 一個節點沒有右子樹且它還是它父節點的左子節點,那麼它的下一個節點就是它的父節點。3. 一個節點沒有右子樹且它還是它父節點的右子節點,那麼它的下一個節點就是含有左子節點的祖先節點。
- 代碼實現
BinaryTreeNode* GetNext(BinaryTreeNode *pNode)
{
if(pNode== nullptr){
return nullptr;
}
BinaryTreeNode* pNext= nullptr;
//如果存在右子樹
if(pNode->m_pRight!= nullptr){
BinaryTreeNode *pRight=pNode->m_pRight;
while(pRight->m_pLeft!= nullptr){
pRight=pRight->m_pLeft;
}
pNext=pRight;
}else if(pNode->m_pParent!= nullptr){//如果不存在右子樹
BinaryTreeNode* pCurrent=pNode;
BinaryTreeNode* pParent=pNode->m_pParent;
while(pParent!= nullptr && pCurrent==pParent->m_pRight){
pCurrent=pParent;
pParent=pParent->m_pParent;
}
pNext=pParent;
}
return pNext;
}
13、對稱的二叉樹(面試28)
- 題目描述
判斷一個二叉樹是否爲對稱二叉樹。
- 思路
自定義後序遍歷:根右左。在考慮葉子節點的left和right的情況下(也就是考慮null), 如果前序遍歷和後序遍歷的結果相同, 那麼二叉樹是對稱的
- 代碼實現
bool isSymmetrical(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2)
{
if(pRoot1==nullptr && pRoot2== nullptr){
return true;
}
if(pRoot1== nullptr || pRoot2== nullptr){
return false;
}
if(pRoot1->m_nValue!=pRoot2->m_nValue){
return false;
}
return isSymmetrical(pRoot1->m_pLeft,pRoot1->m_pRight) &&
isSymmetrical(pRoot2->m_pLeft,pRoot2->m_pRight);
}
bool isSymmetrical(BinaryTreeNode *pRoot)
{
return isSymmetrical(pRoot,pRoot);
}
14、序列化二叉樹(面試37)
- 題目描述
序列化和反序列化二叉樹。
- 思路
如果二叉樹的序列化是從根節點開始的,那麼相應的反序列化在根節點的數值讀出來的時候就可以開始了。因此,我們可以根據前序遍歷的順序來序列化二叉樹,因爲前序遍歷是從根節點開始的。在遍歷二叉樹碰到null指針時,這些null指針序列化爲一個特殊的字符。另外,節點的數值之間要用一個特殊字符隔開(在這裏我用的是‘,’)。
- 代碼實現
void Serialize(BinaryTreeNode *pRoot,ostream &stream){
if(pRoot== nullptr){
stream<<"$,";
return ;
}
stream<<pRoot->m_nValue<<',';
Serialize(pRoot->m_pLeft,stream);
Serialize(pRoot->m_pRight,stream);
}
void Deserialize(BinaryTreeNode **pRoot,istream &stream){
int number;
if(ReadStream(stream,&number)){
*pRoot=new BinaryTreeNode();
(*pRoot)->m_nValue=number;
(*pRoot)->m_pLeft= nullptr;
(*pRoot)->m_pRight= nullptr;
Deserialize(&((*pRoot)->m_pLeft),stream);
Deserialize(&((*pRoot)->m_pRight),stream);
}
}