算法面试题:使用两个堆栈实现一个队列

55 篇文章 5 订阅

更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南

如果你对机器学习感兴趣,请参看一下链接:
机器学习:神经网络导论

队列的插入和删除遵循先入先出的原则,而堆栈元素的插入和删除遵循后进先出的原则。在很多应用场景下,我们需要使用堆栈来模拟队列,或者是使用队列来模拟堆栈。在数学上,已经能够严格证明,我们是不能使用含有n个元素的堆栈来模拟含有n个元素的队列的,这个证明非常复杂,详细证明可以参看论文: The Power of the queue.

用一个堆栈来模拟一个队列是不可能的,但是用两个堆栈来模拟一个队列确是可能的,问题是:如何使用两个堆栈来模拟一个队列,实现两种操作,enqueue 和 dequeue, enqueue是在队列末尾加入一个元素,dequeue是把队列的头部元素取出来。要求不能分配超过O(1)的内存,同时要求当进行m次enqueue或dequeue操作时,算法复杂度必须是O(m).

我们的做法是这样的,两个堆栈,假设分别为StackA, 和 StackB, 当使用enqueue将元素加入堆栈时,把该元素直接压入StackA, 如果需要使用dequeue将队列头元素出栈时,可以直接把StackB中的元素出栈。如果出栈时StackB中没有元素,那么把StackA中的元素逐个弹出然后压入StackB.

例如我们通过enqueue操作构造的一个队列如下:
1<- 2 <- 3 <- 4 <- 5

由于是enque操作,所以上面元素依次压入StackA.
StackA: StackB:
5
4
3
2
1

如果此时要dequeue获取队列的头元素1,那么我们把StackA中的元素逐个弹出,然后再压入B, 于是先弹出5,然后把5压入B, 依次进行后情况为:
StackA: StackB:
1
2
3
4
5
然后把B中的头元素弹出,于是数值1就可以获取了。

该算法不难,关键是如何证明该算法满足时间复杂度O(n).
每次执行enqueue操作是,我们只需要把元素通过push加入StackA,所以时间复杂度是O(1), 问题在于,如果执行dequue操作,我们有可能需要把元素从StackA 全部弹出,然后再压入StackB, 这个过程的时间复杂度是O(n)。事实上,任何一个元素,它能被操作的次数最多不过3次,一次是压入堆栈,一次是从StackA转移到StackB, 一次是从StackB中弹出,所以m次enqueue和dequue操作,其时间复杂度是3*m, 因此我们的算法时间复杂度是线性的。

用于我们算法中没有分配多余内存,因此空间复杂度是O(1).算法的实现代码如下:

import java.util.Stack;


public class StackQueue {
    private Stack<Integer> stackA = new Stack<Integer>();
    private Stack<Integer> stackB = new Stack<Integer>();

    public void enquene(int v) {
        stackA.push(v);
    }

    public int dequeue() {
        if (stackB.isEmpty()) {
            while (!stackA.isEmpty()) {
                stackB.push(stackA.pop());
            }
        }

        return stackB.pop();
    }
}

主入口处的代码为:

import java.util.ArrayList;
import java.util.Random;
import java.util.Stack;


public class StackAndQuque {
    public static void main(String[] args) {

        StackQueue sq = new StackQueue();
        System.out.println("enqueue: ");

        for (int i = 1; i <=5; i++) {
            sq.enquene(i);
            System.out.print(i + " ");
        }

        System.out.println("\ndequeue: ");
        for(int i = 0; i < 5; i++) {
            System.out.print(sq.dequeue() + " ");
        }

    }
}

代码执行后的结果为:

enqueue: 
1 2 3 4 5 
dequeue: 
1 2 3 4 5 

我们可以看到,元素的入队和出队遵守先入先出的次序,因此,我们代码的实现应该是正确的。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一个数组中实现两个堆栈可以采用两种不同的方法: 1. 固定分割:将数组等分为两部分,一部分用于存储第一个堆栈的元素,另一部分用于存储第二个堆栈的元素。这种方法的缺点是两个堆栈的大小不一定相等,可能会导致其中一个堆栈无法存储更多的元素。 2. 变化分割:将两个堆栈的起始位置分别放在数组的两端,向中间移动。当两个堆栈的元素数量相等时,它们的中间位置相遇。这种方法的缺点是需要经常移动堆栈的起始位置,可能会导致性能下降。 下面是一个使用固定分割的示例代码: ```python class TwoStacks: def __init__(self, size): self.array = [0] * size self.top1 = -1 self.top2 = size def push1(self, value): if self.top1 < self.top2 - 1: self.top1 += 1 self.array[self.top1] = value else: raise Exception("Stack 1 overflow") def push2(self, value): if self.top1 < self.top2 - 1: self.top2 -= 1 self.array[self.top2] = value else: raise Exception("Stack 2 overflow") def pop1(self): if self.top1 >= 0: result = self.array[self.top1] self.top1 -= 1 return result else: raise Exception("Stack 1 underflow") def pop2(self): if self.top2 < len(self.array): result = self.array[self.top2] self.top2 += 1 return result else: raise Exception("Stack 2 underflow") ``` 这个类有两个堆栈,分别由 `push1`、`push2`、`pop1` 和 `pop2` 方法支持。它使用一个数组来存储元素,数组的大小由构造函数传递。在初始化时,两个堆栈的起始位置分别为 -1 和 size。在 `push1` 和 `pop1` 方法中,我们将 top1 递增或递减来实现一个堆栈的操作。在 `push2` 和 `pop2` 方法中,我们将 top2 递减或递增来实现第二个堆栈的操作。当 top1 和 top2 相遇时,两个堆栈都满了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值