【C语言】带你用偷懒的方式刷爆二叉树OJ题
慕雪年华

[TOC]

前言

上篇博客我带大家领略了一番链式二叉树的操作,现在让我们来看看二叉树的相关题目,一起来巩固一下知识点吧!

image

点我复习上一篇博客的内容!👉传送门

1.一些选择题

1.1

1
2
3
4
5
设一棵二叉树中有3个叶子结点,有8个度为1的结点,则该二叉树中总的结点数为( )个
A.11
B.12
C.13 √
D.14

设Ni表示度为i的节点个数,则节点总数 N = N0 + N1 + N2

节点个数于节点边的关系: N个节点的树有N-1个边

边与度的关系:N - 1 = N1 + 2 * N2

故:N0 + N1 + N2 - 1 = N1 + 2 * N2

因此,得:N0 = N2 + 1

回到原题,N0 = 3,N1 = 8,可得N2 = 2

因此答案是 3 + 8 + 2 = 13

1.2

1
2
有N个元素的完全二叉树的深度是()
答案:logN+1

高度为h的完全二叉树,节点个数在: 2^(h - 1) - 1 < n <= 2^h - 1

log(n + 1) <= h < log(n + 1) + 1

这里需要注意的是n左右区间的开闭问题

完全二叉树最少的节点个数是2^(h - 1)-1+1个,所以是n>2^(h - 1) - 1


1.3 由已知遍历序列画出原本树的结构

1
2
3
4
5
已知某二叉树的中序遍历序列为JGDHKBAELIMCF,后序遍历序列为JGKHDBLMIEFCA,则其前序遍历序列为( )
A.ABDGHJKCEFILM
B.ABDGJHKCEILMF √
C.ABDHKGJCEILMF
D.ABDGJHKCEIMLF

这道题我刚开始的思路是错的,因为我把它当作完全二叉树来看待,但题目并没有说它是完全二叉树

主要思路:可以从后续遍历确定根节点为A,中序遍历可以确定A的左右子树。再继续从后序遍历中确定A左右子树的根节点,依次往下判断

所以我画了一个分析图,如下👇

image

1
2
3
4
5
已知某二叉树的前序遍历序列为ABDEC,中序遍历序列为BDEAC,则该二叉树( )
A.是满二叉树
B.是完全二叉树,不是满二叉树
C.不是完全二叉树 √
D.是所有的结点都没有右子树的二叉树

这道题的思路和上一道题是一样的

image

1
2
3
4
5
已知某二叉树的前序遍历序列为5 7 4 9 6 2 1,中序遍历序列为4 7 5 6 9 1 2,则其后序遍历序列为( )
A.4 2 5 7 6 9 1
B.4 2 7 5 6 9 1
C.4 7 6 1 2 9 5 √
D.4 7 2 9 5 6 1

本题依旧和上面两道题思路相同!

image

1.4 单边树

1
2
3
4
5
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )
A.所有的结点均无左孩子
B.所有的结点均无右孩子
C.只有一个叶子结点
D.至多只有一个结点

如果前序遍历和后序遍历序列正好相反,说明它是一个单边树,比如下面这前序和中序序列所构成的树的结构:

12345(纵向)

54321

对于单边树,只有一个叶子节点


1.5

1
2
3
4
5
6
20.如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( )种

A.13
B.14 √
C.15
D.16

首先这棵二叉树的高度一定在3~4层之间:

三层:

A(B(C,D),()), A((),B(C,D)), A(B(C,()),D), A(B((),C),D),

A(B,C(D,())), A(B,C((),D))

四层:

如果为四层,就是单边树,每一层只有一个节点,除过根节点,其他节点都有两种选择,在上层节点的左边还是右边,所以222共8种

总共为14种。


2.OJ题刷起来!

KY11 二叉树遍历

牛客网 KY11 二叉树遍历 👉传送门

image

这道题要求我们用先序遍历的操作从一个数组中读出一个树,并构建出树的基本结构,再用中序遍历的方式打印出这颗树

之前我们学习了前序遍历的操作,这里只需要把前序遍历中的printf操作改成构建新树即可

  • 因为涉及道i的多次调用,所以函数中的i需要取地址,必须保证多次调用的i会同步++
  • 构建完树的节点,并赋值后,需要递归构建左右子树,最后返回节点的地址
  • 题目中的#代表NULL,直接return空即可
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
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef char BTDataType;

typedef struct BTreeNode
{
BTDataType data;
struct BTreeNode* left;
struct BTreeNode* right;
}BTNode;

// 二叉树中序遍历
void BTreeInOrder(BTNode* root)
{
if (root == NULL){
return ;
}

BTreeInOrder(root->left);
printf("%c ", root->data);
BTreeInOrder(root->right);
}

BTNode* CreatTree(char *arr,int*i)
{
if (arr[*i] == '#'){
(*i)++;
return NULL;
}

BTNode* newnode=(BTNode*)malloc(sizeof(BTNode));

newnode->data=arr[(*i)++];//i必须取地址
newnode->left=CreatTree(arr,i);//递归构建左子树
newnode->right=CreatTree(arr,i);//递归构建右子树

return newnode;
}

int main()
{
char arr[100];
scanf("%s",arr);

int i=0;
BTNode* root=CreatTree(arr,&i);
BTreeInOrder(root);
return 0;
}

100 相同的树

leetcode:100. 相同的树

image

题目要求很简单,给定两颗树的根节点,要求我们判断这两棵树是否相同

  • 如果两棵树都为空,树相同
  • 如果其中一个为空,另外一个不为空,树不同
  • 如果两个都不为空,但是节点值不相同,树不同
  • 然后再递归判断左子树和右子树,将它们的结果与&&在一起,其中一个为假,返回假
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL&&q==NULL)//比较是否两个节点都为空,都为空是真
return true;

if(p==NULL||q==NULL)//如果有一个为空,另外一个非空,即为假
return false;

if(p->val!=q->val)//都不是空,判断val的值是否相等
return false;

//递归判断左子树和右子树是否相等
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}

image

学会这道题后,后面一些题目其实只需要把它的代码改一改就能用了😂

什么?你不信?那就看看下面这道题!


101 对称二叉树

leetcode:101. 对称二叉树

image

题目要求很简单哈,判断是不是两边对称的树

这和判断树相等有什么区别呢?不就是把左右子树的判断改一下就行了嘛?

直接调用上一题的代码!注意最后的return值,是p的左和q的右进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool _isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL&&q==NULL)//比较是否两个节点都为空,都为空是真
return true;

if(p==NULL||q==NULL)//如果有一个为空,另外一个非空,即为假
return false;

if(p->val!=q->val)//都不是空,判断val的值是否相等
return false;

//递归判断左子树和右子树是否对称相等
return _isSameTree(p->left,q->right)
&& _isSameTree(p->right,q->left);
}

bool isSymmetric(struct TreeNode* root){
return _isSameTree(root->left,root->right);
}

image

哈哈,是不是很爽,别急,后面还有可以偷懒的题目!


572 另外一棵树的子树

leetcode:572. 另一棵树的子树

image

这道题我们要判断一颗树是否为另外一棵树的子树,和判断一个字符串是不是另外一个字符串的子串很相似

其实只需要递归判断每一个节点的左右子树是否和subRoot相同就可以了!

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
bool _isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL&&q==NULL)//比较是否两个节点都为空,都为空是真
return true;

if(p==NULL||q==NULL)//如果有一个为空,另外一个非空,即为假
return false;

if(p->val!=q->val)//都不是空,判断val的值是否相等
return false;

//递归判断左子树和右子树是否相等
return _isSameTree(p->left,q->left)
&& _isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
// if(root==NULL&&subRoot==NULL)
// return true;
// if(root!=NULL&&subRoot==NULL)
// return true;
// 让isSametree函数来比较这俩个

if(root==NULL)
return false;

if(_isSameTree(root,subRoot))
return true;

//只要左右有一个是返回真,那就是子树
return isSubtree(root->left,subRoot)
|| isSubtree(root->right,subRoot);
}

image

是不是爽起来了?再来一道!

image


226 翻转二叉树

leetcode:226. 翻转二叉树

image

这道题的思路如下哈!

  • 如果是空树,不需要翻转,直接return
  • 如果非空,就把该节点的左右子树交换(这里不需要担心交换后找不到子树的问题)
  • 不需要单独搞空的子树,一并交换就可以
  • 当根节点为空的时候,return

啪的一下很快哈,代码就写出来了!

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
void _invertTree(struct TreeNode* root)
{
if(root==NULL)//设置退出条件,如果根节点为空就返回
return;

//让另外两个值来接收原本的左右节点
struct TreeNode* left=root->left;
struct TreeNode* right=root->right;

//更改左右节点
root->right=left;
root->left=right;

//递归子树
_invertTree(root->left);
_invertTree(root->right);
}

struct TreeNode* invertTree(struct TreeNode* root){
if(root==NULL)//判断空树
return NULL;

_invertTree(root);

return root;
}

image

102 层序遍历(较难😥)

leetcode:102. 二叉树的层序遍历

image

这道题相对来说就么有那么容易了,你可能和我一样,压根没看明白题目要求中的后两个参数是用来干嘛的

image

1
2
3
4
5
6
7
8
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){

}

看了一些题解,这才算理解了这道题的要求

  • *returnSize:存放的是二叉树的层数
  • **returnColumnSizes:存放的是二叉树每一层的节点个数
  • 返回值要求是int**:需要返回一个指针数组,该数组中的每一个元素是一个数组A,数组A保存了二叉树每一层的节点值

image

0.错误思路

最开始我的想法是,用单独的函数计算出树的节点个数和层级,再进行一次层序遍历来得到树的值。

但很显然,这一思路在本题是搞不通的!🤔

1.数组队列初始化

上一篇博客中,我讲述了利用队列来实现层序遍历的思路。这道OJ题目我们也是这么干的。不同的是,在我自己写的队列实现里,使用的是链式队列。而本题使用数组队列会好一点!

1
2
3
4
5
6
7
#define MAX 2000
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes) {
if (root == NULL)
return;

struct TreeNode* Queue[MAX];//队列,存放节点的地址
int front = 0, tail = 0;//指向队头和队尾

2.初始化数组

这部分会毕竟绕,先一步一步来理解

1
2
3
4
5
*returnSize = 0;//将二叉树层级初始化为0
//存放二叉树的每一层节点的值
int** ret = (int**)malloc(sizeof(int*) * MAX);
//开辟一个数组来存放每一层的节点个数
*returnColumnSizes = (int*)malloc(sizeof(int*) * (MAX / 2));
  • ret是一个指针数组,存放的是数组A,数组A里面是每一层的节点值。ret也就是题目要求的返回值
  • *returnColumnSizes开辟一个数组来保存每一层的节点数

这里其实returnColumnSizes没有啥二级指针的必要,但是既然题目给了是int**,我们就需要先*解引用再malloc开辟数组

3.队列操作思路

  • 先让根节点入队列,tail++
  • 外层循环判断队列是否非空,如果非空就停止操作
  • 内层循环进行每一层的入队操作,这样才能得到每一层的节点值和节点个数
  • 在内层循环中创建ret数组的子数组,单独存放每一层的节点值
  • 最后将每一层的节点个数赋值给*returnColumnSizes数组,*returnSize++一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   struct TreeNode* head;
Queue[tail++] = root;//根节点入队
while (front != tail)
{
int Csize = 0;//每一层的节点个数
int end = tail;
//end是每一层最末一个节点的指针。在后续的入队列操作中tail会改变,所以需要保存tail的值
ret[*returnSize] = (int*)malloc(sizeof(int*) * (end - front));
//为每一层开辟一个单独的数组来存放值
while (front < end)
{
head = Queue[front++];
ret[*returnSize][Csize++] = head->val;
//数组赋值,同时每一层的节点个数Csize++
if (head->left != NULL)
Queue[tail++] = head->left;
if (head->right != NULL)
Queue[tail++] = head->right;
}
(*returnColumnSizes)[*returnSize] = Csize;//赋值每一层的节点个数
(*returnSize)++;//层数+1
}
return ret;

外层循环结束后,此时ret数组就是题目要求的结果了,返回ret就可以了!

这里有一个小问题,当树为空树时,层级应该是0。所以我们需要在第一行赋值*returnSize = 0;不然会执行出错

image

这道题的思路是我看过题解之后才搞明白的,所以上面的只是一个思路的复现😭还是太菜了!


结语

本篇刷题笔记到这里就结束啦,如果对涉及到的题目有什么不懂的地方,可以在评论区提出哦!

image

要是知道,OJ也能偷懒,嘿嘿嘿嘿……😂

不过最后一道题是真的难,我还以为它和前中后序遍历一样,只是让我们遍历出数组的值呢🙃