【劍指offer題解 整理版】樹
樹
考察點
樹的資料結構特性
- 樹的遍歷規律、遍歷序列特點: 樹的下一個結點
- 樹與遞迴: 對稱二叉樹、映象二叉樹
- 樹與層序遍歷:把二叉樹列印成多行、按之字列印二叉樹、序列化和反序列化二叉樹、從上往下列印二叉樹
- 二叉樹與路徑: 二叉樹中和為某一值的路徑
- 二叉樹與深度: 二叉樹的深度
- 二叉搜尋樹:二叉搜尋樹的第k個結點、二叉搜尋樹與雙向連結串列
樹的下一個結點
從樹的中序遍歷的規則分析一下,樹的下一結點分幾種情況?跟什麼有關?
樹的中序遍歷序列的每一個part都遵循左根右(忽略空子樹)的順序規則。
由此可得出一個大致的結論,next結點要麼與右子樹有關,要麼與根結點有關。
下面我們藉助幾個例項具體分析一下求樹的下一個結點的問題,我們設想幾種情況,先考慮最簡化的情況,一個二層的二叉樹:
- pNode為根節點,那麼當它右子樹不為空時,下一結點為右子樹為右子樹的最左葉結點,當右子樹為空時,next結點為空。
- pNode為根的左結點時,next結點為父結點
- pNode為根的右結點時,next結點為空。
進一步,考慮較為複雜的情況,一個三層的二叉樹:
- pNode為根節點,那麼當它右子樹不為空時,下一結點為右子樹為右子樹的最左葉結點,當右子樹為空時,next結點為空。
- pNode是父結點的左孩子時,next結點為父結點。
- pNode是父結點的右孩子時,next結點為:
- 如pNode有右子樹,那麼next結點為右子樹的最左葉節點 - 如pNode沒有右子樹,那麼next結點分兩種情況: - 父節點的父結點為空,next結點為空(如圖中結點3的情況)。 - 父節點是它父節點的左孩子,next結點為祖父結點(如圖中結點5的情況) - pNode的父親結點是其父節點的右孩子,那麼next結點為空(如途中結點7的情況)。
對於三層的二叉樹的分析已經接近一般性的二叉樹了,我們再自己分析一下就可以推斷出,對於更多層數的二叉樹也是這樣的。
此外,我們可以繼續簡化上述的規律,我們發現處理情況1和情況3的規則幾乎是一樣的,因此可以合併一下:
next結點只與右子樹、父結點、祖父結點有關,規律如下:
1. pNode為根節點或者pNode是其父結點的右孩子時,next結點為:
- **如果pNode有右子樹**,那麼next結點為右字樹的最左葉節點 - **如果pNode沒有右子樹**,那麼next結點分為3種情況: - **pNode的父結點為空或祖父結點為空**,next結點為空 - **pNode的父節點是祖父結點的左孩子**,next結點為祖父結點 - **pNode的父親結點是祖父結點的右孩子**,那麼next結點為空。
pNode為根的左結點時:
- 右子樹為空,next結點為父結點
- 右子樹非空,next結點為右子樹的最左邊結點
接下來我們就可以開始寫程式了。
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
// 邊界情況
if(pNode == NULL) return NULL;
if(pNode->next==NULL || pNode==pNode->next->right)
{
// pNode右子樹為空
if(pNode->right == NULL)
{
return GetNextHelper(pNode);
}
// pNode有右子樹
else
{
// 右子樹的最左葉節點
return GetLeftestLeaf(pNode->right);
}
}
if(pNode == pNode->next->left)
{
// 右子樹為空
if(pNode->right == NULL)
return pNode->next;
//右子樹非空
else
return GetLeftestLeaf(pNode->right);
}
//為了編譯通過
return NULL;
}
TreeLinkNode* GetNextHelper(TreeLinkNode *pRoot)
{
// pNode父節點為空
if(pRoot->next == NULL || pRoot->next->next==NULL) return NULL;
// pNode父節點為組父節點的左孩子
if(pRoot->next == pRoot->next->next->left)
return pRoot->next->next;
// pNode父節點為祖父節點的右孩子
if(pRoot->next == pRoot->next->next->right)
{
return NULL;
}
//為了編譯通過
return NULL;
}
TreeLinkNode* GetLeftestLeaf(TreeLinkNode* pRoot)
{
TreeLinkNode *left = pRoot->left;
if(left==NULL)
return pRoot;
else
return GetLeftestLeaf(left);
}
};
測試:
int main()
{
TreeLinkNode* p8 = new TreeLinkNode(8);
TreeLinkNode* p6 = new TreeLinkNode(6);
TreeLinkNode* p10 = new TreeLinkNode(10);
TreeLinkNode* p5 = new TreeLinkNode(5);
TreeLinkNode* p7 = new TreeLinkNode(7);
TreeLinkNode* p9 = new TreeLinkNode(9);
TreeLinkNode* p11 = new TreeLinkNode(11);
p8->left = p6;
p8->right = p10;
p6->left = p5;
p6->right = p7;
p6->next = p8;
p10->left = p9;
p10->right = p11;
p10->next = p8;
p5->next = p6;
p7->next = p6;
p9->next = p10;
p11->next = p10;
Solution s;
TreeLinkNode* next = s.GetNext(p6);
// cout<<next<<endl;
if(next)
{
std::cout<<next->val<<std::endl;
}
return 0;
}
另一種思路:
上面我們從父節點與祖父結點的關係分類分析,還可以從有無右子樹進行分析,ac程式碼如下,可以對比分析一下:
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == NULL) return NULL;
if(pNode->right == NULL)
//
{
return GetF(pNode);
}
else
//
return GetLeftestLeaf(pNode->right);
}
TreeLinkNode* GetF(TreeLinkNode *pRoot)
{
if(pRoot->next == NULL) return NULL;
if(pRoot == pRoot->next->left)
return pRoot->next;
if(pRoot == pRoot->next->right)
{
if(pRoot->next->next == NULL || pRoot->next == pRoot->next->next->right)
return NULL;
else
return pRoot->next->next;
}
return NULL;
}
TreeLinkNode* GetLeftestLeaf(TreeLinkNode* pRoot)
{
TreeLinkNode *left = pRoot->left;
if(left==NULL)
return pRoot;
else
return GetLeftestLeaf(left);
}
};
對稱二叉樹
如何判斷一顆二叉樹是否是對稱二叉樹?對稱二叉樹符合什麼規則,如何對二叉樹進行遞迴遍歷以便判定對稱二叉樹?
我們先大概設想一下:
一棵樹是對稱二叉樹的話,樹中除了根結點外的每個對稱位置的結點同時為空或者值相同。
由此可得出一個大概的結論,樹的對稱位置相同的樹是對稱二叉樹。
下面藉助幾個具體的例項分析一下判定對稱二叉樹的問題,我們設想幾種情況,先考慮最簡化的情況,一個空樹和只有一個根結點的樹:
它一定是對稱二叉樹。
進一步,考慮稍微複雜的問題,一棵兩層的二叉樹:
- 左右結點都空時(就是一層的二叉樹),對稱
- 左空右不空或左不空右空,不對稱
- 左右都不空但值不相等,不對稱
- 左右都不空且值相等,對稱
1234規則簡而言之:
一棵兩層的二叉樹是否對稱,取決於左?=右
再進一步,考慮更加複雜的問題,一顆三層的二叉樹:
一顆三層的二叉樹是否對稱,取決於
左?=右
左左?=右右
左右?=右左
對於三層的二叉樹的分析已經接近一般性的二叉樹了,我們再自己分析一下就可以推斷出,對於更多層數的二叉樹也有類似的規則,比如一如下的4層二叉樹:
前三層的判定如上所述,第四層是否對稱取決於左左左?=右右右,左左右?=右右左
我們歸納出對稱二叉樹的一般規則;
1. 從根節點出發,判斷其左孩子”?=”右孩子,如果相等,前兩層是對稱的
2. 如果前兩層對稱,那麼我們繼續比較下一層,當前結點為根的左孩子和根的右孩子,比較:
-根的左孩子的左孩子”?=”根的右孩子的右孩子
-根的左孩子的右孩子”?=”根的右孩子的左孩子
如果都相等,那麼說明該層對稱,遞迴判斷下一層(將[左左,右右]作為新的結點重新執行第2布,同理將[左右,右左]作為當前引數進入第2步,兩個結果相與就是該層判斷對稱的結果),如果兩個中有一個不等,一定不對稱,結束遞迴返回結果。
簡化一下上面的規則:
我們看到1、2的邏輯中,第2層的邏輯和第3層及以後一樣,可以合併為:
從根結點出發,當前開始狀態為判斷根的左孩子和右孩子,比較:
- 左左?=右右
- 左右?=右左
如果都相等,那麼說明該層對稱,遞迴判斷下一層(將[左左,右右]作為新的結點重新執行第2布,同理將[左右,右左]作為當前引數進入第2步,兩個結果相與就是該層判斷對稱的結果),如果兩個中有一個不等,一定不對稱,結束遞迴返回結果。
接下來我們就可以開始寫程式了。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
if(pRoot == NULL) return true;
//if(pRoot->left==NULL && pRoot->right==NULL) return true;
//if(pRoot->left==NULL && pRoot->right!=NULL) return false;
//if(pRoot->right==NULL && pRoot->left!=NULL) return false;
//if(pRoot->left->val != pRoot->right->val) return false;
return helper(pRoot->left, pRoot->right);
}
bool helper(TreeNode *left, TreeNode* right)
{
if(left==NULL && right==NULL) return true;
if(left==NULL && right!=NULL) return false;
if(right==NULL && left != NULL) return false;
if(left->val != right->val) return false;
return helper(left->left, right->right)&& helper(left->right,right->left);
}
};
映象二叉樹
如何將一顆二叉樹映象化?採用什麼遍歷順序?才遍歷過程中如何操作。
我們先大致分析一下:
映象二叉樹,實際上是一個將原二叉樹每一個結點的左右孩子的值交換形成的二叉樹。進行交換操作的結點順序可以變,只要保證每個結點都要交換且交換一次即可。
最直觀的交換順序就是前序遍歷(其實也可以使用後續遍歷),在遍歷過程中交換左右孩子的指向即可。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL) return;
TreeNode *tmp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = tmp;
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
為什麼不可以中序遍歷?
想象一個三層二叉樹,如果使用中序遍歷並在遍歷時交換結點的左右孩子的值,那麼根節點的左子樹映象後,根節點交換左右孩子,左子樹變為新的右子樹,接下來映象化右子樹其實是將左自述又映象化了一遍。也就是說左右子樹交換了位置,左子樹被映象了2次,右子樹被映象化0次。不符合我們的設想。因此中序遍歷不行,可以使用前序或者後序遍歷。
也可以非遞迴遍歷這個二叉樹,並交換每個結點的左右孩子,AC程式碼如下,可以對比分析一下:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
stack<TreeNode*> st;
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL)
return;
st.push(pRoot);
while(st.size()>0)
{
TreeNode* top = st.top();
st.pop();
if(top->left)
st.push(top->left);
if(top->right)
st.push(top->right);
TreeNode* temp = top->left;
top->left = top->right;
top->right = temp;
}
}
};
把二叉樹列印成多行
把二叉樹列印成多行,換句話說就是要層序遍歷二叉樹,我們可以藉助二叉樹的什麼特點以及藉助什麼資料結構層序遍歷二叉樹呢?
我們先大致分析一下:
“二”叉樹,具備本層的每一個結點都對應下一層的兩個結點(左孩子和右孩子,孩子可能為空),也就是說,已知本層的結點,就可找到下一層的結點。
下面我們藉助例項具體分析一下:
因為問題比較簡單,我們直接分析一個具有一般化特點的三層樹形結構:
1. 列印第1層,也就是根節點的值
2. 從左到右列印第2層,也就是根的左孩子和右孩子的值
3. 從左到右列印第3層,我們需要找到第2層的結點,分別列印其每個結點的左孩子和右孩子的值。
我們發現,123步驟有一個共有的邏輯,就是列印一層是需要已知上一層結點,然後從上到下一層一層列印,因此,我們藉助一個data來儲存上一層的結點,比如對於第1層,data為空,對於第2層,data為根結點…,那麼data採用什麼資料結構,如何操作才能滿足這種需求呢?
答案是,使用佇列,因為佇列具有先進先出的特點,符合題解的情況。先將根結點push進去,從第二層開始,每次將data中上一層存入的元素都pop出去,每pop一個元素,就將該元素(樹的結點)的左孩子右孩子push到data中(孩子為空,跳過)。這樣就可以讓data在遍歷過程中儲存每一層的結點了,我們也可以藉機列印每層結點的值了。
接下來我們就可以開始寫程式了。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> >result;
vector<vector<int> > Print(TreeNode* pRoot) {
if(pRoot==NULL) return result;
queue<TreeNode*> layer_nodes;
layer_nodes.push(pRoot);
while(!layer_nodes.empty())
{
int len = layer_nodes.size();
vector<int> layer_prints;
for(int i=0;i<len;i++)
{
TreeNode* last_layer_node = layer_nodes.front();
layer_nodes.pop();
layer_prints.push_back(last_layer_node->val);
if(last_layer_node->left)
layer_nodes.push(last_layer_node->left);
if(last_layer_node->right)
layer_nodes.push(last_layer_node->right);
}
result.push_back(layer_prints);
}
return result;
}
};
按之字形順序列印二叉樹
同上,只是從上到下遍歷到偶數層時,將layer_prints反轉一下。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> >result;
vector<vector<int> > Print(TreeNode* pRoot) {
if(pRoot==NULL) return result;
queue<TreeNode*> layer_nodes;
layer_nodes.push(pRoot);
int layer_idx = 0;
while(!layer_nodes.empty())
{
int len = layer_nodes.size();
vector<int> layer_prints;
layer_idx++;
for(int i=0;i<len;i++)
{
TreeNode* last_layer_node = layer_nodes.front();
layer_nodes.pop();
layer_prints.push_back(last_layer_node->val);
if(last_layer_node->left)
layer_nodes.push(last_layer_node->left);
if(last_layer_node->right)
layer_nodes.push(last_layer_node->right);
}
if(layer_idx%2==0)
reverse(layer_prints.begin(), layer_prints.end());
result.push_back(layer_prints);
}
return result;
}
};
從上往下列印二叉樹
同上,只是不用分層列印了,更簡單
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> result;
if(root==NULL) return result;
queue<TreeNode*> layer_nodes;
layer_nodes.push(root);
while(!layer_nodes.empty())
{
int layerLen = layer_nodes.size();
for(int i=0; i<layerLen; i++)
{
TreeNode* top = layer_nodes.front();
result.push_back(top->val);
layer_nodes.pop();
if(top->left)
layer_nodes.push(top->left);
if(top->right)
layer_nodes.push(top->right);
}
}
return result;
}
};
序列化與反序列化二叉樹
序列化與反序列二叉樹是一個互相可逆的過程。序列化是將一棵樹轉化一個載有符合二叉樹所需資訊的字串;反序列化,是根據字串重新生成一棵同樣的二叉樹。
大致分析一下:
在設想序列化二叉樹的方法時,首先需要考慮到從字串恢復一顆二叉樹需要什麼資訊。
- 我們需要所有結點的val值
- 還要記錄二叉樹值所在的位置
我們可以採取某種遍歷順序將結點的val轉為字串依次排放(不要跳過空結點存,它空結點存為為字串’NULL’,這樣是為了保持每個結點都有左右兩個孩子的一致性結構,相當於將二叉樹偽造成滿二叉樹,這可以簡化對結構資訊的儲存)。由於val轉為int後長度時多少char不確定,因此需要為每個每個結點的值之間設定一個分隔符,這裡我們設為’,’。
反序列化時,按照序列化時遍歷的順序重新構造一棵同樣的二叉樹即可。
接下來我們藉助例項,具體分析一下:
- 序列化的過程就是:給定了一棵二叉樹,層序遍歷它,並在遍歷過程中將val轉為字串並加上一個’,’
- 反序列化的過程就是:給定一個字串,從左到右遍歷它,將’,’分割的結點值重新轉為int(如果字串’NULL’說明時空結點),並層序遍歷構造二叉樹。
接下來我們就可以開始寫程式了。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
string serialStr;
TreeNode* deserialRoot;
char* Serialize(TreeNode *root) {
queue<TreeNode*> layer_nodes;
layer_nodes.push(root);
while(!layer_nodes.empty())
{
int len = layer_nodes.size();
string layer_vals_str;
for(int i=0;i<len;i++)
{
TreeNode* last_layer_node = layer_nodes.front();
layer_nodes.pop();
if(last_layer_node)
layer_vals_str += to_string(last_layer_node->val);
else
layer_vals_str += "NULL";
layer_vals_str += ",";
if(last_layer_node)
layer_nodes.push(last_layer_node->left);
if(last_layer_node)
layer_nodes.push(last_layer_node->right);
}
serialStr += layer_vals_str;
}
return const_cast<char*>(serialStr.c_str());
}
TreeNode* Deserialize(char *str) {
int offset = 0;
deserialRoot = getValFromStr(str, offset);
queue<TreeNode*> layer_nodes;
layer_nodes.push(deserialRoot);
while(!layer_nodes.empty())
{
int len = layer_nodes.size();
for(int i=0;i<len;i++)
{
TreeNode* node = layer_nodes.front();
layer_nodes.pop();
if(node == NULL)
continue;
node->left = getValFromStr(str, offset);
node->right = getValFromStr(str, offset);
if(node->left)
layer_nodes.push(node->left);
if(node->right)
layer_nodes.push(node->right);
}
}
return deserialRoot;
}
TreeNode* getValFromStr(string str, int &offset)
{
// cout<<"offset:"<<offset<<endl;
for(int i=offset;i<(int)str.size();i++)
{
if(str[i]==',')
{
string val_str = str.substr(offset, i-offset);
offset = i+1;
// cout<<"val_Str:"<<val_str<<endl;
if(val_str == "NULL")
return NULL;
else
return new TreeNode(stoi(val_str));
}
}
// for compile
return NULL;
}
};
測試:
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <string.h>
using namespace std;
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x), left(NULL), right(NULL)
{}
};
void printTree(TreeNode* root)
{
// vector<int> result;
if(root == NULL) return;
queue<TreeNode*> layer;
layer.push(root);
while(!layer.empty())
{
int len = layer.size();
// cout<<"len:"<<len<<endl;
for(int i=0;i<len;i++)
{
TreeNode *top = layer.front();
cout<<top->val<<" ";
layer.pop();
if(top->left)
layer.push(top->left);
if(top->right)
layer.push(top->right);
}
cout<<endl;
}
// for(int i=0;i<result.size();i++)
}
class Solution
{
...
};
int main()
{
TreeNode *root = NULL;//new TreeNode(4);
TreeNode *p2 = new TreeNode(2);
TreeNode *p3 = new TreeNode(5);
TreeNode *p4 = new TreeNode(1);
TreeNode *p5 = new TreeNode(3);
root->left = p2;
root->right = p3;
p2->left = p4;
p2->right = p5;
Solution s;
cout<<s.Serialize(root)<<endl;
char* serialStr = s.Serialize(root);
printTree(s.Deserialize(serialStr));
return 0;
}
另一種思路:
上面我們使用層序遍歷的順序儲存二叉樹的資訊,還可以使用深度遍歷的方法,ac程式碼如下,可以對比分析一下:
// 所謂序列化指的是遍歷二叉樹為字串;
// 所謂反序列化指的是依據字串重新構造成二叉樹。
// 依據前序遍歷序列來序列化二叉樹,因為前序遍歷序列是從根結點開始的。當在遍歷二叉樹時碰到Null指標時,這些Null指標被序列化為一個特殊的字元“#”。
// 另外,結點之間的數值用逗號隔開。
class Solution
{
public:
char* Serialize(TreeNode* root)
{
string serialStr;
SerializeHelper(root, serialStr);
char* serialChar = new char[serialStr.size()+1];
strcpy(serialChar, serialStr.c_str());
return serialChar;
}
void SerializeHelper(TreeNode* root, string &serialStr)
{
if(root==NULL)
{
serialStr+="#,";
return;
}
serialStr+=to_string(root->val);
serialStr+=",";
SerializeHelper(root->left, serialStr);
SerializeHelper(root->right, serialStr);
}
TreeNode* Deserialize(char* str)
{
string serialStr = str;
vector<string> splits = split(serialStr);
int idx = 0;
return DeserializeHelper(splits, idx);
}
TreeNode* DeserializeHelper(vector<string> eles, int &idx)
{
if(eles[idx]=="#")
return NULL;
TreeNode* root = new TreeNode(atoi(eles[idx].c_str()));
idx++;
root->left = DeserializeHelper(eles, idx);
idx++;
root->right = DeserializeHelper(eles, idx);
return root;
}
vector<string> split(const string& serialStr)
{
cout<<serialStr<<endl;
vector<string> splits;
int lastIdx = 0;
for(int i=0;i<serialStr.size();i++)
{
if(serialStr[i]==',')
{
// cout<<"@"<<serialStr.substr(lastIdx, i-lastIdx)<<endl;
splits.push_back(serialStr.substr(lastIdx, i-lastIdx));
lastIdx = i+1;
}
}
return splits;
}
};
二叉樹中和為某一值的路徑
路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。如何進行遍歷?如何在遍歷過程中記錄路徑的長度?
先大致分析一下:
這種從根節點到葉結點的遍歷路徑和我們遞迴深度遍歷樹的時候的遍歷順序是一樣的,因此遍歷方式就選擇深度遍歷,這裡我們選擇其中的先序遍歷。我們向下一層時,將結點值加到path中,遍歷到葉子結點時比較該路徑長度是否與期待值相等。向上一層時(遞迴函式中與遞迴關聯的code下面的區域,也就是在回溯的部分中),將該結點的值從path中去掉(有點像回溯演算法中的恢復原來的值)。
比較清晰了,可以直接開始寫程式碼了。
class Solution {
public:
vector<vector<int> > result;
vector<int> onePath;
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
Traverse(root,expectNumber);
return result;
}
void Traverse(TreeNode* root, int expectNumber)
{
if(root==NULL) return;
onePath.push_back(root->val);
if(root->left==NULL && root->right ==NULL)
{
int sum = 0;
for(int i=0; i<onePath.size(); i++)
{
sum += onePath[i];
}
if(sum == expectNumber)
result.push_back(onePath);
}
Traverse(root->left, expectNumber);
Traverse(root->right, expectNumber);
onePath.pop_back();
}
};
二叉樹的深度
二叉樹的深度是指值,樹的最長路徑。有兩種解法:
1. 層序遍歷,求層數
2. 深度遍歷,求最長路徑長度
很簡單,直接寫code
1.
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==NULL) return 0;
queue<TreeNode*> m_queue;
m_queue.push(pRoot);
int depth = 0;
while(!m_queue.empty())
{
for(int i=0;i<m_queue.size();i++)
{
TreeNode *top = m_queue.front();
m_queue.pop();
if(top->left)
{
m_queue.push(top->left);
}
if(top->right)
{
m_queue.push(top->right);
}
}
depth++;
}
return depth;
}
};
2.
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int depth = 0;
vector<int> onePath;
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==NULL) return 0;
Traverse(pRoot);
return depth;
}
void Traverse(TreeNode* root)
{
if(root==NULL) return;
onePath.push_back(root->val);
if(root->left==NULL && root->right ==NULL)
{
depth = depth > onePath.size()?depth:onePath.size();
}
Traverse(root->left);
Traverse(root->right);
onePath.pop_back();
}
};
二叉搜尋樹的第k個結點
如何找出二叉搜尋樹的第k小的結點?二叉搜尋樹中結點順序時怎樣的?如何利用順序規律找到第k個結點。
我們大致分析一下:
二叉搜尋樹具有任一結點一定比其左子樹中每個結點大,比其右子樹中每個結點小的特點。且其中序遍歷結果是從小到大排序的。因此,找到第k小的結點,其實就是找中序遍歷的第k個結點。
藉助例項具體分析一下:
由於k值是一個任意給定的值(可能小於等於0,也可能大於搜尋樹中實際的結點個數),因此,我們可以先分析常規情況,然後對這些特殊情況進行處理。
0. 如果k<=0,第k小結點為NULL
1. 遞迴中序遍歷搜尋二叉樹,第k小的結點就是遍歷的k個結點
2. 如果結束遍歷都沒有返回結點,說明k大於搜尋樹中的結點個數,第k小的結點為NULL
接下來就可以寫程式碼了。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
int idx;
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot == NULL || k<=0) return NULL;
idx = 0;
TreeNode* kthNode = NULL;
_MidOrder(pRoot, k, kthNode);
return kthNode;
}
void _MidOrder(TreeNode* pRoot, const int &k, TreeNode* &kthNode)
//注意,要在函式中改變指標的指向時,指標引數要加&,否則時函式中改變的指標的copy變數,且隨著函式結束這個copy被銷燬,傳入的指標並沒有被改變
{
if(pRoot == NULL) return;
_MidOrder(pRoot->left, k, kthNode);
idx++;
if(idx == k)
{
kthNode = pRoot;
return;
}
_MidOrder(pRoot->right, k, kthNode);
}
};
如果MidOrder函式不想要這麼多引數,可以將kthNode變數的宣告提出函式外,宣告為class的全域性變數,就可以直接在成員函式無需傳參直接修改它了。
class Solution {
public:
int idx;
TreeNode* kthNode = NULL;
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot == NULL || k<=0) return NULL;
idx = 0;
// TreeNode* kthNode = NULL;
_MidOrder(pRoot, k, kthNode);
return kthNode;
}
void _MidOrder(TreeNode* pRoot, const int &k)//, TreeNode* &kthNode)
//注意,要在函式中改變指標的指向時,指標引數要加&,否則時函式中改變的指標的copy變數,且隨著函式結束這個copy被銷燬,傳入的指標並沒有被改變
{
if(pRoot == NULL) return;
_MidOrder(pRoot->left, k, kthNode);
idx++;
if(idx == k)
{
kthNode = pRoot;
return;
}
_MidOrder(pRoot->right, k, kthNode);
}
};
二叉搜尋樹與雙向連結串列
如何將二叉搜尋樹轉化為雙向連結串列,二叉搜尋樹的左右指標指向有什麼特定?左右指標在搜尋樹中的指向與雙向連結串列的前驅後繼結點有什麼聯絡嗎?
先大致分析一下:
二叉搜尋樹的中序遍歷序列中的結點順序和雙向連結串列的結點順序一致,如果在遍歷過程中,將左右孩子指標的指向調整為雙向連結串列期望的指向就搜尋樹轉化為雙向連結串列了。
接下來,我們藉助例項具體分析一下,如何調整左右孩子指標的指向:
首先我們固定了遍歷順序為中序遍歷,此外,我們要依次兩兩獲取遍歷序列中的相鄰結點,這藉助變數暫存前1個結點實現(不暫存後1結點的原因時,因為我們還沒遍歷到後1結點,因此無法獲取,而前1結點由於已經遍歷過可以輕易獲得),以便調整left、right指標的目標值。
1. 從2開始(last_node為1),讓last_node->right指向自己
2. 接下來調整3(last_node為2),讓自己->left指向last_node
3. 接下來調整4(last_node為3),讓last_node->right指向自己,自己->left指向last_node
4. 接下來調整5(last_node為4),讓自己->left指向last_node
我們發現1234操作的規則是一樣的,總結一下:
中序遍歷二叉樹,並在中序遍歷的過程中調整指標,使得left指向中序的前一結點,中序的前一結點的right指向該結點
接下來就可以寫程式碼了:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode* lastVisit = NULL;
midOrder_convertToLink(pRootOfTree, lastVisit);
TreeNode* newHead = pRootOfTree;
if(newHead==NULL) return NULL;
while(newHead->left)
{
newHead = newHead->left;
}
return newHead;
}
// 中序遍歷二叉樹,並在中序遍歷的過程中調整指標,使得left指向中序的前一結點,中序的前一結點的right指向該結點
void midOrder_convertToLink(TreeNode *root, TreeNode *&lastVisit)
{
if(root==NULL) return;
midOrder_convertToLink(root->left, lastVisit);
root->left = lastVisit;
if(lastVisit!=NULL) lastVisit->right = root;
lastVisit = root;
midOrder_convertToLink(root->right, lastVisit);
}
};
相關推薦
【劍指offer題解 整理版】樹
樹 考察點 樹的資料結構特性 樹的遍歷規律、遍歷序列特點: 樹的下一個結點 樹與遞迴: 對稱二叉樹、映象二叉樹 樹與層序遍歷:把二叉樹列印成多行、按之字列印二叉樹、序列化和反序列化二叉樹、從上往下列印二叉樹 二叉樹與路徑: 二叉樹中和為某一
【劍指offer第三題】從尾到頭列印連結串列
import java.util.ArrayList; public class Solution { ArrayList list=new ArrayList();//放在遞迴體外面,每次新
【劍指offer第五題】用兩個棧實現佇列
棧的實現是先進後出,佇列是先進先出。思路就是第一個棧的元素按次序出棧,然後第二個棧依次入棧,然後出棧。 import java.util.Stack; public class Solution
【劍指offer第六題】旋轉陣列的最小數
題目描述 把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。 輸入一個非減排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。 例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋
【劍指offer第七題】斐波那契數列
題目描述 大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項為0)。 n<=39 剛開始覺得輸入為一個數,然後找到這個數在斐波那契數列中的位置
【劍指offer第八題】跳臺階
題目描述 一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果) 第一級臺階1種,第二級臺階2種,第四級3種,第四級5種,第五級8種
【劍指offer第十題】矩形覆蓋
題目描述 我們可以用2*1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法? n=0,0種; n=1,1種; n=2,2種; n=
【劍指offer系列之二叉樹】判斷是否為平衡二叉樹
題目: 平衡二叉樹的性質為:要麼是一顆空樹,要麼任何一個節點的左右子樹高度差的絕對值不超過1。給定一棵二叉樹的頭結點head,判斷這棵二叉樹是否為平衡二叉樹。要求時間複雜度為O(N) 思路:
劍指offer題解(二叉樹與雙向連結串列)
題目描述 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。 解題思路 中序遍歷搜尋二叉樹,用pre儲存中序遍歷的前一個節點,cur為當前節點,然後使pre->right=cu
劍指Offer題解【Python版】
題目連結 9. Fizz Buzz 問題 給你一個整數n. 從 1 到 n 按照下面的規則列印每個數: 如果這個數被3整除,列印fizz. 如果這個數被5整除,列印buzz. 如果這個數能
【劍指offer】Java版代碼(完整版)
從尾到頭打印鏈表 .net 字符串 刪除 ron 代碼下載 逆序 鏈表 撲克 原文地址:https://blog.csdn.net/baiye_xing/article/details/78428561 一、引言 《劍指offer》可謂是程序猿面試的神書了,在面試中幫了我很
【劍指Offer】- 重建二叉樹[Java版]
題目描述 輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。 /** * Created by
【劍指offer】1-10題:C++和Java版
劍指offer 面試題1:賦值運算子函式 題目:如下為型別CMyString 的宣告,請為該型別新增賦值符函式。 class CmyString { public: CmyString(char* pData = nullptr); CmyString(c
【劍指offer】Java版程式碼(完整版)
一、引言 《劍指offer》可謂是程式猿面試的神書了,在面試中幫了我很多,大部分面試的演算法題都會遇到原題或者是類似的題。但是書上的程式碼都是C版的,我在這裡整理了一份Java版的程式碼供大家學習參考,這些程式碼我都是在OJ上跑過全AC的,所以正確性你大可放心
劍指offer題解C++【24】二叉樹中和為某一值的路徑
題目描述 輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。 解題思路 按照先序遍歷將結點加入路徑,如果當前結點是葉子結點則判斷當前路徑和是否為目標數,若滿足條件
【劍指offer-第二版】部分題目與解答【C++版本】
20180612 求職在即,《劍指offer》作為大家都推薦的一本應試寶典,確實也有刷一刷的必要。很多題目都比較經典,也涵蓋了大多數的演算法和資料結構。把自己刷題的過程做一個總結,權當是一個筆記。當前還處在未完成狀態,希望自己能堅持做完。 我自己使用
【劍指offer-Java版】25二叉樹中和為某一值的路徑
二叉樹中值和為某一值的路徑:類似圖的深度優先遍歷 對於此類問題一直有點弱,多想幾次就OK了–主要是不熟悉,畢竟程式碼寫出來之後一看就明白,但是自己想的時候還是有點困難 publi
劍指offer題解C++【1】
題目描述 在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。 時間限制:1秒
劍指offer題解C++【22】從上往下列印二叉樹
題目描述 從上往下打印出二叉樹的每個節點,同層節點從左至右列印。 解題思路 二叉樹的層次遍歷,藉助一個佇列實現。 建立一個佇列,其中元素的型別為二叉樹的節點; 按照“根-左-右”的順序依次將二
劍指offer題解C++【25】複雜連結串列的複製
題目描述 輸入一個複雜連結串列(每個節點中有節點值,以及兩個指標,一個指向下一個節點,另一個特殊指標指向任意一個節點),返回結果為複製後複雜連結串列的head。(注意,輸出結果中請不要返回引數中的節點引用,否則判題程式會直接返回空) 解題思路 原始連結