算法小白开始刷题,本文包含了自己的思考过程,所以内容可能比较冗余,如需思路,可直接看文章后面的 优化解法。
题目链接:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
题目
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:
[null,null,3,-1]
示例 2:
输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:
[null,-1,null,null,5,2]
提示:
- 1 <= values <= 10000
- 最多会对 appendTail、deleteHead 进行 10000 次调用
思路:
此问题使用堆栈,可以定义两个栈,一个栈 stack 用于存储队列元素,另一个 stackTemp 作为删除队列元素时要用到的临时栈。
“队列尾部插入整数” 不需要返回结果,直接使用 stack 的基本 push 操作即可。
“队列头部输出整数” 时,由于 stack 是先进后出的容器,所以需要通过将 stack 中的元素注意 pop 出来并 push 进 stackTemp 中,使得队列头部元素位于 stackTemp 的栈顶,然后输出元素并将 stackTemp 中的剩余元素 pop 出来 push 进 stack 中。
代码:
class CQueue {
stack<int> stack, stackTemp;
public:
CQueue() {
while(!stack.empty()) {
stack.pop();
}
while(!stackTemp.empty()) {
stackTemp.pop();
}
}
void appendTail(int value) {
stack.push(value);
return;
}
int deleteHead() {
if(stack.empty())
return -1;
else {
while(!stack.empty()) {
int temp;
temp = stack.top();
stackTemp.push(temp);
stack.pop();
}
int result;
result = stackTemp.top();
stackTemp.pop();
while(!stackTemp.empty()) {
int temp;
temp = stackTemp.top();
stack.push(temp);
stackTemp.pop();
}
return result;
}
}
};
成绩:
附
如上图所示,在 LeetCode 中提交的程序的运行时间和内存消耗会有所浮动。
程序优化
step 1
由于成员函数 CQueue() 只会在程序运行开始时执行一次,这是 stack 、stackTemp 两个栈本来就是空栈,所以不需要判断其是否为空并 push 栈中的所有元素。
另外,在删除元素时,使用到的临时变量 temp 可以直接在函数中定义,而不需要在两个 while 循环中定义两次。
修改结果如下:
class CQueue {
stack<int> stack, stackTemp;
public:
CQueue() {
}
void appendTail(int value) {
stack.push(value);
return;
}
int deleteHead() {
if(stack.empty())
return -1;
else {
int temp;
while(!stack.empty()) {
temp = stack.top();
stackTemp.push(temp);
stack.pop();
}
int result;
result = stackTemp.top();
stackTemp.pop();
while(!stackTemp.empty()) {
temp = stackTemp.top();
stack.push(temp);
stackTemp.pop();
}
return result;
}
}
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
时间空间复杂度没有什么改善。
step 2
在删除队列元素时,使用的是将 stack 中的所有元素均 push 到 stackTemp 中,再删除 stackTemp 的栈顶元素,但其实 stack 的栈底元素不需要 push 进 stackTemp 中,可以直接 pop 掉,代码如下:
int deleteHead() {
if(stack.empty())
return -1;
else {
int temp;
while(stack.size() > 1) {
temp = stack.top();
stackTemp.push(temp);
stack.pop();
}
int result;
result = stack.top();
stack.pop();
while(!stackTemp.empty()) {
temp = stackTemp.top();
stack.push(temp);
stackTemp.pop();
}
return result;
}
时间空间复杂度仍没有改善。
优化解法
本题比较耗时的就是在删除元素的时候。
官方题解:
删除元素
- 如果 stackTemp 为空,则将 stack 里的所有元素弹出插入到 stackTemp 里。
- 如果 stackTemp 仍为空,则返回 -1,否则从 stackTemp 弹出一个元素并返回。
代码
class CQueue {
stack<int> stack, stackTemp;
public:
CQueue() {
while(!stack.empty()) {
stack.pop();
}
while(!stackTemp.empty()) {
stackTemp.pop();
}
}
void appendTail(int value) {
stack.push(value);
return;
}
int deleteHead() {
if(stackTemp.empty()) {
while(!stack.empty()) {
stackTemp.push(stack.top());
stack.pop();
}
}
if(stackTemp.empty())
return -1;
else {
int result;
result = stackTemp.top();
stackTemp.pop();
return result;
}
}
};
成绩
复杂度分析
- 时间复杂度:对于插入和删除操作,时间复杂度均为 O ( 1 ) O(1) O(1) 。插入不用多说,对于删除操作,虽然看起来是 O ( n ) O(n) O(n) 的时间复杂度,但是仔细考虑下每个元素只会 “至多被插入和弹出 stackTemp 一次” ,因此均摊下来每个元素被删除的时间复杂度仍为 O ( 1 ) O(1) O(1)。
- 空间复杂度: O ( n ) O(n) O(n) 。需要使用两个栈存储已有的元素。
附
- CQueue 函数中的 stack 和 stackTemp 的初始化步骤还是应该加上。
- 官方题解中的思路有点类似于缓冲区,新插入的元素可以先暂存在 stack 中,等到 stackTemp 中的元素全部 pop 之后(类似于系统资源不再被使用,已经被释放之后)再将 stack 中的元素 push 进 stackTemp 中。