这道力扣题目看似简单,实则暗藏玄机。题目编号232要求我们仅使用栈(stack)这种数据结构来实现队列(queue)的所有基本操作。作为数据结构基础题,它经常出现在大厂面试的第一轮技术面中,用来考察候选人对基础数据结构的理解深度。
队列是FIFO(先进先出)结构,而栈是LIFO(后进先出)结构,两者特性完全相反。用栈实现队列,相当于要用"反向操作"的特性来模拟"正向操作"的行为。在实际工程中,这种数据结构转换的思想也经常用于系统设计,比如用两个数据库表实现消息队列的消费顺序控制。
实现这个功能的核心在于使用两个栈:一个输入栈(inStack)负责处理入队操作,一个输出栈(outStack)负责处理出队和查看队首元素的操作。当需要出队时,如果输出栈为空,就将输入栈的所有元素依次弹出并压入输出栈,这样输出栈的栈顶元素就是队列的队首元素。
这种设计的时间复杂度分析:
python复制class MyQueue:
def __init__(self):
self.inStack = []
self.outStack = []
def push(self, x: int) -> None:
self.inStack.append(x)
def pop(self) -> int:
if not self.outStack:
while self.inStack:
self.outStack.append(self.inStack.pop())
return self.outStack.pop()
def peek(self) -> int:
if not self.outStack:
while self.inStack:
self.outStack.append(self.inStack.pop())
return self.outStack[-1]
def empty(self) -> bool:
return not self.inStack and not self.outStack
虽然pop和peek操作在最坏情况下是O(n)时间复杂度,但通过摊还分析,每个元素最多被压入和弹出每个栈各一次,所以这些操作的摊还时间复杂度是O(1)。这种分析在面试中常被考察,需要能够清晰解释。
空间复杂度是O(n),因为需要存储所有元素。在实际工程实现中,可以考虑以下几点优化:
编写测试用例时应考虑:
调试时可以打印两个栈的状态,可视化数据流动:
python复制def __str__(self):
return f"InStack: {self.inStack}, OutStack: {self.outStack}"
浏览器前进后退功能正是用双栈实现的典型场景:
在分布式系统中,有时需要保证消息处理的顺序性。可以用类似的双栈思想:
这种设计可以平衡生产者和消费者的速度差异,同时保证消息的顺序处理。
这道题在面试中通常会延伸考察以下方面:
在回答时,建议先给出基础实现,然后逐步深入这些扩展问题,展示全面的理解。
Java中使用Stack类(虽然官方推荐用Deque):
java复制class MyQueue {
private Stack<Integer> in = new Stack<>();
private Stack<Integer> out = new Stack<>();
public void push(int x) {
in.push(x);
}
public int pop() {
peek(); // 重用peek逻辑
return out.pop();
}
public int peek() {
if (out.empty()) {
while (!in.empty()) {
out.push(in.pop());
}
}
return out.peek();
}
public boolean empty() {
return in.empty() && out.empty();
}
}
C++中std::stack的pop()不返回值,需要先top()再pop():
cpp复制class MyQueue {
private:
stack<int> in, out;
void transfer() {
while (!in.empty()) {
out.push(in.top());
in.pop();
}
}
public:
void push(int x) {
in.push(x);
}
int pop() {
if (out.empty()) transfer();
int val = out.top();
out.pop();
return val;
}
int peek() {
if (out.empty()) transfer();
return out.top();
}
bool empty() {
return in.empty() && out.empty();
}
};
这是本问题的反向版本(LeetCode 225题),同样有巧妙的解法:
扩展问题:如何实现一个支持优先级出队的双栈队列?
解决方案:可以在push时维护元素的有序性,或者在transfer时进行排序,但这会增加时间复杂度。
如何在O(1)时间内支持查询队列中的最大/最小值?
这需要额外维护单调栈或使用其他辅助数据结构,是更高级的面试问题。
为了验证我们的实现效率,可以设计以下测试:
测试结果通常会显示:
在实际项目中使用这种模式时,我总结了以下几点经验:
这种双栈设计模式的价值不仅在于解决面试题,更在于培养我们利用简单数据结构构建复杂功能的能力。当系统设计受到某些限制时(如只能用特定数据结构),这种思维模式就显得尤为重要。