文章目录
剑指offer 09 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 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
<
=
v
a
l
u
e
s
<
=
10000
1 <= values <= 10000
1<=values<=10000
最多会对 appendTail
、deleteHead
进行 10000
次调用
解题思路
示例解释:
题目都已经告知是需要两个栈,那么这两个栈都应该设置成队列的成员属性,一个栈用于实际存放元素,而另一个栈就是用于各种操作时的中间容器而已。
解法一
元素永远保存在stack1(主栈中
),stack2(辅助栈)
只是作为一个中间容器。
Java代码
class CQueue {
//由于需要两个栈来实现队列,所以这两个栈应该是成员属性
private Stack<Integer> stack1;//主栈
private Stack<Integer> stack2;//辅助栈
public CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(stack1.isEmpty()) return -1;
copy(stack1,stack2);//stack1的数据都移到stack2
int res = stack2.pop();
copy(stack2,stack1);//stack2的数据都移回stack1
return res;
}
//定义一个辅助函数,用于把数据从一个栈移到另一个栈
private void copy(Stack<Integer> stack1,Stack<Integer> stack2){
while(!stack1.isEmpty()){
int value = stack1.pop();
stack2.push(value);
}
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
go代码
type CQueue struct {
stack1 *list.List
stack2 *list.List
}
func Constructor() CQueue {
return CQueue{
stack1 : list.New(),
stack2 : list.New(),//go初始化结构体时,最后一个字段后也需要加上逗号
}
}
func (this *CQueue) AppendTail(value int) {
this.stack1.PushBack(value)
}
func (this *CQueue) DeleteHead() int {
if this.stack1.Len() == 0 { return -1 }
copy(this.stack1,this.stack2)
val := this.stack2.Back()
this.stack2.Remove(val)//Back和Remove操作合在一起模拟了栈的pop操作
copy(this.stack2,this.stack1)
return val.Value.(int)//注意类型断言
}
func copy(stack1 *list.List,stack2 *list.List){
for stack1.Len() > 0 {
stack2.PushBack(stack1.Remove(stack1.Back()))
}
}
/**
* Your CQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.AppendTail(value);
* param_2 := obj.DeleteHead();
*/
显然,这里只需要进队和出队操作,所以每次移到stack2
的数据是可以不用移回来了的。详见解法二。
解法二
stack1
仍然是主栈,插入操作直接进stack1
即可,而出队操作时,可以先判断stack2
是否是空的,非空则直接出栈一个元素即是队列的对头元素,若是stack2
空,则将stack1
的元素都移到stack2
,刚好又逆序,符合出队的顺序了。
class CQueue {
//由于需要两个栈来实现队列,所以这两个栈应该是成员属性
private Stack<Integer> stack1;
private Stack<Integer> stack2;
public CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
//stack2空就将stack1的元素移过来,stack2非空则可以直接出栈,栈顶元素就是队首元素
if(stack2.isEmpty()) {
copy(stack1,stack2);//stack1的数据都移到stack2
}
//如果此时stack2还是空的,说明stack1和stack2中都没有元素,队列空
if(stack2.isEmpty()) return -1;
return stack2.pop();//stack2非空,栈顶元素就是队首元素
}
//定义一个辅助函数,用于把数据从一个栈移到另一个栈
private void copy(Stack<Integer> stack1,Stack<Integer> stack2){
while(!stack1.isEmpty()){
int value = stack1.pop();
stack2.push(value);
}
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
go代码
type CQueue struct {
stack1 *list.List
stack2 *list.List
}
func Constructor() CQueue {
return CQueue{
stack1 : list.New(),
stack2 : list.New(),//go初始化结构体时,最后一个字段后也需要加上逗号
}
}
func (this *CQueue) AppendTail(value int) {
this.stack1.PushBack(value)
}
func (this *CQueue) DeleteHead() int {
if this.stack2.Len() == 0 {
copy(this.stack1,this.stack2)
}
if this.stack2.Len() == 0 { return -1}
val := this.stack2.Back()
this.stack2.Remove(val)//Back和Remove操作合在一起模拟了栈的pop操作
return val.Value.(int)//注意类型断言
}
func copy(stack1 *list.List,stack2 *list.List){
for stack1.Len() > 0 {
stack2.PushBack(stack1.Remove(stack1.Back()))
}
}
/**
* Your CQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.AppendTail(value);
* param_2 := obj.DeleteHead();
*/
第九题扩展题1 用栈实现队列 LeetCode232
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列的支持的所有操作(push、pop、peek、empty):
实现 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
(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:
你能否实现每个操作均摊时间复杂度为
O
(
1
)
O(1)
O(1) 的队列?换句话说,执行 n
个操作的总时间复杂度为
O
(
n
)
O(n)
O(n) ,即使其中一个操作可能花费较长时间。
示例:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100
次 push、pop、peek
和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop
或者 peek
操作)
解题思路
解法一
解法与上面完全一致,同样提供两份对比代码和执行结果对比
Java代码
class MyQueue {
//由于需要两个栈来实现队列,所以这两个栈应该是MyQueue的属性
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
/** Initialize your data structure here. */
public MyQueue() {
}
//定义一个辅助函数,用于把数据从一个栈移到另一个栈
private void copy(Stack<Integer> stack1,Stack<Integer> stack2){
while(stack1.size()>0){
int num = stack1.pop();
stack2.push(num);
}
}
/** Push element x to the back of queue. */
public void push(int x) {
stack1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
copy(stack1,stack2);//stack1的数据都移到stack2
int res = stack2.pop();
copy(stack2,stack1);//stack2的数据都移到stack1
return res;
}
/** Get the front element. */
public int peek() {
copy(stack1,stack2);//stack1的数据都移到stack2
int res = stack2.peek();
copy(stack2,stack1);//stack2的数据都移到stack1
return res;
}
/** Returns whether the queue is empty. */
public boolean empty() {
//由于每次操作完之后数据总是存在stack1中的,所以判断stack1是否为空就可以了
return stack1.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
go代码
type MyQueue struct {
stack1 *list.List
stack2 *list.List
}
func Constructor() MyQueue {
return MyQueue {
stack1 : list.New(),
stack2 : list.New(),
}
}
func (this *MyQueue) Push(x int) {
this.stack1.PushBack(x)
}
func (this *MyQueue) Pop() int {
copy(this.stack1,this.stack2)
e := this.stack2.Back()
res := this.stack2.Remove(e)//注意Remove返回的是interface{},而非*list.Element类型
copy(this.stack2,this.stack1)
return res.(int)
}
func (this *MyQueue) Peek() int {
copy(this.stack1,this.stack2)
e := this.stack2.Back()
copy(this.stack2,this.stack1)
return e.Value.(int)
}
func (this *MyQueue) Empty() bool {
return this.stack1.Len() <= 0
}
func copy(stack1, stack2 *list.List) {
for stack1.Len() > 0 {
e := stack1.Back()
stack1.Remove(e)
//注意这里是stack2.PushBack(e.Value.(int)),而不是stack2.PushBack(e)
//因为e是*list.Element类型,而我们需要是int类型
stack2.PushBack(e.Value.(int))
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Peek();
* param_4 := obj.Empty();
*/
解法二
元素移到stack2
进行操作后并不移回stack1
写法如下
Java代码
class MyQueue {
//由于需要两个栈来实现队列,所以这两个栈应该是MyQueue的属性
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
/** Initialize your data structure here. */
public MyQueue() {
}
//定义一个辅助函数,用于把数据从一个栈移到另一个栈
private void copy(Stack<Integer> stack1,Stack<Integer> stack2){
while(stack1.size()>0){
int num = stack1.pop();
stack2.push(num);
}
}
/** Push element x to the back of queue. */
public void push(int x) {
stack1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
//stack2空就将stack1的元素移过来,stack2非空则可以直接出栈,栈顶元素就是队首元素
if(stack2.isEmpty()) {
copy(stack1,stack2);//stack1的数据都移到stack2
}
//如果此时stack2还是空的,说明stack1和stack2中都没有元素,队列空
if(stack2.isEmpty()) return -1;
return stack2.pop();//stack2非空,栈顶元素就是队首元素
}
/** Get the front element. */
public int peek() {
//stack2空就将stack1的元素移过来,stack2非空则可以直接peek,栈顶元素就是队首元素
if(stack2.isEmpty()) {
copy(stack1,stack2);//stack1的数据都移到stack2
}
//如果此时stack2还是空的,说明stack1和stack2中都没有元素,队列空
if(stack2.isEmpty()) return -1;
return stack2.peek();//stack2非空,栈顶元素就是队首元素
}
/** Returns whether the queue is empty. */
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();//两个栈都空就是队列空了
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
go代码
type MyQueue struct {
stack1 *list.List
stack2 *list.List
}
func Constructor() MyQueue {
return MyQueue {
stack1 : list.New(),
stack2 : list.New(),
}
}
func (this *MyQueue) Push(x int) {
this.stack1.PushBack(x)
}
func (this *MyQueue) Pop() int {
if this.stack2.Len() > 0 {
return this.stack2.Remove(this.stack2.Back()).(int)
}
copy(this.stack1,this.stack2)
return this.stack2.Remove(this.stack2.Back()).(int)
}
func (this *MyQueue) Peek() int {
if this.stack2.Len() <= 0 {
copy(this.stack1,this.stack2)
}
if this.stack2.Len() <= 0 {
return -1 //stack1中元素移过来了,stack2仍然是空,说明队列是空的
}
return this.stack2.Back().Value.(int)
}
func (this *MyQueue) Empty() bool {
return this.stack1.Len() <= 0 && this.stack2.Len() <= 0
}
func copy(stack1, stack2 *list.List) {
for stack1.Len() > 0 {
e := stack1.Back()
stack1.Remove(e)
//注意这里是stack2.PushBack(e.Value.(int)),而不是stack2.PushBack(e)
//因为e是*list.Element类型,而我们需要是int类型
stack2.PushBack(e.Value.(int))
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Peek();
* param_4 := obj.Empty();
*/
使用列表而非list.Stack容器库的Go代码
type MyQueue struct {
// 主栈,用于入栈操作
MainStack []int
// 辅助栈,用于查看和出栈操作
HelpStack []int
}
func Constructor() MyQueue {
return MyQueue{
MainStack : make([]int,0),
HelpStack : make([]int,0),
}
}
func (this *MyQueue) Push(x int) {
this.MainStack = append(this.MainStack,x)
}
func (this *MyQueue) Pop() int {
// 辅助栈中没有元素的时候,将主栈元素全部移入辅助栈
if len(this.HelpStack) == 0 {
this.mainStackToHelpStack()
}
// 移完后还是空,说明整个队列都空了,无法pop
if len(this.HelpStack) == 0 {
return -1
}
// pop
res := this.HelpStack[len(this.HelpStack)-1]
this.HelpStack = this.HelpStack[0:len(this.HelpStack)-1]
return res
}
func (this *MyQueue) Peek() int {
// 辅助栈中没有元素的时候,将主栈元素全部移入辅助栈
if len(this.HelpStack) == 0 {
this.mainStackToHelpStack()
}
// 移完后还是空,说明整个队列都空了,无法pop
if len(this.HelpStack) == 0 {
return -1
}
// peek
return this.HelpStack[len(this.HelpStack)-1]
}
func (this *MyQueue) Empty() bool {
fmt.Println(len(this.MainStack),len(this.HelpStack))
return len(this.MainStack) == 0 && len(this.HelpStack) == 0
}
// 将主栈元素全部移入辅助栈
func(this *MyQueue) mainStackToHelpStack() {
// 从后往前取数即可认为是出栈操作
for i := len(this.MainStack) - 1;i >= 0;i--{
this.HelpStack = append(this.HelpStack,this.MainStack[i])
}
// 移动完成后,清空MainStack
this.MainStack = make([]int,0)
}
/**
* Your MyQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Peek();
* param_4 := obj.Empty();
*/
第九题扩展题2 用两个队列实现栈 LeetCode225
225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(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
(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
最多调用100
次 push、pop、top
和 empty
每次调用 pop
和 top
都保证栈不为空
进阶: 你能否实现每种操作的均摊时间复杂度为
O
(
1
)
O(1)
O(1) 的栈?换句话说,执行 n
个操作的总时间复杂度
O
(
n
)
O(n)
O(n) ,尽管其中某个操作可能需要比其他操作更长的时间。你可以使用两个以上的队列。
解题思路
与上面的题目思路基本是一样的,但是由于队列的先进先出
性质,从queue1
将元素移到queue2
后,元素仍然是先进先出
的,不符合栈的先进后出
,故这里没办法像上面的题一样让两个容器都放数据的解法了,只有一个队列用于实际存元素,另一个队列用于各种操作时的中间容器的解法
。
Java代码
class MyStack {
//用两个队列实现栈,两个队列就应该是MyStack的属性
private Queue<Integer> queue1; //实际保存数据
private Queue<Integer> queue2; //辅助容器
/** Initialize your data structure here. */
public MyStack() {
queue1 = new LinkedList<>(); //LinkedList实现了Queue接口
queue2 = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
//push进来的要放到队首,所以先判断queue1是否空,不空的话先都移走,等下再移回来
//目的就是要让x放到queue1的队首,这样队首元素出队也刚好符合是栈顶元素出栈
while(!queue1.isEmpty()){
int num = queue1.peek();
queue2.add(num);
queue1.poll();
}
//此时queue1已空,可以放入x了
queue1.add(x);
//将queue2中的元素再放回来
while(!queue2.isEmpty()){
int num = queue2.peek();
queue1.add(num);
queue2.poll();
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
//数据都在queue1中,而queue1的队首就是所谓的栈顶元素,即要弹出的元素
return queue1.poll();
}
/** Get the top element. */
public int top() {
return queue1.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue1.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
go代码
type MyStack struct {
queue1, queue2 *list.List
}
func Constructor() MyStack {
return MyStack{
queue1 : list.New(),
queue2 : list.New(),
}
}
func (this *MyStack) Push(x int) {
// 后入的元素要放到队首,所以先将queue1中的元素都移走
Copy(this.queue1,this.queue2)
this.queue1.PushBack(x)
Copy(this.queue2,this.queue1)
}
func (this *MyStack) Pop() int {
e := this.queue1.Front()
this.queue1.Remove(e)
return e.Value.(int)
}
func (this *MyStack) Top() int {
return this.queue1.Front().Value.(int)
}
func (this *MyStack) Empty() bool {
return this.queue1.Len() <= 0
}
// 由于在循环中改变了queue,所以不应该使用这种迭代器遍历的方式,该方式会出错
// func Copy(queue1,queue2 *list.List) {
// for e := queue1.Front();e != nil;e = e.Next() {
// queue2.PushBack(e.Value.(int))
// queue1.Remove(e)
// }
// }
func Copy(queue1, queue2 *list.List) {
for queue1.Len() > 0 {
e := queue1.Back()
queue1.Remove(e)
//注意这里是stack2.PushBack(e.Value.(int)),而不是stack2.PushBack(e)
//因为e是*list.Element类型,而我们需要是int类型
queue2.PushBack(e.Value.(int))
}
}
/**
* Your MyStack object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Top();
* param_4 := obj.Empty();
*/
使用切片的话,直接一个切片就可以搞定了,思路很简单,一直操作的是队尾即可,Go代码如下
type MyStack struct {
// 使用一个队列实现栈
List []int
}
func Constructor() MyStack {
return MyStack{
List : make([]int,0),
}
}
func (this *MyStack) Push(x int) {
this.List = append(this.List,x)
}
func (this *MyStack) Pop() int {
if this.Empty() {
return -1
}
res := this.List[len(this.List) - 1]
this.List = this.List[0:len(this.List) - 1]
return res
}
func (this *MyStack) Top() int {
if this.Empty() {
return -1
}
return this.List[len(this.List) - 1]
}
func (this *MyStack) Empty() bool {
return len(this.List) == 0
}
/**
* Your MyStack object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* param_2 := obj.Pop();
* param_3 := obj.Top();
* param_4 := obj.Empty();
*/
小结
这种用两个容器协调做事的题目,一般一个容器是主容器,存放实际元素,而另一个容器就是辅助容器,协助完成某些操作。