LeetCode经典栈与队列面试题——《循环队列》、《有效的括号》、《用栈实现队列》、《用队列实现栈》

目录

一、循环队列

循环队列的定义

初始化

入队

出队

获取队头元素

获取队尾元素

 判空函数

判满函数

销毁循环队列

完整代码

二、有效的括号

具体思想

完整代码:

三、用栈实现队列

具体思想

定义

初始化

入队

出队

获取队头元素

判空函数

队列的销毁

完整代码

四、用队列实现栈

具体思想

定义

初始化

入栈

出栈

获取栈顶元素

判空函数

销毁栈

完整代码


一、循环队列

题目介绍:

 题目链接:622. 设计循环队列 - 力扣(LeetCode) (leetcode-cn.com)

循环队列的定义

typedef struct 
{
    int *a;//定义数组
    int front;//队头下标
    int tail;//队尾下标
    int k;//队列长度
} MyCircularQueue;

初始化

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* cq=(MyCircularQueue* )malloc(sizeof(MyCircularQueue));//为cq开辟空间
    cq->a=(int *)malloc(sizeof(int)*(k+1));//为数组开辟空间
//初始化
    cq->front=0;
    cq->tail=0;
    cq->k=k;
    return cq;
}

入队

在插入数据时有一点需要注意,如果tail==k时再向队列中插入数据时tail++就要大于k了,因此我们需要对tail的值进行取模操作,如图所示:

 代码如下:

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    return false;

    obj->a[obj->tail]=value;
    obj->tail++;
    obj->tail%=(obj->k+1);

    return true;
}

出队

在删除数据时也有一点需要注意,如果此时刚好front==k,front++就要大于k了,因此我们需要对front进行取模操作,如图所示:

 代码如下:

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return false;

    obj->front++;
    obj->front%=(obj->k+1);
    return true;

}

获取队头元素

此操作没有什么特别,直接返回下标为front的数组值就可以了。

代码如下:

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return -1;

    return obj->a[obj->front];
}

获取队尾元素

 需要注意的是,如果tail==0时取队尾值的时候需要特判,不能直接返回下标为tail-1的数组值,因为此时tail-1会变成负数,因此需要特判。如图所示:

 代码如下:


int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return -1;

    /*int i=(obj->tail+obj->k)%(obj->k+1);
    return obj->a[i];*/

    if(obj->tail==0)
    return obj->a[obj->k];
    else
    return obj->a[obj->tail-1];

}

 判空函数

若front==tail说明队列为空。

代码如下:


bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->front==obj->tail;
}

判满函数

由于是循环队列,所以如果队列长度和我们定义的数组大小相等的话将很难判断出满的情况,所以我们定义的数组大小比队列长度要大一,这一想法在初始化那一栏已经体现出来了,那么我们将可以很好的判断队列何时为满。

如果tail!=k时,只要tail+1==front,队列就是满的状态,可是如果tail==k时,tail+1将大于k,因此我们用(tail+1)%(k+1)==front来作为判断队列是否为满的条件。

代码如下:

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->tail+1)%(obj->k+1)==obj->front;
}

销毁循环队列

先释放结构中的数组a,在释放结构体。

代码如下:

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}

完整代码

typedef struct 
{
    int *a;
    int front;
    int tail;
    int k;
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* cq=(MyCircularQueue* )malloc(sizeof(MyCircularQueue));
    cq->a=(int *)malloc(sizeof(int)*(k+1));
    cq->front=0;
    cq->tail=0;
    cq->k=k;

    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    return false;

    obj->a[obj->tail]=value;
    obj->tail++;
    obj->tail%=(obj->k+1);

    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return false;

    obj->front++;
    obj->front%=(obj->k+1);
    return true;

}

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return -1;

    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    return -1;

    /*int i=(obj->tail+obj->k)%(obj->k+1);
    return obj->a[i];*/

    if(obj->tail==0)
    return obj->a[obj->k];
    else
    return obj->a[obj->tail-1];

}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->front==obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->tail+1)%(obj->k+1)==obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}

二、有效的括号

题目介绍:

 题目链接:力扣

具体思想

遇到左括号就放到栈里面,遇到右括号就去栈里面去寻找是否有对应的左括号,如果有的话就把它出栈,如果没有的话直接返回false。

数组不为空的话情况如下:

1、" ( ( ) "虽然出栈了一个左括号,但是栈里面还有一个左括号,说明当数组遍历完全的时候栈里面是不能有数据的,如果栈里面还有存留的数据,说明括号不匹配,直接返回false。

2、" ) "当遍历到右括号时,栈里面是空的,也是不匹配的,直接返回false。

3、" ( ] "这种情况其实就是一般思想,栈里面有数据并且也有右括号,但是他们不匹配,直接返回false。

如果这几种情况都不符合,那么就是括号配对啦,返回true。

由于我们还没学到C++,因此还没有栈可以让我们直接使用所以我们在这道题之前需要做的是实现一个栈,随后跟着上述方法去走,就没有错啦。

如果还有不懂的,一下代码中注释慢慢哦,大家可以细细品味,代码如下:

bool isValid(char * s)
{
    
    Stack st;//定义一个栈
    StackInit(&st);//初始化我们定义的栈
    while(*s)//如果数组不为空 才能去做我们要做的括号匹配
    {
        //如果为左括号我们就入栈
        if(*s == '[' 
        || *s == '(' 
        || *s == '{')
        {
            StackPush(&st, *s);
            ++s;入栈之后记好一定要让s+1,达到遍历的效果
        }
        else
        {
            //遇到右括号了 但是栈里面并没有数据
            if(StackEmpty(&st))
            {
                return false;
            }
            //取栈顶元素
            STDataType top = StackTop(&st);
            //将栈顶指向下一个元素达到遍历的效果
            StackPop(&st);
            //由于成立的情况太多太多我们直接求不成立的情况将其返回false
            if((*s == ']' && top != '[')
            || (*s == '}' && top != '{')
            || (*s == ')' && top != '('))
            {
                StackDestroy(&st);
               return false;
            }
            else
            {
                //不要忘记将s+1,达到遍历的效果
                ++s;
            }
        }
    }
    //这个操作主要是针对如果栈里面还有没有配对的左括号
    //说明是括号不匹配,
    //因此只有经过一系列操作后栈为空才符合条件
    bool ret = StackEmpty(&st);
    StackDestroy(&st);
    return ret;
}

完整代码:

#include<stdio.h>	
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;//栈
	STDataType top;//栈顶
	STDataType capacity;//容量
}Stack;

// 初始化栈 
void StackInit(Stack* ps);

// 入栈 
void StackPush(Stack* ps, STDataType x);

// 出栈 
void StackPop(Stack* ps);

// 获取栈顶元素 
STDataType StackTop(Stack* ps);

// 获取栈中有效元素个数 
int StackSize(Stack* ps);

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps);

// 销毁栈 
void StackDestroy(Stack* ps);





// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

// 入栈 
void StackPush(Stack* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 :
			ps->capacity * 2;
		STDataType* tmp = realloc
		(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}


// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top - 1];
}

// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);

	return ps->top;
}

// 检测栈是否为空,如果为空返回非零结果,
//如果不为空返回0 
bool StackEmpty(Stack* ps)
{
	assert(ps);

	return ps->top == 0;
}

// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}
bool isValid(char * s)
{
    
    Stack st;
    StackInit(&st);
    while(*s)
    {
        if(*s == '[' 
        || *s == '(' 
        || *s == '{')
        {
            StackPush(&st, *s);
            ++s;
        }
        else
        {
            //遇到右括号了 但是栈里面并没有数据
            if(StackEmpty(&st))
            {
                return false;
            }
            STDataType top = StackTop(&st);
            StackPop(&st);
            if((*s == ']' && top != '[')
            || (*s == '}' && top != '{')
            || (*s == ')' && top != '('))
            {
                StackDestroy(&st);
               return false;
            }
            else
            {
                ++s;
            }
        }
    }
    bool ret = StackEmpty(&st);
    StackDestroy(&st);
    return ret;
}

三、用栈实现队列

题目介绍:

 题目链接:232. 用栈实现队列 - 力扣(LeetCode) (leetcode-cn.com)

具体思想

利用两个栈来实现队列的各项功能,一个栈用来实现入队操作,一个栈用来实现出队操作。

同上题《有效的括号》一样,我们首先要实现一个栈。

定义

//定义两个栈 一个用来入队 一个用来出队
typedef struct 
{
    ST PushST;
    ST PopST;
} MyQueue;

初始化

MyQueue* myQueueCreate() 
{
    MyQueue *q=(MyQueue*)malloc(sizeof(MyQueue));//为q开辟空间
    //分别对两个栈进行初始化
    StackInit(&q->PushST);
    StackInit(&q->PopST);
    return q;
}

入队

对push栈进行入队操作。

代码如下:

void myQueuePush(MyQueue* obj, int x) 
{
    StackPush(&obj->PushST,x);
}

出队

这个操作算是这道题的重难点,我们需要先把push栈中的数据依次出栈,并且入栈到pop栈中,然后对pop栈进行出栈操作,就达到了出队操作,这里图解更清楚,如图所示:

 代码如下:

int myQueuePop(MyQueue* obj) 
{
    //条件 pop为空且push不为空的时候把push的值放入pop中
    if(StackEmpty(&obj->PopST))
    {
        while(!StackEmpty(&obj->PushST))
        {
            StackPush(&obj->PopST,StackTop(&obj->PushST));
            StackPop(&obj->PushST);
        }
    }
    int front=StackTop(&obj->PopST);
    StackPop(&obj->PopST);
    return front;
}

获取队头元素

其实这个跟上一个Pop极其相似 只不过Pop是把队头的数据获取到后给移除了,而这个操作只是获取到了队头的数据。

代码如下:

int myQueuePeek(MyQueue* obj) 
//其实这个跟上一个Pop极其相似 只不过Pop是把队头的数据获取到后给移除了
//而这个操作只是获取到了队头的数据
{
    //条件 pop为空切push不为空的时候把push的值放入pop中
    if(StackEmpty(&obj->PopST))
    {
        while(!StackEmpty(&obj->PushST))
        {
            StackPush(&obj->PopST,StackTop(&obj->PushST));
            StackPop(&obj->PushST);
        }
    }
    return StackTop(&obj->PopST);

}

判空函数

当pop栈与push栈都为空时,队列就为空。

代码如下:

bool myQueueEmpty(MyQueue* obj) 
{
    return StackEmpty(&obj->PopST)&&StackEmpty(&obj->PushST);
}

队列的销毁

先销毁两个栈再销毁指向栈的指针。

代码如下:

void myQueueFree(MyQueue* obj) 
{
    StackDestroy(&obj->PushST);
    StackDestroy(&obj->PopST);
    free(obj);
}

完整代码

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);



bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0; // ps->top = -1;
	ps->capacity = 0;
}

void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top - 1];
}

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}







//定义两个栈 一个用来入队 一个用来出队
typedef struct 
{
    ST PushST;
    ST PopST;
} MyQueue;

MyQueue* myQueueCreate() 
{
    MyQueue *q=(MyQueue*)malloc(sizeof(MyQueue));//为q开辟空间
    //分别对两个栈进行初始化
    StackInit(&q->PushST);
    StackInit(&q->PopST);
    return q;
}

void myQueuePush(MyQueue* obj, int x) 
{
    StackPush(&obj->PushST,x);
}

int myQueuePop(MyQueue* obj) 
{
    //条件 pop为空且push不为空的时候把push的值放入pop中
    if(StackEmpty(&obj->PopST))
    {
        while(!StackEmpty(&obj->PushST))
        {
            StackPush(&obj->PopST,StackTop(&obj->PushST));
            StackPop(&obj->PushST);
        }
    }
    int front=StackTop(&obj->PopST);
    StackPop(&obj->PopST);
    return front;
}

int myQueuePeek(MyQueue* obj) 
//其实这个跟上一个Pop极其相似 只不过Pop是把队头的数据获取到后给移除了
//而这个操作只是获取到了队头的数据
{
    //条件 pop为空切push不为空的时候把push的值放入pop中
    if(StackEmpty(&obj->PopST))
    {
        while(!StackEmpty(&obj->PushST))
        {
            StackPush(&obj->PopST,StackTop(&obj->PushST));
            StackPop(&obj->PushST);
        }
    }
    return StackTop(&obj->PopST);

}

bool myQueueEmpty(MyQueue* obj) 
{
    return StackEmpty(&obj->PopST)&&StackEmpty(&obj->PushST);
}

void myQueueFree(MyQueue* obj) 
{
    StackDestroy(&obj->PushST);
    StackDestroy(&obj->PopST);
    free(obj);
}

/**
 * Your MyQueue struct will be instantiated and called as such:
 * MyQueue* obj = myQueueCreate();
 * myQueuePush(obj, x);
 
 * int param_2 = myQueuePop(obj);
 
 * int param_3 = myQueuePeek(obj);
 
 * bool param_4 = myQueueEmpty(obj);
 
 * myQueueFree(obj);
*/

四、用队列实现栈

题目介绍:

题目链接:225. 用队列实现栈 - 力扣(LeetCode) (leetcode-cn.com) 

具体思想

我们用两个队列来实现一个栈,和上题一样,在做这道题之前我们需要手动实现一个队列。

定义

typedef struct 
//定义了两个队列
{
	Queue q1;
	Queue q2;
} MyStack;

初始化

MyStack* myStackCreate() 
{
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&st->q1);
    QueueInit(&st->q2);
    return st;
}

入栈

将数据插入到不为空的队列中。

void myStackPush(MyStack* obj, int x) 
{
    //将数据插入到不为空的队列中也就是我们要实现的栈中
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

出栈

此题难点来咯,出栈的时候我们需要将不空的那个队列的数据留一个数据其余数据入队到那个空的队列中去,然后将剩余的那个数据进行出队,就实现了出栈操作咯,如图所示:

 

 代码如下:


int myStackPop(MyStack* obj) 
{
    //找到空的那个队列
    Queue* emptyQ=&obj->q1;
    Queue*nonemptyQ=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        emptyQ=&obj->q2;
        nonemptyQ=&obj->q1;
    }

    //在有数据的队列里面留一个数据等待pop其他的依次入队到没有数据的队列里面
    while(QueueSize(nonemptyQ)>1)
    {
        QueuePush(emptyQ,QueueFront(nonemptyQ));
        QueuePop(nonemptyQ);
    }
    //获取并返回 “栈顶” 也就是队尾的那个数据 并且pop掉
    int top=QueueFront(nonemptyQ);
    QueuePop(nonemptyQ);
    return top;
}

获取栈顶元素

只要获取非空队列的队尾数据就可以了。

int myStackTop(MyStack* obj) 
{
    if(!QueueEmpty(&obj->q1))
    {
       return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

判空函数

如果两个队列都为空,栈就为空。

bool myStackEmpty(MyStack* obj) 
{
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

销毁栈

先释放q1和q2,然后再释放结构体。

void myStackFree(MyStack* obj) 
{
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    QueueDestroy(obj);
}

完整代码

typedef int QDataType;

typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;

typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
	// size_t _size;
}Queue;

//void QueueInit(QueueNode** pphead, QueueNode** pptail);
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);


void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->head = pq->tail = NULL;
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

void QueuePop(Queue* pq)
{
	assert(pq);
	//if (pq->head == NULL)
	//	return;
	assert(!QueueEmpty(pq));

	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

int QueueSize(Queue* pq)
{
	assert(pq);

	int n = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		++n;
		cur = cur->next;
	}

	return n;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}




typedef struct 
//定义了两个队列
{
	Queue q1;
	Queue q2;
} MyStack;

MyStack* myStackCreate() 
{
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&st->q1);
    QueueInit(&st->q2);
    return st;
}

void myStackPush(MyStack* obj, int x) 
{
    //将数据插入到不为空的队列中也就是我们要实现的栈中
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj) 
{
    //找到空的那个队列
    Queue* emptyQ=&obj->q1;
    Queue*nonemptyQ=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        emptyQ=&obj->q2;
        nonemptyQ=&obj->q1;
    }

    //在有数据的队列里面留一个数据等待pop其他的依次入队到没有数据的队列里面
    while(QueueSize(nonemptyQ)>1)
    {
        QueuePush(emptyQ,QueueFront(nonemptyQ));
        QueuePop(nonemptyQ);
    }
    //获取并返回 “栈顶” 也就是队尾的那个数据 并且pop掉
    int top=QueueFront(nonemptyQ);
    QueuePop(nonemptyQ);
    return top;
}

int myStackTop(MyStack* obj) 
{
    if(!QueueEmpty(&obj->q1))
    {
       return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) 
{
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) 
{
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    QueueDestroy(obj);
}

/**
 * Your MyStack struct will be instantiated and called as such:
 * MyStack* obj = myStackCreate();
 * myStackPush(obj, x);
 
 * int param_2 = myStackPop(obj);
 
 * int param_3 = myStackTop(obj);
 
 * bool param_4 = myStackEmpty(obj);
 
 * myStackFree(obj);
*/

猜你喜欢

转载自blog.csdn.net/m0_57249790/article/details/124328230