当前位置:   article > 正文

栈和队列OJ(面试高频题 - 看完包!!!拿捏)

栈和队列OJ(面试高频题 - 看完包!!!拿捏)

目录

题目一:括号匹配问题(来源)

        题目描述

        题目思路及实现

题目二:用队列实现栈(来源)

        题目描述

        题目思路及实现

题目三:用栈实现队列(来源)

        题目描述

        题目思路及实现

题目四:设计循环队列(来源)

        题目描述

        题目思路及实现


题目一:括号匹配问题(来源)

        题目描述

        给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

        有效字符串需满足:

        1、左括号必须用相同类型的右括号闭合。

        2、左括号必须以正确的顺序闭合。

        3、每个右括号都有一个对应的相同类型的左括号。

        题目思路及实现

        该题是栈的典型应用,满足后进先出的规则(后入栈的前括号将优先与先出现的后括号相匹配)。

        遍历字符串,遇到前括号直接入栈。遇到后括号,判断该后括号与栈顶的前括号是否匹配(若此时栈为空,则字符串无效),若不匹配则字符串无效;若匹配则删除栈顶元素,继续遍历字符串,直到字符串遍历完毕。当字符串遍历完后,检测栈是否为空,若为空,则字符串有效,若不为空,说明有前括号未匹配,字符串无效。

  1. typedef char STDataType;
  2. typedef struct Stack
  3. {
  4. STDataType* a;
  5. int top;
  6. int capacity;
  7. }Stack;
  8. //初始化栈
  9. void StackInit(Stack* pst)
  10. {
  11. assert(pst);
  12. pst->a = (STDataType*)malloc(sizeof(STDataType)* 4);
  13. pst->top = 0;
  14. pst->capacity = 4;
  15. }
  16. //销毁栈
  17. void StackDestroy(Stack* pst)
  18. {
  19. assert(pst);
  20. free(pst->a);
  21. pst->a = NULL;
  22. pst->top = 0;
  23. pst->capacity = 0;
  24. }
  25. //入栈
  26. void StackPush(Stack* pst, STDataType x)
  27. {
  28. assert(pst);
  29. if (pst->top == pst->capacity)
  30. {
  31. STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity * 2);
  32. if (tmp == NULL)
  33. {
  34. printf("realloc fail\n");
  35. exit(-1);
  36. }
  37. pst->a = tmp;
  38. pst->capacity *= 2;
  39. }
  40. pst->a[pst->top] = x;
  41. pst->top++;
  42. }
  43. //检测栈是否为空
  44. bool StackEmpty(Stack* pst)
  45. {
  46. assert(pst);
  47. return pst->top == 0;
  48. }
  49. //出栈
  50. void StackPop(Stack* pst)
  51. {
  52. assert(pst);
  53. assert(!StackEmpty(pst));
  54. pst->top--;
  55. }
  56. //获取栈顶元素
  57. STDataType StackTop(Stack* pst)
  58. {
  59. assert(pst);
  60. assert(!StackEmpty(pst));
  61. return pst->a[pst->top - 1];
  62. }
  63. /*---以上代码是栈的基本功能实现,以下代码是题解主体部分---*/
  64. bool isValid(char * s)
  65. {
  66. // 初始化一个栈st,用于存储遇到的打开的括号
  67. Stack st;
  68. StackInit(&st);
  69. // 创建指向当前字符的指针cur
  70. char* cur = s;
  71. // 遍历输入字符串s中的每个字符
  72. while(*cur)
  73. {
  74. // 如果当前字符是打开的括号
  75. if(*cur == '(' || *cur == '{' || *cur == '[')
  76. {
  77. // 将其压入栈中
  78. StackPush(&st, *cur);
  79. // 移动到下一个字符
  80. cur++;
  81. }
  82. // 否则,如果当前字符是关闭的括号
  83. else
  84. {
  85. // 若栈为空,则说明没有对应的打开括号与之匹配,直接返回false
  86. if(StackEmpty(&st))
  87. {
  88. StackDestroy(&st);
  89. return false;
  90. }
  91. // 获取栈顶元素(即最近进入栈内的打开括号)
  92. char top = StackTop(&st);
  93. // 检查栈顶元素与当前关闭括号是否匹配
  94. if((top == '(' && *cur != ')') ||
  95. (top == '{' && *cur != '}') ||
  96. (top == '[' && *cur != ']'))
  97. {
  98. // 不匹配,则返回false
  99. StackDestroy(&st);
  100. return false;
  101. }
  102. else
  103. {
  104. // 匹配成功,弹出栈顶元素(即消耗一个括号对)
  105. StackPop(&st);
  106. // 移动到下一个字符
  107. cur++;
  108. }
  109. }
  110. }
  111. // 遍历结束后,若栈为空,则说明所有括号都已正确匹配,返回true;否则返回false
  112. bool ret = StackEmpty(&st);
  113. StackDestroy(&st);
  114. return ret;
  115. }

        此函数利用了栈这一数据结构的特性:对于任何有效的括号序列,当扫描到一个关闭括号时,栈顶的元素一定是与其相匹配的打开括号。遍历完字符串后,栈应为空,表示所有的打开括号都有相应的关闭括号与之匹配。 

题目二:用队列实现栈(来源)

        题目描述

        请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

        实现 MyStack 类:

        void push(int x) 将元素 x 压入栈顶。

        int pop() 移除并返回栈顶元素。

        int top() 返回栈顶元素。

        boolean empty() 如果栈是空的,返回 true ;否则,返回 false

        题目思路及实现

        使用两个队列,始终保持一个队列为空。                                                                                       当我们需要进行压栈操作时,将数据压入不为空的队列中(若两个都为空,则随便压入一个队列)。当需要进行出栈操作时,将不为空的队列中的数据导入空队列,仅留下一个数据,这时将这个数据返回并且删除即可。判断栈是否为空,即判断两个队列是否同时为空。

  1. typedef int QDataType;
  2. typedef struct QListNode
  3. {
  4. struct QListNode* next;
  5. QDataType data;
  6. }QListNode;
  7. typedef struct Queue
  8. {
  9. QListNode* head;
  10. QListNode* tail;
  11. }Queue;
  12. //初始化队列
  13. void QueueInit(Queue* pq)
  14. {
  15. assert(pq);
  16. pq->head = NULL;
  17. pq->tail = NULL;
  18. }
  19. //销毁队列
  20. void QueueDestroy(Queue* pq)
  21. {
  22. assert(pq);
  23. QListNode* cur = pq->head;
  24. while (cur)
  25. {
  26. QListNode* next = cur->next;
  27. free(cur);
  28. cur = next;
  29. }
  30. pq->head = NULL;
  31. pq->tail = NULL;
  32. }
  33. //队尾入队列
  34. void QueuePush(Queue* pq, QDataType x)
  35. {
  36. assert(pq);
  37. QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
  38. if (newnode == NULL)
  39. {
  40. printf("malloc fail\n");
  41. exit(-1);
  42. }
  43. newnode->data = x;
  44. newnode->next = NULL;
  45. if (pq->head == NULL)
  46. {
  47. pq->head = pq->tail = newnode;
  48. }
  49. else
  50. {
  51. pq->tail->next = newnode;
  52. pq->tail = newnode;
  53. }
  54. }
  55. //检测队列是否为空
  56. bool QueueEmpty(Queue* pq)
  57. {
  58. assert(pq);
  59. return pq->head == NULL;
  60. }
  61. //队头出队列
  62. void QueuePop(Queue* pq)
  63. {
  64. assert(pq);
  65. assert(!QueueEmpty(pq));
  66. if (pq->head->next == NULL)
  67. {
  68. free(pq->head);
  69. pq->head = NULL;
  70. pq->tail = NULL;
  71. }
  72. else
  73. {
  74. QListNode* next = pq->head->next;
  75. free(pq->head);
  76. pq->head = next;
  77. }
  78. }
  79. //获取队列头部元素
  80. QDataType QueueFront(Queue* pq)
  81. {
  82. assert(pq);
  83. assert(!QueueEmpty(pq));
  84. return pq->head->data;
  85. }
  86. //获取队列尾部元素
  87. QDataType QueueBack(Queue* pq)
  88. {
  89. assert(pq);
  90. assert(!QueueEmpty(pq));
  91. return pq->tail->data;
  92. }
  93. //获取队列中有效元素个数
  94. int QueueSize(Queue* pq)
  95. {
  96. assert(pq);
  97. QListNode* cur = pq->head;
  98. int count = 0;
  99. while (cur)
  100. {
  101. count++;
  102. cur = cur->next;
  103. }
  104. return count;
  105. }
  106. /*---以上代码是队列的基本功能实现,以下代码是题解主体部分---*/
  107. // 定义一个结构体MyStack,它包含两个队列q1和q2,用以模拟栈的功能
  108. typedef struct
  109. {
  110. Queue q1; // 第一个辅助队列
  111. Queue q2; // 第二个辅助队列
  112. } MyStack;
  113. // 创建并初始化一个MyStack类型的栈对象
  114. MyStack* myStackCreate()
  115. {
  116. MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); // 分配内存空间
  117. QueueInit(&pst->q1); // 初始化第一个队列
  118. QueueInit(&pst->q2); // 初始化第二个队列
  119. return pst; // 返回新创建的栈对象
  120. }
  121. // 将整数x压入栈顶
  122. void myStackPush(MyStack* obj, int x)
  123. {
  124. // 判断哪个队列非空,就将元素压入哪个队列
  125. if (!QueueEmpty(&obj->q1))
  126. {
  127. QueuePush(&obj->q1, x); // 如果q1非空,则将元素压入q1
  128. }
  129. else
  130. {
  131. QueuePush(&obj->q2, x); // 否则将元素压入q2
  132. }
  133. }
  134. // 弹出并返回栈顶元素
  135. int myStackPop(MyStack* obj)
  136. {
  137. Queue* pEmpty = &obj->q1; // 初始化一个指向空队列的指针
  138. Queue* pNoEmpty = &obj->q2; // 初始化一个指向非空队列的指针
  139. // 如果q1非空,则交换两个指针指向的队列
  140. if (!QueueEmpty(&obj->q1))
  141. {
  142. pEmpty = &obj->q2;
  143. pNoEmpty = &obj->q1;
  144. }
  145. // 把非空队列的所有元素依次转移到空队列,保持栈的后进先出性质
  146. while (QueueSize(pNoEmpty) > 1)
  147. {
  148. QueuePush(pEmpty, QueueFront(pNoEmpty)); // 将非空队列的第一个元素移至空队列末尾
  149. QueuePop(pNoEmpty); // 删除非空队列的第一个元素
  150. }
  151. // 获取并返回非空队列的最后一个元素(即栈顶元素)
  152. int front = QueueFront(pNoEmpty);
  153. QueuePop(pNoEmpty); // 删除已获取的栈顶元素
  154. return front;
  155. }
  156. // 返回栈顶元素但不弹出
  157. int myStackTop(MyStack* obj)
  158. {
  159. // 根据队列状态获取栈顶元素
  160. if (!QueueEmpty(&obj->q1))
  161. {
  162. return QueueBack(&obj->q1); // 若q1非空,则返回q1最后一个元素(栈顶元素)
  163. }
  164. else
  165. {
  166. return QueueBack(&obj->q2); // 否则返回q2最后一个元素(栈顶元素)
  167. }
  168. }
  169. // 检查栈是否为空
  170. bool myStackEmpty(MyStack* obj)
  171. {
  172. // 若两个队列均为空,则栈为空
  173. return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
  174. }
  175. // 销毁栈并释放内存
  176. void myStackFree(MyStack* obj)
  177. {
  178. QueueDestroy(&obj->q1); // 销毁并释放q1队列资源
  179. QueueDestroy(&obj->q2); // 销毁并释放q2队列资源
  180. free(obj); // 释放栈对象本身占用的内存
  181. }

        这段代码中,通过两个队列巧妙地实现了栈的功能。其中,压入操作总是把元素添加到非空队列的末尾,而弹出操作时,会先确保所有元素都在同一个队列内,并且按照栈的“后进先出”原则进行操作。

题目三:用栈实现队列(来源)

        题目描述

        请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

        实现 MyQueue 类:

        void push(int x) 将元素 x 推到队列的末尾

        int pop() 从队列的开头移除并返回元素

        int peek() 返回队列开头的元素

        boolean empty() 如果队列为空,返回 true ;否则,返回 false

        题目思路及实现

        使用两个栈,第一个栈只用于数据的输入,第二个栈只用于数据的输出。当需要输出数据,但第二个栈为空时,先将第一个栈中的数据一个一个导入到第二个栈,然后第二个栈再输出数据即可。

  1. typedef int STDataType;
  2. typedef struct Stack
  3. {
  4. STDataType* a;
  5. int top;
  6. int capacity;
  7. }Stack;
  8. //初始化栈
  9. void StackInit(Stack* pst)
  10. {
  11. assert(pst);
  12. pst->a = (STDataType*)malloc(sizeof(STDataType)* 4);
  13. pst->top = 0;
  14. pst->capacity = 4;
  15. }
  16. //销毁栈
  17. void StackDestroy(Stack* pst)
  18. {
  19. assert(pst);
  20. free(pst->a);
  21. pst->a = NULL;
  22. pst->top = 0;
  23. pst->capacity = 0;
  24. }
  25. //入栈
  26. void StackPush(Stack* pst, STDataType x)
  27. {
  28. assert(pst);
  29. if (pst->top == pst->capacity)
  30. {
  31. STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity * 2);
  32. if (tmp == NULL)
  33. {
  34. printf("realloc fail\n");
  35. exit(-1);
  36. }
  37. pst->a = tmp;
  38. pst->capacity *= 2;
  39. }
  40. pst->a[pst->top] = x;
  41. pst->top++;
  42. }
  43. //检测栈是否为空
  44. bool StackEmpty(Stack* pst)
  45. {
  46. assert(pst);
  47. return pst->top == 0;
  48. }
  49. //出栈
  50. void StackPop(Stack* pst)
  51. {
  52. assert(pst);
  53. assert(!StackEmpty(pst));
  54. pst->top--;
  55. }
  56. //获取栈顶元素
  57. STDataType StackTop(Stack* pst)
  58. {
  59. assert(pst);
  60. assert(!StackEmpty(pst));
  61. return pst->a[pst->top - 1];
  62. }
  63. //获取栈中有效元素个数
  64. int StackSize(Stack* pst)
  65. {
  66. assert(pst);
  67. return pst->top;
  68. }
  69. /*---以上代码是栈的基本功能实现,以下代码是题解主体部分---*/
  70. // 定义一个名为MyQueue的结构体,该结构体包含两个栈:pushST(用于插入元素)和popST(用于取出元素),用来模拟队列的功能
  71. typedef struct
  72. {
  73. Stack pushST; // 用于插入元素的栈
  74. Stack popST; // 用于取出元素的栈
  75. } MyQueue;
  76. // 创建并初始化一个MyQueue类型的队列对象
  77. MyQueue* myQueueCreate()
  78. {
  79. MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); // 动态分配内存
  80. StackInit(&obj->pushST); // 初始化插入元素的栈
  81. StackInit(&obj->popST); // 初始化取出元素的栈
  82. return obj; // 返回新创建的队列对象
  83. }
  84. // 将整数x推入队列尾部(实际操作是将其压入pushST栈顶)
  85. void myQueuePush(MyQueue* obj, int x)
  86. {
  87. StackPush(&obj->pushST, x); // 将元素x压入pushST栈顶
  88. }
  89. // 查看队列头部元素但不删除
  90. int myQueuePeek(MyQueue* obj)
  91. {
  92. // 如果popST栈为空,则将pushST栈中的所有元素依次弹出并压入popST栈
  93. if(StackEmpty(&obj->popST))
  94. {
  95. while(!StackEmpty(&obj->pushST))
  96. {
  97. StackPush(&obj->popST, StackTop(&obj->pushST)); // 将pushST栈顶元素移动到popST栈顶
  98. StackPop(&obj->pushST); // 弹出pushST栈顶元素
  99. }
  100. }
  101. // 返回popST栈顶元素(此时为队列头部元素)
  102. return StackTop(&obj->popST);
  103. }
  104. // 从队列头部弹出并返回元素
  105. int myQueuePop(MyQueue* obj)
  106. {
  107. int top = myQueuePeek(obj); // 获取队列头部元素
  108. StackPop(&obj->popST); // 弹出popST栈顶元素(队列头部元素)
  109. return top; // 返回已弹出的头部元素
  110. }
  111. // 判断队列是否为空
  112. bool myQueueEmpty(MyQueue* obj)
  113. {
  114. // 当插入元素的pushST栈和取出元素的popST栈都为空时,队列为空
  115. return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
  116. }
  117. // 销毁队列并释放内存
  118. void myQueueFree(MyQueue* obj)
  119. {
  120. StackDestroy(&obj->pushST); // 销毁并释放pushST栈的内存
  121. StackDestroy(&obj->popST); // 销毁并释放popST栈的内存
  122. free(obj); // 释放MyQueue对象本身的内存
  123. }

        这段代码通过两个栈来模拟队列功能,当插入元素时,将元素压入pushST栈。当需要查看或删除队列头部元素时,首先确保popST栈非空,若为空则将pushST栈中的元素全部转移至popST栈,从而保证popST栈顶元素始终为队列头部元素。这样,通过两个栈的配合,可以实现队列的先进先出(FIFO)特性。 

题目四:设计循环队列(来源)

        题目描述

        设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

        循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

        你的实现应该支持如下操作:

        MyCircularQueue(k): 构造器,设置队列长度为 k 。

        Front: 从队首获取元素。如果队列为空,返回 -1 。

        Rear: 获取队尾元素。如果队列为空,返回 -1 。

        enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。

        deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。

        isEmpty(): 检查循环队列是否为空。

        isFull(): 检查循环队列是否已满。

 

        题目思路及实现

        在环形队列中,队列为空时,队头队尾指向同一个位置。当队列不为空时,队头指向插入的第一个数据,队尾指向最后一个数据的下一个位置。

        当tail+1等于front时,说明环形队列已满。

        注意:环形队列的队尾不能像常规队列中队尾一样指向最后一个数据,如果这样的话,我们将不能区别环形队列的状态是空还是满,因为此时队头和队尾都指向同一个位置。这就意味着,我们必须留出一个空间,这个空间不能存放数据,这样我们才能很好的区别环形队列的状态是空还是满。

        如果用一个数组来实现这个环形队列的话,上面这三种状态就对应于以下三种状态:

        可以看出,此时这个数组和环形完全扯不上关系,这其实很简单,我们只需注意判断两个地方:

        1.当指针指向整个数组的后方的时候,让该指针重新指向数组的第一个元素。

        2.当指针指向整个数组的前方的时候,让该指针直接指向数组最后一个有效元素的后面。

  1. // 定义一个循环队列结构体MyCircularQueue
  2. typedef struct
  3. {
  4. int* a; // 存储队列元素的数组
  5. int k; // 队列的最大容量
  6. int front; // 队列的头部索引
  7. int tail; // 队列的尾部索引
  8. } MyCircularQueue;
  9. // 创建一个容量为k的循环队列
  10. MyCircularQueue* myCircularQueueCreate(int k)
  11. {
  12. MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
  13. obj->a = (int*)malloc(sizeof(int) * (k + 1)); // 为元素数组分配内存,预留一个额外位置便于循环
  14. obj->front = 0; // 初始化头部索引
  15. obj->tail = 0; // 初始化尾部索引
  16. obj->k = k; // 设置队列容量
  17. return obj;
  18. }
  19. // 判断循环队列是否为空
  20. bool myCircularQueueIsEmpty(MyCircularQueue* obj)
  21. {
  22. return obj->front == obj->tail;
  23. }
  24. // 判断循环队列是否已满
  25. bool myCircularQueueIsFull(MyCircularQueue* obj)
  26. {
  27. int tailNext = obj->tail + 1; // 计算下一个可能的尾部索引
  28. if (tailNext == obj->k + 1)
  29. {
  30. // 处理索引溢出,实现循环
  31. tailNext = 0;
  32. }
  33. return tailNext == obj->front; // 如果计算后的尾部索引等于头部索引,则队列已满
  34. }
  35. // 往循环队列中入队一个值
  36. bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
  37. {
  38. if (myCircularQueueIsFull(obj))
  39. {
  40. return false; // 队列已满,无法入队
  41. }
  42. else
  43. {
  44. obj->a[obj->tail] = value; // 在尾部索引处插入元素
  45. obj->tail++; // 更新尾部索引
  46. if (obj->tail == obj->k + 1)
  47. {
  48. obj->tail = 0; // 处理索引溢出,实现循环
  49. }
  50. return true; // 入队成功
  51. }
  52. }
  53. // 从循环队列中出队一个值
  54. bool myCircularQueueDeQueue(MyCircularQueue* obj)
  55. {
  56. if (myCircularQueueIsEmpty(obj))
  57. {
  58. return false; // 队列为空,无法出队
  59. }
  60. else
  61. {
  62. obj->front++; // 更新头部索引
  63. if (obj->front == obj->k + 1)
  64. {
  65. obj->front = 0; // 处理索引溢出,实现循环
  66. }
  67. return true; // 出队成功
  68. }
  69. }
  70. // 获取循环队列的头部元素
  71. int myCircularQueueFront(MyCircularQueue* obj)
  72. {
  73. if (myCircularQueueIsEmpty(obj))
  74. {
  75. return -1; // 队列为空,无头部元素
  76. }
  77. else
  78. {
  79. return obj->a[obj->front]; // 返回头部索引处的元素
  80. }
  81. }
  82. // 获取循环队列的尾部元素
  83. int myCircularQueueRear(MyCircularQueue* obj)
  84. {
  85. if (myCircularQueueIsEmpty(obj))
  86. {
  87. return -1; // 队列为空,无尾部元素
  88. }
  89. else
  90. {
  91. int tailPrev = obj->tail - 1; // 计算上一个可能的尾部索引
  92. if (tailPrev == -1)
  93. {
  94. // 处理索引负值,实现循环
  95. tailPrev = obj->k;
  96. }
  97. return obj->a[tailPrev]; // 返回尾部索引前一个位置的元素
  98. }
  99. }
  100. // 释放循环队列所占内存
  101. void myCircularQueueFree(MyCircularQueue* obj)
  102. {
  103. free(obj->a); // 释放元素数组内存
  104. free(obj); // 释放MyCircularQueue结构体内存
  105. }

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号