在之前的博客中,我们学习了栈与队列的基本内容,并且实现了栈与队列。今天我们进行刷题训练,走进栈与队列的世界中去感受一番!!!
括号匹配问题
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
OJ链接 【leetcode 20.有效的括号】
这道题我们先做分析:满足的三大条件总结:类型匹配、顺序匹配、数量匹配。要满足这三个条件才可以返回true,反之则返回false。
最左边的括号要与最右边的括号进行匹配,这里我们就可以使用栈先进后出的特性来完成。当我们遇到一个左括号时,让其进栈进行储存。反之遇到右括号时,让栈中元素进行出栈进行匹配。
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef char STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* ps); void STDestroy(ST* ps); void STPush(ST* ps, STDataType x); void STPop(ST* ps); int STSize(ST* ps); bool STEmpty(ST* ps); STDataType STTop(ST* ps); void STInit(ST* ps) { assert(ps); ps->a = NULL; ps->capacity = 0; ps->top = 0; } void STDestroy(ST* ps) { assert(ps); free(ps->a); ps->a = NULL; ps->top = ps->capacity = 0; } void STPush(ST* ps, STDataType x) { assert(ps); if (ps->top == ps->capacity) { int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity); if (tmp == NULL) { perror("realloc"); exit(-1); } ps->a = tmp; ps->capacity = newCapacity; } ps->a[ps->top] = x; ps->top++; } void STPop(ST* ps) { assert(ps); assert(ps->top > 0); --ps->top; } int STSize(ST* ps) { assert(ps); return ps->top; } bool STEmpty(ST* ps) { assert(ps); return ps->top == 0; } STDataType STTop(ST* ps) { assert(ps); assert(ps->top > 0); return ps->a[ps->top - 1]; } bool isValid(char * s){ ST st; STInit(&st); char topval; while(*s) { if(*s == '('||*s == '['||*s == '{') { STPush(&st, *s); } else { if(STEmpty(&st)) { STDestroy(&st); return false; } topval = STTop(&st); STPop(&st); if((*s == ']' && topval != '[') || (*s == ')' && topval != '(') || (*s == '}' && topval != '{')) { STDestroy(&st); return false; } } s++; } bool ret = STEmpty(&st); STDestroy(&st); return ret; }
leetcode这道题中,对C语言非常不友好,如果想使用栈数据结构就必须自己写接口,我们可以使用博主之前博客中实现栈的接口复制粘贴上即可完成。
bool ret = STEmpty(&st); STDestroy(&st); return ret;
最后三句代码,是为了验证括号数量匹配的问题,如果循环完成后栈中还有括号元素,证明示例中左右括号不匹配数量有问题,必须返回false。
使用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的基本操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
OJ链接 【leetcode255.用队列实现栈】
分析:
错误想法:假设第一个队列中有四个数据1、2、3、4,队列的特点是先进先出。我们一般的想法为把内容反转一下,再先进先出即可。但是这个想法是错误的,当我们反转后取出4后假设再push加入5和6两个数据。前面的数据会如同栈一样后进先出,但是到5和6时又会成为队列出5和6。所以这个方法是不可行的。
正确想法:
当我们如果pop释放完4后继续想再次push录入数据,我们应该录入到哪里?
我们应该录入到不为空的队列中,然后继续进行上面的操作即可完成使用队列实现栈的特性
总结:不为空的队列进行存入数据,为空的队列进行导入数据!!!
实现如下:
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<stdbool.h> typedef int QDataType; typedef struct QueueNode { struct QueueNode* next; QDataType data; }QNode; typedef struct Queue { QNode* head; QNode* tail; int size; }Que; void QueueInit(Que* pq); void QueueDestroy(Que* pq); void QueuePush(Que* pq, QDataType x); void QueuePop(Que* pq); QDataType QueueFront(Que* pq); QDataType QueueBack(Que* pq); bool QueueEmpty(Que* pq); int QueueSize(Que* pq); void QueueInit(Que* pq) { pq->head = pq->tail = NULL; pq->size = 0; } void QueueDestroy(Que* pq) { assert(pq); QNode* cur = pq->head; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pq->head = pq->tail = NULL; pq->size = 0; } int QueueSize(Que* pq) { assert(pq); return pq->size; } void QueuePush(Que* pq, QDataType x) { assert(pq); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc"); exit(-1); } newnode->data = x; newnode->next = NULL; if (pq->tail == NULL) { pq->head = pq->tail = newnode; } else { pq->tail->next = newnode; pq->tail = newnode; } pq->size++; } void QueuePop(Que* pq) { assert(pq); assert(!QueueEmpty(pq)); if (pq->head->next == NULL) { free(pq->head); pq->head = pq->tail = NULL; } else { QNode* next = pq->head->next; free(pq->head); pq->head = next; } pq->size--; } QDataType QueueFront(Que* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->head->data; } QDataType QueueBack(Que* pq) { assert(pq); assert(!QueueEmpty(pq)); return pq->tail->data; } bool QueueEmpty(Que* pq) { assert(pq); return pq->tail == NULL; } typedef struct { Que q1; Que q2; } MyStack; MyStack* myStackCreate() { MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); QueueInit(&pst->q1); QueueInit(&pst->q2); return pst; } void myStackPush(MyStack* obj, int x) { if(!QueueEmpty(&obj->q1)) { QueuePush(&obj->q1, x); } else { QueuePush(&obj->q2, x); } } int myStackPop(MyStack* obj) { Que* empty = &obj->q1; Que* noempty = &obj->q2; if(!QueueEmpty(&obj->q1)) { noempty = &obj->q1; empty = &obj->q2; } //前size-1个空队列 while(QueueSize(noempty)>1) { QueuePush(empty,QueueFront(noempty)); QueuePop(noempty); } int top = QueueFront(noempty); QueuePop(noempty); 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); free(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); */
这道题C语言也没有任何链表接口,我们得去写完整。但是这些类型的题目都是低耦合的,只要接口不变,内容怎么改变都无所谓,我们继续复制粘贴队列的实现。
但是这这题需要两个队列,所以嵌套关系比较复杂,下面提供一张关系图片进行参考:
用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(、、、):
pushpoppeekempty
实现 类:MyQueue
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回 ;否则,返回true
false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有 , , , 和 操作是合法的。
push to top
peek/pop from top
size
is empty
- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
OJ链接【leetcode232.用栈实现队列】
分析:用两个栈实现队列先进先出的特点。
与用队列实现栈相似,但有所不同的是将push添加的数据从一个栈中转入另一个栈(遵循后进先出原则)就成为逆序,所以当想要pop时,只需将存入数据的栈中元素转入到空栈中去,进行依次释放即可。
当未释放完却想要继续pop添加元素时,我们添加到最开始增加元素的栈中去等待另一个栈中元素全部pop完后继续进行上述方法,将添加新元素的栈中元素按照栈先进后出原则转入另一个栈进行释放即可。
总结:将两个栈分别看作pushst(添加栈)popst(释放栈)。如果想要添加元素就放入pushst中去,想要释放元素先要将pushst中的元素放入popst中去,再进行释放。
注意:如果中途有push添加元素,只有将popst中的元素释放为空后才可以继续从pushst中给予元素给popst继续释放!!!
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* ps); void STDestroy(ST* ps); void STPush(ST* ps, STDataType x); void STPop(ST* ps); int STSize(ST* ps); bool STEmpty(ST* ps); STDataType STTop(ST* ps); void STInit(ST* ps) { assert(ps); ps->a = NULL; ps->capacity = 0; ps->top = 0; } void STDestroy(ST* ps) { assert(ps); free(ps->a); ps->a = NULL; ps->top = ps->capacity = 0; } void STPush(ST* ps, STDataType x) { assert(ps); if (ps->top == ps->capacity) { int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity); if (tmp == NULL) { perror("realloc"); exit(-1); } ps->a = tmp; ps->capacity = newCapacity; } ps->a[ps->top] = x; ps->top++; } void STPop(ST* ps) { assert(ps); assert(ps->top > 0); --ps->top; } int STSize(ST* ps) { assert(ps); return ps->top; } bool STEmpty(ST* ps) { assert(ps); return ps->top == 0; } STDataType STTop(ST* ps) { assert(ps); assert(ps->top > 0); return ps->a[ps->top - 1]; } typedef struct { ST pushst; ST popst; } MyQueue; MyQueue* myQueueCreate() { MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); STInit(&obj->pushst); STInit(&obj->popst); return obj; } void myQueuePush(MyQueue* obj, int x) { STPush(&obj->pushst, x); } int myQueuePeek(MyQueue* obj) { if(STEmpty(&obj->popst)) { while(!STEmpty(&obj->pushst)) { STPush(&obj->popst, STTop(&obj->pushst)); STPop(&obj->pushst); } } return STTop(&obj->popst); } int myQueuePop(MyQueue* obj) { int front = myQueuePeek(obj); STPop(&obj->popst); return front; } bool myQueueEmpty(MyQueue* obj) { return STEmpty(&obj->pushst) && STEmpty(&obj->popst); } void myQueueFree(MyQueue* obj) { STDestroy(&obj->pushst); STDestroy(&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); */
此题依旧没有接口,需要我们自己添加完成,但是相同的方法我们就不再过多强调,题不是很难,但是层层嵌套还是比较绕的。
设计循环队列
设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k)
: 构造器,设置队列长度为 k 。Front
: 从队首获取元素。如果队列为空,返回 -1 。Rear
: 获取队尾元素。如果队列为空,返回 -1 。enQueue(value)
: 向循环队列插入一个元素。如果成功插入则返回真。deQueue()
: 从循环队列中删除一个元素。如果成功删除则返回真。isEmpty()
: 检查循环队列是否为空。isFull()
: 检查循环队列是否已满。
提示:
- 所有的值都在 0 至 1000 的范围内;
- 操作数将在 1 至 1000 的范围内;
- 请不要使用内置的队列库。
OJ链表【leetcode 622.设计循环队列】
那我们用数组还是用链表来实现循环队列好呢?
无论使用链表还是数组,当内容为空时两个指针指向的位置都在最开始,而每增加一个数据Rear都往后走一个位置(Rear指向的是最后一个内容的下一个节点处),所以当我们取对尾数据时,假设使用链表就非常的不好寻找(除非使用双向链表或者别的方法进行标记),但是数组却非常好寻找队尾的元素内容。所以我们使用数组来实现循环列表。(但是链表非常好进行循环找头)各有利弊!!!
第二个问题:当循环队列为空和全满时,Rear指针与Front指针全部指向同一个位置,我们应该如何区分?
解法一:我们可以设置一个size来记录数据个数。
解法二:我们可以在设置数组大小时多开一个空间不去使用 front == Rear 就是空,Rear的下一个为Front即为满。
使用数组时,我们需要注意当rear指向最后一个时,在循环队列前面还有空余的情况下,如何让rear指针回到最初呢?
方法一:我们可以使用if判断,如果rear指向最后时,我们可以让其直接等于0.
方法二:比较巧妙,我们可以用 obj->rear %= (obj->k+1);进行操作。
同理front方法一样。
typedef struct { int* a; int front; int rear; int k; } MyCircularQueue; MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue)); obj->a = (int*)malloc(sizeof(int)*(k+1)); obj->front = obj->rear = 0; obj->k = k; return obj; } bool myCircularQueueIsEmpty(MyCircularQueue* obj) { return obj->front == obj->rear; } bool myCircularQueueIsFull(MyCircularQueue* obj) { return (obj->rear + 1) % (obj->k + 1) == obj->front; } bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { if(myCircularQueueIsFull(obj)) { return false; } obj->a[obj->rear] = value; obj->rear++; obj->rear %= (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; } else { return obj->a[obj->front]; } } int myCircularQueueRear(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) { return -1; } else { return obj->a[(obj->rear + obj->k) % (obj->k + 1)]; } } void myCircularQueueFree(MyCircularQueue* obj) { free(obj->a); free(obj); } /** * Your MyCircularQueue struct will be instantiated and called as such: * MyCircularQueue* obj = myCircularQueueCreate(k); * bool param_1 = myCircularQueueEnQueue(obj, value); * bool param_2 = myCircularQueueDeQueue(obj); * int param_3 = myCircularQueueFront(obj); * int param_4 = myCircularQueueRear(obj); * bool param_5 = myCircularQueueIsEmpty(obj); * bool param_6 = myCircularQueueIsFull(obj); * myCircularQueueFree(obj); */
在函数myCircularQueueRear中,我们需要得到rear前一个的数组内容。一般情况都非常好表示,但有一种情况需要我们取斟酌。
当rear已经指向第一个时,怎样才能返回元素4呢?
方法一:当rear为0时,我们直接可以让其返回k下标的内容。(比较低级)
方法二: return obj->a[(obj->rear + obj->k) % (obj->k + 1)];我们可以使用算数巧妙地表示最后末尾地元素。
这道题的特殊情况比较多情况比较复杂,写代码时我们应该将问题考虑周全!!!
以上是本次OJ题目分享全部内容,感谢大家观看,一键三连是对博主最大的支持!!!谢谢❤️