算法刷题5【剑指offer系列之栈和队列】

2020.06.21

1、两个栈来实现一个队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路:
1、入栈只入push栈,出栈只出pop栈
2、若pop栈不为空,push栈不能进入
3、push栈的元素如果可以加入到pop栈,则需要全部一起到pop栈

public class Stack2Queue {
    Stack<Integer> pushStack = new Stack<Integer>();
    Stack<Integer> popStack = new Stack<Integer>();

    /**
     * 入队只入一个栈
     * @param node
     */
    public void push(int node) {
        pushStack.push(node);
    }

    /**
     * 出队只从一个栈中出
     * @return
     */
    public int pop() {
        if(popStack.empty()){
            while (!pushStack.empty()){
                popStack.push(pushStack.pop());
            }
        }
        return popStack.pop();
    }

}

扩展:两个队列实现栈

思路:队列实现栈:腾空一下位置,将最后一个返回
出栈和入栈都是在data队列,但是需要一个辅助队列实现。
入栈只入数据队列
出栈是需要返回数据队列最后进入的一个元素,所以将队列的最后一个元素按照要求返回,然后将data队列剩下的放到辅助队列,最后需要换引用。
如果不换引用,将data队列剩下的放到辅助队列后,data队列就空了

public class Queue2Stack {
    public static void main(String[] args) {
        Queue2Stack stack=new Queue2Stack();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        System.out.println(stack.pop());  //4
        System.out.println(stack.pop());  //3
        System.out.println("top"+stack.top()); //2
        System.out.println(stack.pop()); //2
        System.out.println(stack.pop()); //1

    }

    private ArrayDeque<Integer> data= new ArrayDeque<>();
    private ArrayDeque<Integer> help= new ArrayDeque<>();

    /**
     * 模拟入栈操作:入栈只入数据栈
     * @param node
     */
    public void push(Integer node){
        data.offer(node);
    }

    /**
     * 模拟出栈:需要借助辅助队列,将数据队列的前面的数据都移动
     * @return
     */
    public int pop(){
        if (data.isEmpty()){
          return Integer.MIN_VALUE;
        }
        //全部移动到help
        while (data.size()>1){
            help.offer(data.poll());
        }
        int res=data.poll();
        swap();
        return res;

    }

    /**
     * 获取栈顶元素
     * @return
     */
    public  int top(){
        if (data.isEmpty()){
            return Integer.MIN_VALUE;
        }
        while (data.size()>1){
            help.offer(data.poll());
        }
        int res=data.poll();
        //和pop不同的就多了这一步
        help.offer(res);
        swap();
        return res;
    }

    /**
     * 交换两个队列的引用
     */
    private void swap() {
        ArrayDeque<Integer> temp=data;
        data=help;
        help=temp;
    }
}

2、最小栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
思路:
1、使用辅助栈min,入栈只进data栈,min栈同步记录最小值,如果进入的数num大于min栈的栈顶,则栈顶继续压栈,否则min栈入栈num
2、出栈的时候data栈和min栈同步出栈

public class MinStack {
    Stack<Integer> data = new Stack<>();
    Stack<Integer> min = new Stack<>();

    public static void main(String[] args) {
        MinStack minStack = new MinStack();
        minStack.push(3);
        System.out.println(minStack.min());
        minStack.push(4);
        System.out.println(minStack.min());
        minStack.push(2);
        System.out.println(minStack.min());
        minStack.push(3);
        System.out.println(minStack.min());
        minStack.pop();
        System.out.println(minStack.min());
        minStack.pop();
        System.out.println(minStack.min());
        minStack.push(0);
        System.out.println(minStack.min());

    }

    public void push(int node) {
        data.push(node);
        //关键:min栈进栈的时候需要判断
        if (min.empty()) {
            min.push(node);
        } else {
            if (min.peek() <= node) {
                min.push(min.peek());
            } else {
                min.push(node);
            }
        }
    }

    public void pop() {
        if (!data.empty()) {
            data.pop();
        }
        if (!min.empty()) {
            min.pop();
        }
    }

    public int top() {
        if (!data.empty()) {
            return data.peek();
        }
        return -1;
    }

    public int min() {
        if (!min.empty()) {
            return min.peek();
        }
        return -1;

    }
}

更好的思路:使用min变量来分割截至目前的栈中的最小值,如果出现比min更小的,说明min需要被保存,表示这个更小的值之前的最小值是min,同时更新min
出栈时候,需要判断出栈的元素res是不是min,如果是,说明出栈的元素是更小值(因为这个min其实是更新后的min),res下一个才是旧的最小值,需要再pop一次。

public class MinStackBetter {
    private Stack<Integer> data = new Stack<>();
    int min = Integer.MAX_VALUE;

    /**
     * 数据入栈是需要处理
     *
     * @param node
     */
    public void push(int node) {
        //1、min保存截至目前入栈的元素中的最小值
        //遇到更小需要更新
        if (min >= node) {
            data.push(min);
            //min更新变得更小
            min = node;
        }
        //先min入栈再node入栈就是保证min是表示下面的元素中的最小值
        //正常插入新的数据
        data.push(node);
    }

    /**
     * 出栈的时候也需要特殊处理:
     */
    public void pop() {
        //正常数据只需要pop一次
        int res = data.pop();
        //说明还需要再pop一下
        if (res == min) {
            min = data.pop();
        }
    }

    public int top() {
        return data.peek();
    }

    public int min() {
        return min;
    }
}

3、判断栈的压入和弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:使用辅助栈,出栈的序列顺序决定压栈序列是否压栈
(1)遍历入栈序列,加入辅助栈,同时使用popindex遍历出栈队列,如果下一个弹出的数刚好是栈顶元素,则说明入栈序列和出栈序列合格,需要循环弹出。
(2)如果不等,则继续循环,也就是入栈序列继续下一个入栈
(3)最后判断栈是否为空,如果栈为空,说明出栈序列符合规定。
在这里插入图片描述在这里插入图片描述

public class IsPopOrder {

    public boolean IsPopOrder(int[] pushA, int[] popA) {
        if (pushA == null || pushA.length == 0
                || popA == null || popA.length == 0||popA.length!=pushA.length) {
            return false;
        }
        ArrayDeque<Integer> stack=new ArrayDeque<>();
        //需要遍历入栈序列
        int len=pushA.length,popIndex=0;
        for (int i=0;i<len;i++){
            stack.push(pushA[i]);
            //如果栈顶和出栈序列相等,则全部直接出栈
            while (!stack.isEmpty()&&stack.peek()==popA[popIndex]){
                stack.pop();
                popIndex++;
            }
            //如果不等,则继续循环,也就是入栈序列一直入栈
        }
        //如果最后栈空,说明刚好完全出栈
        return stack.isEmpty();
    }```
    
    



4、数据流中的中位数(堆)

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:大根堆存小数(小数的最大)+小根堆存大数(大数的最小)+个数差别不超过1(使用判断大小实现)
1、大根堆存放的是较小的n/2个数,小根堆存放的是较大的n/2个数,判断大小是通过大根堆的堆顶,第一个元素(通过大根堆是否为空判断第一个)因为堆都是空,第一个元素先入大于大根堆
2、大于大根堆堆顶就放到小根堆,小于等于就放到大根堆
3、如果两个堆的元素个数(通过大小获取)差值超过1,则多的那个弹出堆顶给少的那个堆(确保两个堆的元素个数差值不超过1),最后大根堆和小根堆的堆顶一定可以找到中位数(有可能不是传入的数,而是计算而得的小数)
4、使用count记录元素个数,如果元素个数是奇数,则哪个堆的数量多就是哪个堆顶;若为偶数,则中位数是两个堆顶的平均数。
注意:顺序是小根堆,逆序是大根堆
在这里插入图片描述

public class GetMedianFromDataStream {
    public static void main(String[] args) {
        GetMedianFromDataStream stream=new GetMedianFromDataStream();
        stream.Insert(5);
        Double median1 = stream.GetMedian();
        System.out.println(median1);
        stream.Insert(2);
        Double median2 = stream.GetMedian();
        System.out.println(median2);
        stream.Insert(3);
        Double median3 = stream.GetMedian();
        System.out.println(median3);
        stream.Insert(4);
        Double median4 = stream.GetMedian();
        System.out.println(median4);
        stream.Insert(1);
        Double median5 = stream.GetMedian();
        System.out.println(median5);
        stream.Insert(6);
        Double median6 = stream.GetMedian();
        System.out.println(median6);
        stream.Insert(7);
        Double median7 = stream.GetMedian();
        System.out.println(median7);
        stream.Insert(0);
        Double median8 = stream.GetMedian();
        System.out.println(median8);
        stream.Insert(8);
        Double median9 = stream.GetMedian();
        System.out.println(median9);

    }

    PriorityQueue<Integer> minHeap=new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1-o2;
        }
    });

    PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;
        }
    });
    int count=0;
    public void Insert(Integer num) {
        //注意不能放在后面
        count++;
        //1、一开始先插入大根堆
        if (maxHeap.isEmpty()){
            maxHeap.offer(num);
            return;
        }else {
            if (maxHeap.peek()>=num){
                //比大根堆的max小,则放入大根堆
                maxHeap.offer(num);
            }else {
                //比大根堆的max大,则放入小根堆
                minHeap.offer(num);
            }
        }
        //每次加入新的数以后都要进行堆的调整,保证个数相差不超过1
        modifyTwoHeapsSize();
    }

    private void modifyTwoHeapsSize() {
        if (maxHeap.size()+2==minHeap.size()){
            maxHeap.offer(minHeap.poll());
        }else if (minHeap.size()+2==maxHeap.size()){
            minHeap.offer(maxHeap.poll());
        }
    }


    public Double GetMedian() {
        if (count==0){
            return null;
        }
        if ((count&1)==1){
            //说明是奇数,哪个堆大就返回哪个堆的堆顶
            if (maxHeap.size()>minHeap.size()){
                //注意不是poll
                return Double.valueOf(maxHeap.peek());
            }else {
                return Double.valueOf(minHeap.peek());
            }
        }else {
            //说明是偶数,返回两个堆堆顶的平均数
            return (maxHeap.peek()+minHeap.peek())/2.0;
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值