1. 队列结构实现栈结构
使用队列实现栈结构,核心思想为功能分割。第一种方法是使用两个队列,流程如下图所示:
压栈时,直接进入 Data 队列即可。出栈时,Data 队列每次仅保留队尾一个元素,其他元素全部进入 Help 队列中,这时返回给用户 Data 队列中仅有的元素值。之后 Data 队列的元素弹出,并将 Data 和 Help 的地址互换,重复执行上述操作。(永远对 Data 队列进行操作)
#include <iostream>
#include <queue>
using namespace std;
// 方法一,使用两个队列模拟栈
class MyStack {
public:
queue<int> Data;
queue<int> Help;
// 进入元素直接进入 Data 队列,删除元素只在 Data 队列仅有一个元素时才删除
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
Data.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
// 如果队列空
if(Data.empty()){
return -1;
}
// 将 Data 队列元素移至 Help 队列,Data 队列只剩下队尾元素即可
while(Data.size() != 1){
Help.push(Data.front());
Data.pop();
}
// 此时Data队列仅剩一个元素,将其弹出
int pop_number = Data.back();
Data.pop();
// 交换 Data 和 Help 的地址
swap(Data, Help);
return pop_number;
}
/** Get the top element. */
int top() {
return Data.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return Data.empty();
}
};
复杂度分析:
时间复杂度:压栈时,元素直接进入队列,O(1) 操作。出栈时,每次需要移动 n-1 个元素,故时间复杂度为 O(n)。获取栈顶元素和判断栈空均为 O(1) 操作。
空间复杂度:使用了两个队列,故空间复杂度为 O(n)。
方法二,使用一个队列模仿栈结构(https://leetcode-cn.com/problems/implement-stack-using-queues/solution/yong-dui-lie-shi-xian-zhan-by-leetcode-solution/)。 核心思想为,栈顶指针永远指向最新的元素。从此角度考虑,需要将新入队的元素始终保持队首位置。故在新元素入队前,记录此时队列中元素个数。在新元素入队后,新元素前面的所有元素全部出队而后重新入队,此时新元素在队首位置。
#include <iostream>
#include <queue>
using namespace std;
// 使用一个队列模拟栈
class MyStack {
public:
queue<int> Data;
// 一个队列模拟栈,每次入队之前,记录队中元素个数,然后这些元素先出队,让最新进入的元素到达队首
// 核心思想是:栈顶指针永远指向最新进入的元素
// Data 就是一个栈,只不过压栈特殊些
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
int count = Data.size(); // 获取队列中已有的元素个数
Data.push(x);
// count 个元素先出队,再入队
while(count != 0){
int temp = Data.front();
Data.pop();
Data.push(temp);
count--;
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int pop_number = Data.front();
Data.pop();
return pop_number;
}
/** Get the top element. */
int top() {
return Data.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return Data.empty();
}
};
复杂度分析:
时间复杂度:由于每次进入新元素时,需要移动 n-1 个元素,故入栈操作 O(n)。其他操作均为 O(1)。
空间复杂度: 使用了一个队列,故空间复杂度 O(n)。
2. 栈结构实现队列结构
使用栈实现队列结构,核心思想仍为功能分割。创建两个栈,push 栈和 pop 栈。用户的输入永远放入 push 栈,pop 栈永远只向用户输出。例如用户输入 1 2 3 4 5,那么将该数全部压入 push 栈,用户取数时将 push 栈内容全部压入 pop 栈,取数时即为 1 2 3 4 5。
输入直接压入 push 栈,输出则借助 pop 栈。但要注意两点:
1)push 栈元素在压入 pop 栈之前,一定要确保 pop 栈内无元素,是个空栈
2)每次 push 栈压入到 pop 栈时,一定要把所有元素一次性压完
// 剑指 Offer 09 用两个栈实现队列
class CQueue {
// s.empty() 如果栈为空返回true,否则返回false
// s.size() 返回栈中元素的个数
// s.pop() 删除栈顶元素但不返回其值
// s.top() 返回栈顶的元素,但不删除该元素
// s.push() 在栈顶压入新元素
// 核心思想:功能分割
stack<int> stack_push, stack_pop;
public:
CQueue() {
}
// push 栈只用来压数据
void appendTail(int value) {
stack_push.push(value);
}
// pop 栈用来弹数据
int deleteHead() {
// 如果 pop 栈空
if(stack_pop.empty()){
// 如果push栈也空,直接-1
if(stack_push.empty()){
return -1;
}else{ // 如果 push 栈不空,那把 push 栈的所有数据弹到 pop 栈
while(!stack_push.empty()){
stack_pop.push(stack_push.top());
stack_push.pop(); // 删除栈顶元素
}
}
}
// 此时 pop 栈一定不空,直接返回即可
int delete_value = stack_pop.top();
stack_pop.pop();
return delete_value;
}
};
时间复杂度:对于插入和删除操作,时间复杂度均为 O(1)。插入不多说,对于删除操作,虽然看起来是 O(n) 的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 pop 栈 一次」,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。
空间复杂度:O(n)。需要使用两个栈存储已有的元素。