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

前言

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

QQ图片20220416140203

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

1.一些选择题

1.1

设一棵二叉树中有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

有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 由已知遍历序列画出原本树的结构

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

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

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

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

IMG_20220415_103954

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

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

image-20220416163152483

已知某二叉树的前序遍历序列为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

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

IMG_20220415_105012

1.4 单边树

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

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

12345(纵向)

54321

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


1.5

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-20220416164513012

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

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

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

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

  • 如果两棵树都为空,树相同
  • 如果其中一个为空,另外一个不为空,树不同
  • 如果两个都不为空,但是节点值不相同,树不同
  • 然后再递归判断左子树和右子树,将它们的结果与&&在一起,其中一个为假,返回假
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-20220416170618951

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

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


101 对称二叉树

leetcode:101. 对称二叉树

image-20220416165729067

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

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

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

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-20220416170605085

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


572 另外一棵树的子树

leetcode:572. 另一棵树的子树

image-20220416170019257

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

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

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-20220416170549320

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

image-20220416170310498


226 翻转二叉树

leetcode:226. 翻转二叉树

image-20220416170322647

这道题的思路如下哈!

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

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

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-20220416170530844

102 层序遍历(较难😥)

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

image-20220420180516194

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

image-20220420180638971

/**
 * 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-20220420181016592

0.错误思路

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

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

1.数组队列初始化

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

#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.初始化数组

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

*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++一次
    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-20220420190637205

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


结语

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

image-20220416170310498

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

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

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕雪华年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值