• 【牛客网面试必刷TOP101】二叉树篇(二)


    一、前言

    二叉树是数据结构中重要的一个章节,他的重要性也不言而喻,在未来不管是笔试还是面试都会遇到这类的题目,所以接下来我就会把一些常考的题目全部整理出来供大家学习指正。


    二、学习刷题网站

    在这里插入图片描述

    点击下面链接即可进行刷题学习
    开始刷题

    三、刷题

    先说明一下一些题目取自牛客网面试必刷TOP101
    里面的一些题目在我以前的文章详细写到过,如果没有用新的方法就不会再做讲解
    LeetCode刷题 —— 手撕二叉树

    <1> 判断是不是二叉搜索树

    题目链接
    描述

    给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。
    二叉搜索树满足每个节点的左子树上的所有节点均严格小于当前节点且右子树上的所有节点均严格大于当前节点。

    例:
    图1:
    在这里插入图片描述
    图2:
    在这里插入图片描述

    数据范围:节点数量满足1 ≤ n ≤ 10^4,节点上的值满足1 ≤ val ≤ 2^31−1

    示例1

    输入:{1,2,3}
    返回值:false
    说明:如题面图1

    示例2

    输入:{2,1,3}
    返回值:true
    说明:如题面图2

    思路分析:
    对于这种搜索树有一个规律:
    使用中序遍历就会发现他是递增的,所以我们利用这个性质,使用中序遍历:先遍历左子树,在判断root的值是否大于左子树,不是则返回false,再递归右子树。为了判断大小得加一个全局变量pre。

    #include 
    static long pre = INT_MIN;
    
    bool isValidBST(struct TreeNode* root ) {
        if(!root)
        {
            return true;
        }
        //进入左子树
        if(!isValidBST(root->left))
        {
            return false;
        }
        if(root->val <= pre)
        {
            return false;
        }
        //更新pre值
        pre = root->val;
        //进入右子树
        if(!isValidBST(root->right))
        {
            return false;
        }
        return true;
    }
    
    • 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

    接下来画图理解:
    对于这棵树:
    在这里插入图片描述
    在这里插入图片描述


    <2>判断是不是完全二叉树

    题目链接
    描述

    给定一个二叉树,确定他是否是一个完全二叉树。
    完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)
    数据范围:节点数满足1 ≤ n ≤ 100

    样例图1:
    在这里插入图片描述

    样例图2:
    在这里插入图片描述
    样例图3:
    在这里插入图片描述
    示例1

    输入:{1,2,3,4,5,6}
    返回值:true

    示例2

    输入:{1,2,3,4,5,6,7}
    返回值:true

    示例3

    输入:{1,2,3,4,5,#,6}
    返回值:false

    思路分析:
    根据完全二叉树性质可以发现如果走层序遍历,只要遇到NULL,后边的一定全部为NULL,如果后面出现不为NULL的,一定不是完全二叉树,前面知道层序遍历得用栈。

    typedef struct TreeNode* QDateType;
    
    typedef struct QueueNode
    {
    	struct QueueNode* next;
    	QDateType date;
    }QueueNode;
    
    typedef struct Queue
    {
    	QueueNode* head;
    	QueueNode* tail;
    }Queue;
    
    bool QueueEmpty(Queue* pst);
    
    void QueueInit(Queue* pst)
    {
    	pst->head = pst->tail = NULL;
    }
    
    
    
    void QueueDestroy(Queue* pst)
    {
    	QueueNode* cur = pst->head;
    	while (cur)
    	{
    		QueueNode* next = cur->next;
    		free(cur);
    		cur = next;
    	}
    	pst->head = pst->tail = NULL;
    
    }
    
    void QueuePush(Queue* pst, QDateType x)
    {
    	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    	if (newnode == NULL)
    	{
    		printf("malloc fail\n");
    		exit(-1);
    	}
    	newnode->date = x;
    	newnode->next = NULL;
    	if (pst->head == NULL)
    	{
    		pst->head = newnode;
    		pst->tail = newnode;
    	}
    	else
    	{
    		pst->tail->next = newnode;
    		pst->tail = newnode;
    	}
    }
    
    void QueuePop(Queue* pst)
    {
    	if (pst->head->next == NULL)
    	{
    		free(pst->head);
    		pst->head = pst->tail = NULL;
    	}
    	else
    	{
    		QueueNode* next = pst->head->next;
    		free(pst->head);
    		pst->head = next;
    	}
    }
    
    QDateType QueueBack(Queue* pst)
    {
    	return pst->tail->date;
    }
    
    QDateType QueueFront(Queue* pst)
    {
    	return pst->head->date;
    }
    
    //真返回非0
    bool QueueEmpty(Queue* pst)
    {
    	return pst->head == NULL;
    }
    
    int QueueSize(Queue* pst)
    {
    	QueueNode* cur = pst->head;
    	int size = 0;
    	while (cur)
    	{
    		size++;
    		cur = cur->next;
    	}
    	return size;
    }
    
    bool isCompleteTree(struct TreeNode* root ) {
        Queue q;
        QueueInit(&q);
        QueuePush(&q, root);
        while(!QueueEmpty(&q))
        {
            struct TreeNode* tmp = QueueFront(&q);
            QueuePop(&q);
            if(tmp == NULL)
            {
                //后边必须全部为NULL
                while(!QueueEmpty(&q))
                {
                    if(QueueFront(&q) != NULL)
                    {
                        return false;
                    }
                    QueuePop(&q);
                }
                return true;
            }
            else
            {
                QueuePush(&q, tmp->left);
                QueuePush(&q, tmp->right);
            }
        }
        return true;
    }
    
    • 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
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    <3>二叉搜索树的最近公共祖先

    题目链接

    描述

    给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
    1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x,满足x是p和q的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先.
    2.二叉搜索树是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值
    3.所有节点的值都是唯一的。
    4.p、q 为不同节点且均存在于给定的二叉搜索树中。

    数据范围:

    3<=节点总数<=10000
    0<=节点值<=10000

    如果给定以下搜索二叉树: {7,1,12,0,4,11,14,#,#,3,5},如下图:
    在这里插入图片描述

    示例1

    输入:{7,1,12,0,4,11,14,#,#,3,5},1,12
    返回值:7
    说明:节点1 和 节点12的最近公共祖先是7

    示例2

    输入:{7,1,12,0,4,11,14,#,#,3,5},12,11
    返回值:12
    说明:因为一个节点也可以是它自己的祖先.所以输出12

    思路分析:

    ①搜索路径比较

    利用好搜索二叉树的性质:要找到目标值,如果该节点比目标小,则向右子树寻找,反之向左子树寻找。
    那么我们可以通过两个数组保存两个路径,最后遍历两个数组,最后一个相等的数值就为公共节点(前面一定相等

    void AdjustArr(struct TreeNode* root, int p, int* arr, int* l)
    {
        if(!root)
        {
            return;
        }
        if(root->val < p)
        {
            arr[*l] = root->val;
            (*l)++;
            AdjustArr(root->right, p, arr, l);
        }
        else if(root->val > p)
        {
            arr[*l] = root->val;
            (*l)++;
            AdjustArr(root->left, p, arr, l);
        }
        else
        {
            arr[*l] = root->val;
            (*l)++;
            return;
        }
    }
    
    int lowestCommonAncestor(struct TreeNode* root, int p, int q ) {
        int* arr1 = (int*)malloc(sizeof(int) * 1000);
        //记录长度
        int l1 = 0;
        AdjustArr(root, p, arr1 , &l1);
        int* arr2 = (int*)malloc(sizeof(int) * 1000);
        //记录长度
        int l2 = 0;
        AdjustArr(root, q, arr2 , &l2);
        //遍历两个数组,找到第一个不同的点
        int ret = 0;
        for(int i = 0; i < l1 && i < l2; i++)
        {
            if(arr1[i] == arr2[i])
            {
                ret = arr1[i];
            }
            else
            {
                break;
            }
        }
        return ret;
    }
    
    • 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

    ②一次遍历

    如果p和q都小于当前数值,则公共祖先一定在左子树上,如果p和q都大于当前节点,则公共祖先一定在右子树上,如果一个大于一个小于,则该节点就是公共祖先节点。

    int lowestCommonAncestor(struct TreeNode* root, int p, int q ) {
        if(root->val < p && root->val < q)
        {
            return lowestCommonAncestor(root->right, p, q);
        }
        else if(root->val > p && root->val > q)
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        else
        {
            return root->val;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    用递归图理解一下:
    在这里插入图片描述


    三、小结

    我们可以看到二叉树基本解题思路就是递归,如果递归想不出代码,可以直接看最后一步怎么实现,其次一定要多画图理解。

    点击链接一起刷题吧



  • 相关阅读:
    Java集合面试
    Android 12系统源码_WindowInsets (一)WindowInsets相关类和功能介绍
    FPGA内部资源
    Hadoop3教程(十二):MapReduce中Shuffle机制的概述
    java 手写KMP算法
    Ribbon负载均衡+Nacos服务搭建
    ARM-Cortex_M3/M4处理器开发简介
    Containerd【轻量级容器管理工具】
    C/C++---------------LeetCode第LCR. 024.反转链表
    PyTorch模型定义和相关使用
  • 原文地址:https://blog.csdn.net/qq_66314292/article/details/126561858