前置知识
数据结构:栈和队队列 理解(B站搜:王道考研-数据结构)
栈和队列的基本函数的代码能敲熟练
(此部分内容今后新开一个专栏:数据结构)
题目内容
力扣题目链接: lhttps://leetcode.cn/problems/implement-stack-using-queues/description/
请你仅使用两个队列实现一个后入先出(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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
进阶:你能否仅用一个队列来实现栈。
思路
题目进阶要求用一个队列,但实际上这比用两个队列实现更好想到。
在草稿纸上画数两条平行线作为一个队列,模拟出栈先进后出的操作,
然后就发现出栈时要弹出的元素为队列最后进入的元素,这时做如下
操作:将队列中最后一个元素保留,其它元素重新进队,然后最后一
个元素就到了队列的最前方。此时再执行一次出队操作,就相当于
将目标元素从栈中弹出了。
再好一点的理解:把队列抽象成一个只有一个出口的圈,圈里面的元素
一直在转,想让谁出去就在转到它那的时候停一下。
上面的思路理解了,两个队列实现自己思考吧。
代码
typedef struct queue{
int *elements;
int front;
int rear;
int capacity;
} Queue;
Queue *creatQueue(int k) {
Queue *obj = (Queue*)malloc(sizeof(Queue));
obj->front = obj->rear = 0;
obj->capacity = k;
obj->elements = (int*)malloc(k * sizeof(int));
return obj;
}
void enQueue(Queue *obj, int x) {
obj->elements[obj->rear] = x;
obj->rear = (obj->rear + 1) % obj->capacity;
return ;
}
int deQueue(Queue *obj) {
int x = obj->elements[obj->front];
obj->front = (obj->front + 1) % obj->capacity;
return x;
}
// 这个查看头元素函数好像没有用到
int peekQueue(Queue *obj) {
return obj->elements[obj->front];
}
bool isEmptyQueue(Queue *obj) {
return obj->front == obj->rear;
}
void freeQueue(Queue *obj) {
free(obj->elements);
obj->elements = NULL;
free(obj);
obj = NULL;
}
typedef struct {
Queue *queue1, *queue2;
} MyStack;
MyStack* myStackCreate() {
MyStack *obj = (MyStack*)malloc(sizeof(MyStack));
obj->queue1 = creatQueue(20);
obj->queue2 = creatQueue(20);
return obj;
}
void myStackPush(MyStack* obj, int x) {
// x进入非空队列
enQueue(isEmptyQueue(obj->queue1) ? obj->queue2 : obj->queue1, x);
}
int myStackPop(MyStack* obj) {
if (isEmptyQueue(obj->queue1)) {
// 当执行到队列2中只剩下一个元素时结束
while ((obj->queue2->rear-1) % obj->queue2->capacity != obj->queue2->front) {
// 将队列2中元素转移到队列1中(操作后元素的相对位置和要出队的元素都不变,只是单纯的左手倒右手)
enQueue(obj->queue1, deQueue(obj->queue2));
}
// 此时队列2的最后一个元素出队
return deQueue(obj->queue2);
} else { //else中代码逻辑同上
while ((obj->queue1->rear-1) % obj->queue1->capacity != obj->queue1->front) {
enQueue(obj->queue2, deQueue(obj->queue1));
}
return deQueue(obj->queue1);
}
}
int myStackTop(MyStack* obj) {
// 返回非空队列中的最后一个元素的值,需计算rear的前一位
if (isEmptyQueue(obj->queue1)) {
return obj->queue2->elements[(obj->queue2->rear-1) % obj->queue2->capacity];
}
return obj->queue1->elements[(obj->queue1->rear-1) % obj->queue1->capacity];
}
bool myStackEmpty(MyStack* obj) {
return isEmptyQueue(obj->queue1) && isEmptyQueue(obj->queue2);
}
void myStackFree(MyStack* obj) {
freeQueue(obj->queue1);
freeQueue(obj->queue2);
}
/**
1. Your MyStack struct will be instantiated and called as such:
2. MyStack* obj = myStackCreate();
3. myStackPush(obj, x);
4. int param_2 = myStackPop(obj);
5. int param_3 = myStackTop(obj);
6. bool param_4 = myStackEmpty(obj);
7. myStackFree(obj);
*/
总结
本题目
用时1h45min,30min写队列的代码,15min思考如何解题,35min完成
栈的代码,然后运行通过,提交卡到9/16案例,之后一直找问题。
知识点
1.与力扣官方解答相比,初始化首尾=-1。
在队列实现中,将队列的head(或称为front)和rear(或称为back)初始化为-1是一种常见的做法,特别是在使用数组实现循环队列的情况下。这种初始化方式的主要目的是表示队列为空。当队列为空时,head和rear都指向同一个无效的位置,即-1,这使得检查队列是否为空变得简单直接,只需要比较这两个指针是否相等即可。
下面是两种队列初始化为-1的不同点以及它们之间的差异:
第一个实现:
在这个实现中,head和rear被初始化为-1。这种方法的好处是,当队列为空时,head和rear相等,都为-1,这样可以很容易地通过比较这两个指针来判断队列是否为空。
第二个实现:
在这个实现中,front和rear都被初始化为0,而容量为k的队列在空的状态下,front和rear相等,均为0。这意味着队列一开始是“准备好”的状态,即它有空间存放第一个元素。当队列为空时,front和rear都指向队列的第一个位置。
这两种方法在功能上是等价的,都是为了方便判断队列是否为空。然而,在具体实现细节上有所不同:
初始化状态:在第一种实现中,队列初始化为-1表示队列为空;而在第二种实现中,初始化为0同样表示队列为空。
入队操作:第一种实现中,在入队之前需要检查是否已经是空队列,并且第一次入队后会把head设为0。而第二种实现则不需要额外检查空队列,直接进行入队操作。
出队操作:在第一种实现中,如果队列为空,head和rear都将保持为-1。在第二种实现中,如果队列出队后为空,front和rear都设置为0。
队列遍历:在第二种实现中,由于front和rear都从0开始,所以在遍历队列时可能需要更多的条件判断以确保不会访问到无效索引。
总的来说,初始化为-1的方法在判断队列是否为空时更为直观,而初始化为0的方法在处理队列的边界情况时可能稍微简便一些。不过这些差异主要是实现上的细节不同,并不影响队列的基本功能。
2.free函数使用细则
请注意,free() 不重置指针值为 NULL;这是程序员的责任。在 free() 调用之后,指针仍然指向之前分配的内存地址,因此将其设置为 NULL 是一个好习惯。
本题代码如下
void freeQueue(Queue *obj) {
free(obj->elements);
obj->elements = NULL;
free(obj);
obj = NULL;
}
小技巧
while ((obj->queue2->rear-1) % obj->queue2->capacity != obj->queue2->front) {
// 将队列2中元素转移到队列1中(操作后元素的相对位置和要出队的元素都不变,只是单纯的左手倒右手)
enQueue(obj->queue1, deQueue(obj->queue2));
}
两个函数嵌套而不是两句分写,还要添int变量x。
2.设置最大容量20而不是题目说的100,可以由大到小测试出来。
具体视频说明
做题感受
一口气写到代码运行,运行不过,心力就耗尽了,看不下去代码了。
一道简单题目(官方说的),真正用时不止2.5h,耗了时间,但没有像
做一道数学题那样一直反复的调整思路的快感。
应该是刷题太少,目前阶段还是在学习使用数据结构,算法的方面微乎其微,看看再刷20题,什么感受。