[TOC]
前言 
本篇博客,将带着大家刷3道非常经典的OJ题。它们并不算特别难,但对我们理解数据结构中栈和队列的概念有很大的帮助。
如果你还不了解栈 ,可以看看我之前 的博客👉点我 
队列 的博客就不写啦,本篇刷题的时候会提到队列的操作
 
话不多说,直接开始吧!
1.用队列实现栈 
leetcode:225. 用队列实现栈 
 
这道题的要求很简单,用两个队列来模拟栈的实现。
我们知道,队列的操作是从后进,从前出 ,这就和我们在餐厅排队一样,先进入餐厅排队的人先得到座位。
而栈是遵循上进上出 的,即栈只能在栈顶插入元素和删除元素
两个队列要如何结合,才能实现栈的要求呢?
1.1思路 
首先我们讲数据push到其中一个队列中
如果要访问此时的栈顶 ,使用队列中的tail尾指针来访问,即题目要求的TOP函数
当我们需要pop数据的时候,将队列中的N-1个数据全部移动到另外一个队列里,再将最后的栈顶数据删除并返回 
最终的目的就是保证一个队列为空,另外一个队列保存数据 
 
这样就达成了栈只能在栈顶删除数据的要求
最后还有一个函数是boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
当两个队列中都没有数据的时候,这个模拟的栈就是空的; 
只要其中一个队列有数据的时候,就代表这个栈是有数据的。 
 
1.2队列的代码 
你可能会注意到,这道题中并没有给我们初始的队列代码,也就是说我们需要“造轮子”,将自己写的C语言队列代码放在前头
这里我贴出一个队列的代码,大家可以复制到自己的编译器上测试一下。
如果有不理解的地方,可以在评论区提出 !
和栈的代码实现不同,队列使用链表的方式更加方便。其相当于一个只能尾插和头删的单链表 
为了方便我们找尾进行尾插,这里定义了另外一个结构体类型Queue,其中tail指针指向链表的尾部,方便尾插(否则每次尾插都需要遍历) 
 
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 typedef  int  QDataType;typedef  struct  QueueNode {     QDataType data;     struct  QueueNode * next ; }QNode; typedef  struct  Queue {     QNode* head;     QNode* tail; }Queue; void  QueueInit (Queue* pq) {     assert(pq);     pq->head = NULL ;     pq->tail = NULL ; } void  QueueDestory (Queue* pq) {     assert(pq);     QNode* findtail = pq->head;     while  (findtail)     {         QNode* next = findtail->next;         free (findtail);         findtail = next;     }     pq->head = NULL ;     pq->tail = NULL ; } void  QueuePush (Queue* pq, QDataType x) {     assert(pq);     QNode* newnode = (QNode*)malloc (sizeof (QNode));     if  (newnode == NULL )     {         printf ("Push err\n" );         exit (-1 );     }     newnode->data = x;     newnode->next = NULL ;     if  (pq->tail == NULL )     {         assert(pq->head == NULL );         pq->head = pq->tail = newnode;     }     else      {         pq->tail->next = newnode;         pq->tail = newnode;     }     return ; } void  QueuePop (Queue* pq) {     assert(pq);     assert(pq->head && pq->tail);     if  (pq->head->next == NULL )     {         free (pq->head);         pq->head = NULL ;         pq->tail = NULL ;     }     else      {         QNode* next = pq->head->next;         free (pq->head);         pq->head = next;     } } bool  QueueEmpty (Queue* pq) {     assert(pq);          return  pq->head == NULL  && pq->tail == NULL ; } size_t  QueueSize (Queue* pq) {     assert(pq);     size_t  size = 0 ;     QNode* findtail = pq->head;     while  (findtail)     {         size++;         findtail = findtail->next;     }     return  size; } QDataType QueueFront (Queue* pq)  {     assert(pq);     assert(pq->head);     return  pq->head->data; } QDataType QueueBack (Queue* pq)  {     assert(pq);     assert(pq->tail);     return  pq->tail->data; } 
1.3本题的代码 
将上述的队列代码拷贝在题目所给模板之前后,我们就可以开始写这道题需要的代码实现了
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 typedef  struct  {    Queue q1;     Queue q2; } MyStack; MyStack* myStackCreate ()  {     MyStack* pst = (MyStack*)malloc (sizeof (MyStack));          if  (pst == NULL )         return  NULL ;     QueueInit(&pst->q1);     QueueInit(&pst->q2);     return  pst; } void  myStackPush (MyStack* obj, int  x)  {    assert(obj);          if  (!QueueEmpty(&obj->q1))     {         QueuePush(&obj->q1, x);     }     else      {         QueuePush(&obj->q2, x);     } } int  myStackPop (MyStack* obj)  {    assert(obj);          Queue* empty = &obj->q1;     Queue* unempty = &obj->q2;     if  (!QueueEmpty(&obj->q1))     {         empty = &obj->q2;         unempty = &obj->q1;     }     while  (QueueSize(unempty) > 1 )     {         int  top1 = QueueFront(unempty);         QueuePush(empty, top1);         QueuePop(unempty);     }          int  top = QueueFront(unempty);     QueuePop(unempty);     return  top; } int  myStackTop (MyStack* obj)  {    assert(obj);     if  (!QueueEmpty(&obj->q1))     {         return  QueueBack(&obj->q1);     }     else      {         return  QueueBack(&obj->q2);     } } bool  myStackEmpty (MyStack* obj)  {    assert(obj);          return  QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2); } void  myStackFree (MyStack* obj)  {    assert(obj);     QueueDestory(&obj->q1);     QueueDestory(&obj->q2);     free (obj);     return ; } 
16个用例都完美通过了!
1.4优化设计 
使用一个队列就能实现这个功能。
数据入队列; 
需要top/pop的时候,将队列前的数据全部重新移动到队列尾部,留下最后一个,就是栈顶数据; 
top函数调用完毕后需要将剩下的这个数据也移动到队列尾部; 
pop函数调用完毕后将size--; 
 
代码如下
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 class  MyStack  {    int  size = 0 ;     queue<int > que; public :    MyStack () {     }          void  push (int  x)           que.push (x);         size++;     }          int  pop ()                    int  count = size;         while (count-->1 ){             int  temp = que.front ();             que.pop ();             que.push (temp);         }                  int  ret = que.front ();         que.pop ();         size--;         return  ret;     }          int  top ()                             int  count = size;         while (count-->1 ){             int  temp = que.front ();             que.pop ();             que.push (temp);         }                  int  ret = que.front ();         que.push (ret);         que.pop ();          return  ret;     }          bool  empty ()           return  size == 0 ;     } }; 
2.用栈实现队列 
leetcode: 232. 用栈实现队列 
 
有了上一道题目的经验,这道题的实现就不那么困难了。
2.1思路 
栈的特点是只能在栈顶删除和插入数据
 
队列需要在队尾插入数据,在对头删除数据
 
 
假设现在我们有下面这两个栈,第一个栈里面存放了1 2 3 4的数据
需要进行队列的POP 操作时,我们需要删除的是最底部的1
可以先将栈中的所有数据pop并push到另外一个空栈 中,再将最后一个数据存放后删除,返回存放的值。
这部分操作与第一题中的操作很像,但是有一个致命的问题 :在新的栈里的数据和我们原本想要的数据是相反的!
1 2 3 1  2  3  4  删除1 后原本数据 2  3  4  实际数据 4  3  2  
解决这个问题的方法只有一个,那就是把这一批数据再复制回原本的栈中,它的顺序就对了
而我自己写的栈是用数组 实现的,题目要求的peek函数可以直接通过访问非空栈的0下标 元素得到
empty函数同理,只有两个栈中都为空时,模拟的队列才是空的
2.2栈的代码 
栈的实现在本文开头的博客链接中有详细解释,这里不再复述
回到开头 
 
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 typedef  int  STDataType;typedef  struct  Stack {     STDataType* a;     int  top;      int  capacity;  }Stack; void  StackInit (Stack* ps) {     STDataType* new = (STDataType*)malloc (sizeof (STDataType) * 4 );     if  (new == NULL )     {         exit (-1 );     }     else      {         ps->a = new;         ps->top = 0 ;         ps->capacity = 4 ;     } } void  StackDestroy (Stack* ps) {     assert(ps);     free (ps->a);     ps->a = NULL ;     ps->capacity = 0 ;     ps->top = 0 ; } void  StackPush (Stack* ps, STDataType data) {     assert(ps);     if  (ps->top == ps->capacity)     {         STDataType* new = (STDataType*)realloc (ps->a, sizeof (STDataType) * (ps->capacity) * 2 );         if  (new == NULL )         {             exit (-1 );         }         else          {             ps->a = new;             ps->capacity *= 2 ;         }     }     ps->a[ps->top] = data;     ps->top++; } void  StackPop (Stack* ps) {     assert(ps);     if  (ps->top > 0 )         (ps->top)--; } STDataType StackTop (Stack* ps)  {     assert(ps);     assert(ps->top > 0 );     return  ps->a[ps->top - 1 ]; } int  StackSize (Stack* ps) {     assert(ps);     return  ps->top; } bool  StackEmpty (Stack* ps) {     assert(ps);     if  (ps->top == 0 )         return  true ;     else          return  false ; } void  StackPrint (Stack* ps) {     assert(ps);     int  n = ps->top;     for  (int  i = 0 ; i < n; i++)     {         printf ("%d " , ps->a[i]);     }     printf ("\n" ); } 
2.3本题的代码 
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 typedef  struct  {    Stack st1;     Stack st2; } MyQueue; MyQueue* myQueueCreate ()  {     MyQueue* qt = (MyQueue*)malloc (sizeof (MyQueue));     if  (qt == NULL )         return  NULL ;     StackInit(&qt->st1);     StackInit(&qt->st2);     return  qt; } void  myQueuePush (MyQueue* obj, int  x)  {    assert(obj);     if  (!StackEmpty(&obj->st1))     {         StackPush(&obj->st1, x);     }     else      {         StackPush(&obj->st2, x);     }     return ; } int  myQueuePop (MyQueue* obj)  {    assert(obj);     Stack* empty = &obj->st1;     Stack* nonempty = &obj->st2;     if  (!StackEmpty(&obj->st1))     {         Stack* empty = &obj->st2;         Stack* nonempty = &obj->st1;     }     while  (StackSize(nonempty) > 1 )     {         int  top1 = StackTop(nonempty);         StackPush(empty, top1);         StackPop(nonempty);     }     int  top = StackTop(nonempty);     StackPop(nonempty);          while  (StackSize(empty) > 0 )     {         int  top2 = StackTop(empty);         StackPush(nonempty, top2);         StackPop(empty);     }     return  top; } int  myQueuePeek (MyQueue* obj)  {    assert(obj);     Stack* empty = &obj->st1;     Stack* nonempty = &obj->st2;     if  (!StackEmpty(&obj->st1))     {         Stack* empty = &obj->st2;         Stack* nonempty = &obj->st1;     }          return  nonempty->a[0 ]; } bool  myQueueEmpty (MyQueue* obj)  {    assert(obj);     return  StackEmpty(&obj->st1) && StackEmpty(&obj->st2); } void  myQueueFree (MyQueue* obj)  {    assert(obj);     StackDestroy(&obj->st1);     StackDestroy(&obj->st2);     free (obj);     return ; } 
2.4优化设计 
上面的这个方法在peek和pop的时候都需要把数据搬两次,比较麻烦;
但使用另外一种方式,一个做入栈,一个做出栈,就可以减少数据在两个栈间拷贝的次数。
队列插入数据的时候,往入栈插入; 
队列删除数据的时候,将入栈的所有数据弹出并插入出栈,此时出栈顶部就是需要删除的数据; 
新数据插入,继续往入栈插入,重复上述两个步骤。 
 
出队列的时候需要遵循先入先出的顺序,如果出栈中有数据,那么就直接取就是需要出队列的数据了。出栈中没有数据,才需要将入栈中的所有数据都放入出栈。
代码如下
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 class  MyQueue  {    stack<int > stIn,stOut;     int  size = 0 ; public :    MyQueue () {     }          void  push (int  x)           stIn.push (x);         size++;     }          int  pop ()           int  ret = peek ();          stOut.pop ();         size--;         return  ret;     }          int  peek ()           if (stOut.empty ()){                          int  count = size;             while (count--)             {                 stOut.push (stIn.top ());                 stIn.pop ();             }            }                  int  ret = stOut.top ();         return  ret;     }          bool  empty ()           return  size == 0 ;     } }; 
3.设计循环队列 
leetcode:622. 设计循环队列 
 
这道题最主要的突破口,就是弄明白题目所说的循环队列的意思
实际上他就是一个下图所示的队列,当tail使用完已有空间后,可以跳到前面继续使用之前开辟好的空间,而不需要额外的扩容空间。
为了方便找头以及找尾,本体将使用数组方式来实现它的代码
3.1思路 
确定是数组形式后,我们就要来思考两个问题:什么时候队列为空?什么时候队列是满?
3.1.1判断是否为空 
这个看起来好像非常简单,只要头指针和尾指针相同,队列不就是空的了嘛!
这个想法对,但有有点瑕疵,即我们怎么界定空和非空的界限?
3.1.2判断是否为满 
每插入一个数据,tail指针就会往后++一次,指向已有数据的下一位
当下一位也被装好了数据之后,tail就会回到开头 ,来到front指针的位置!
这个时候,tail=front,但是队列实际上已经满了!
这就让我们不得不思考,如何将这两种情况区分开来? 
啊哈哈哈哈,答案来喽:
如果需要的数据为K个,为队列的数组开辟K+1个空间
 
这时候,只要tail+1=front,就代表队列已经满了。多出来的这一个空间不存放数据 
注意:这个空间不一定是最末尾的哪一个,它会随着队列的插入、删除操作而移动 
当tail在尾部,tail=k(注意tail是下标而不是指针)且front=0时,队列为满 
 
这样就很完美的把空和满 两种情况给区分开来了!
3.2本题代码实现 
需要注意题目给出的返回值要求,依照题目函数要求来编写
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 typedef  struct  {    int * data;     int  front;     int  tail;     int  k; } MyCircularQueue; bool  myCircularQueueIsEmpty (MyCircularQueue* obj) ;bool  myCircularQueueIsFull (MyCircularQueue* obj) ;MyCircularQueue* myCircularQueueCreate (int  k)  {     MyCircularQueue* qt = (MyCircularQueue*)malloc (sizeof (MyCircularQueue));     if  (qt == NULL )         return  NULL ;     qt->data = (int *)malloc (sizeof (int ) * (k + 1 ));     qt->k = k;          qt->front = 0 ;     qt->tail = 0 ;     return  qt; } bool  myCircularQueueEnQueue (MyCircularQueue* obj, int  value)  {    assert(obj);     if  (myCircularQueueIsFull(obj))         return  false ;     obj->data[obj->tail] = value;               if  (obj->tail == obj->k)     {         obj->tail = 0 ;     }     else      {         obj->tail ++ ;     }          return  true ; } bool  myCircularQueueDeQueue (MyCircularQueue* obj)  {    assert(obj);     if  (myCircularQueueIsEmpty(obj))         return  false ;     if  (obj->front == obj->k)     {         obj->front = 0 ;     }     else      {         obj->front++;     }     return  true ; } int  myCircularQueueFront (MyCircularQueue* obj)  {    assert(obj);     if  (myCircularQueueIsEmpty(obj))         return  -1 ;     return  obj->data[obj->front]; } int  myCircularQueueRear (MyCircularQueue* obj)  {    assert(obj);     if  (myCircularQueueIsEmpty(obj))         return  -1 ;     if  (obj->tail == 0 )     {         return  obj->data[obj->k];     }     else      {         return  obj->data[obj->tail - 1 ];     }     return  -1 ; } bool  myCircularQueueIsEmpty (MyCircularQueue* obj)  {    return  obj->tail == obj->front; } bool  myCircularQueueIsFull (MyCircularQueue* obj)  {    if  ((obj->tail == obj->k) && obj->front == 0 )     {         return  true ;     }     else      {         return  obj->tail + 1  == obj->front;     } } void  myCircularQueueFree (MyCircularQueue* obj)  {    assert(obj);     free (obj->data);     free (obj);     return ; } 
结语 
刷完这3道题,有没有感觉数据结构的很多内容都是关系紧密的?🤩
只要我们把链表和顺序表的内容 搞得透彻,栈和队列这部分就没那么难了!
加油!