树专题总结

二叉搜索树

二叉搜索树的中序遍历是有序的。

中序遍历的利用

JZ36 二叉搜索树与双向链表

充分利用中序遍历的有序性构造有序的双向链表。

改造原始的中序遍历,避免构造额外的空间。核心的逻辑是:左中右中,中的前驱是左,后继是右子树。所以可以使用一共pre指针指向左节点,然后root的left指向pre,pre的right指向root。pre更新到root,pre设置为全局的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode * pre;
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree==nullptr){
return nullptr;
}
pre=nullptr;
//找到头节点
TreeNode * ret;
ret=pRootOfTree;
while(ret->left!=nullptr){
ret=ret->left;
}
inorder(pRootOfTree);
return ret;
}
void inorder(TreeNode * root){
if(root->left!=nullptr){
inorder(root->left);
}
//中序处理环节
root->left=pre;
if(pre!=nullptr){
pre->right=root;
}
pre=root;
if(root->right!=nullptr){
inorder(root->right);
}
}
};

JZ8 二叉树的下一个结点

描述:

给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针

要求:**空间复杂度 O*(1) **,时间复杂度 O(n)

分析可能存在的三种情况:

  1. 如果给出的结点有右子节点,则最终要返回的下一个结点即右子树的最左下的结点
  2. 如果给出的结点无右子节点,且当前结点是其父节点的左子节点,则返回其父节点
  3. 如果给出的结点无右子节点,且当前结点是其父节点的右子节点,则先要沿着左上方父节点爬树,一直爬到当前结点是其父节点的左子节点为止,返回的就是这个父节点;或者没有满足上述情况的则返回为NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
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==nullptr)
return nullptr;
//情况1
if(pNode->right!=nullptr){
TreeLinkNode* cur=pNode->right;
TreeLinkNode* pre=cur;
while(cur!=nullptr){
pre=cur;
cur=cur->left;
}
return pre;
}
//情况2
if(pNode->next==nullptr)
return nullptr;
if(pNode->next->left==pNode){
return pNode->next;
}
//情况3
TreeLinkNode* cur=pNode;
while(cur->next!=nullptr&&(cur!=cur->next->left)){
cur=cur->next;
}
if(cur==cur->next->left)
return cur->next;
else
return nullptr;
}

};

利用搜索二叉树的有序性

JZ68 二叉搜索树的最近公共祖先

解答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int p, int q) {
// write code here
queue<TreeNode *>myqueue;
myqueue.push(root);
int rettmp;
int minnum=min(p,q);
int maxnum=max(p,q);
TreeNode *cur=root;

while (cur!=nullptr) {
if(maxnum<cur->val)
cur=cur->left;
else if(minnum>cur->val)
cur=cur->right;
else if(cur->val>=minnum&&cur->val<=maxnum){
rettmp=cur->val;
break;
}

}
return rettmp;

}

};

JZ86 在二叉树中找到两个节点的最近公共祖先

递归思路:

分析遍历到任意的一个结点(之前没有找到过那两个结点),可能的情况分为四类:

  • 该结点和要找的任意一个值相等---》返回该节点

  • 该节点的左树上没找到目标节点---》说明结果在右子树上,继续遍历右子树
  • 该节点的右树上没找到目标节点---》说明结果在左子树上,继续遍历左子树
  • 该节点的左树上找到了目标节点,右边也找到了节点---》返回该节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
TreeNode * ret=subtask(root,o1,o2);
return ret->val;
}
TreeNode * subtask(TreeNode* root, int o1, int o2){
if(root==nullptr||root->val==o1||root->val==o2){
return root;
}
TreeNode *left=subtask(root->left, o1, o2);
TreeNode * right=subtask(root->right,o1,o2);
if(left==nullptr){
return right;
}
if(right==nullptr){
return left;
}
return root;
}
};

本题还可以使用hashmap和hashset暴力求解(思路比较简单此处不做陈述)

这种用hashmap记录的一条可以反向回去的链条的方法也是比较经典值得思考的。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <unordered_map>
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here

unordered_map<int,int> mymap;
mymap[root->val]=-1;
queue<TreeNode *>myqueue;
myqueue.push(root);
TreeNode * cur;
while(!myqueue.empty()&&(mymap.find(o1)==mymap.end()||mymap.find(o2)==mymap.end())){
cur=myqueue.front();
myqueue.pop();
if(cur->left!=nullptr){
mymap[cur->left->val]=cur->val;
myqueue.push(cur->left);
}
if(cur->right!=nullptr){
mymap[cur->right->val]=cur->val;
myqueue.push(cur->right);
}
}
unordered_set<int> ancestor;
//ancestor.insert(o1);
int curnum=o1;
while(mymap.find(curnum)!=mymap.end()){
ancestor.insert(curnum);
curnum=mymap[curnum];
}
curnum=o2;
while(ancestor.find(curnum)==ancestor.end()){
curnum=mymap[curnum];
}
return curnum;
}

};

利用层次遍历

JZ28 对称的二叉树

给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)

对称的二叉树每一层都是回文的情况。那我们从左往右遍历一层(包括空节点),和从右往左遍历一层(包括空节点),应该得到相同的结果。而且我们不需要两个层次遍历都完整地遍历二叉树,只需要一半就行了,从左往右遍历左子树,从右往左遍历右子树,各自遍历一半相互比对,因为遍历到另一半都已经检查过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pRoot TreeNode类
* @return bool布尔型
*/
bool isSymmetrical(TreeNode* pRoot) {
// write code here
queue<TreeNode*> myqueue1,myqueue2;
if(pRoot==nullptr)
return true;
myqueue1.push(pRoot->left);
myqueue2.push(pRoot->right);
int num=1;
int numnew=0;
while(!myqueue1.empty()){
if(myqueue2.empty()){
return false;
}
while(num!=0){
num--;
TreeNode * cur1=myqueue1.front();
TreeNode * cur2=myqueue2.front();
myqueue1.pop();
myqueue2.pop();
if(cur1==nullptr&&cur2==nullptr){
continue;
}
if((cur1==nullptr&&cur2!=nullptr)||(cur1!=nullptr&&cur2==nullptr))
return false;
if(cur1->val!=cur2->val)
return false;
if(cur1->left!=nullptr&&cur2->right!=nullptr){
myqueue1.push(cur1->left);
myqueue2.push(cur2->right);
numnew++;
}
if(cur2->left!=nullptr&&cur1->right!=nullptr){
myqueue2.push(cur2->left);
myqueue1.push(cur1->right);
numnew++;
}
if((cur2->left==nullptr&&cur1->right!=nullptr)||(cur2->left!=nullptr&&cur1->right==nullptr)||(cur1->left==nullptr&&cur2->right!=nullptr)||
( cur1->left!=nullptr&&cur2->right==nullptr))
return false;
}
num=numnew;
numnew=0;

}
return true;
}
};

路径和问题

JZ82 二叉树中和为某一值的路径(一)

只判断路径是否存在的:

使用简单的递归就可以解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param sum int整型
* @return bool布尔型
*/
bool hasPathSum(TreeNode* root, int sum) {
if(root==nullptr){
return false;
}
return subtask(root,sum);
}
bool subtask(TreeNode* root, int sum) {

//判断是否是叶子节点
if(root->left==nullptr&&root->right==nullptr){
if(sum-root->val==0)
return true;
else
return false;
}
//尝试左边路径
bool lefttry=false;
bool righttry=false;
if(root->left!=nullptr)
lefttry=subtask(root->left, sum-root->val);
//尝试右边路径
if(root->right!=nullptr)
righttry=subtask(root->right, sum-root->val);
return lefttry||righttry;
}
};

JZ34 二叉树中和为某一值的路径(二)

进阶版本,需要给出所有的路径,需要用深度优先搜索有恢复现场的步骤

这个在DFS深度优先搜索博文中介绍过,如下链接:

解析博客

JZ84 二叉树中和为某一值的路径(三)

不再限制是不是从根节点到叶子节点的路径。要计算路径的数量

感觉有点动态规划的意思,不过这里不用改成动态规划,用递归解决即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* };
*/
#include <cstdio>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param sum int整型
* @return int整型
*/
int FindPath(TreeNode* root, int sum) {
if(root==nullptr)
return 0;
// write code here
int ret=0;
ret+=subtask(root, sum);
if(root->right!=nullptr)
ret+=FindPath(root->right,sum);
if(root->left!=nullptr)
ret+=FindPath(root->left,sum);
return ret;
}
int subtask(TreeNode * root ,int target){

//下面注释掉的写法是错的,因为这里的值是存在负数的,
//因此不能用等于0来作为结束标志了。就算等于负数也是有可能的
// if(target==0){
// return 1;
// }
// if(root->left==nullptr&&root->right==nullptr&&target!=0){
// return 0;
// }
//
int ret=0;
if(root==nullptr)
return 0;//这里也不能只是返回0
target-=root->val;
if(target==0)
ret++;
ret+=subtask(root->right,target);
ret+=subtask(root->left,target);
return ret;
}
};