Unit 1 栈和队列

Unit 1 栈和队列

Q1 设计一个有getMin功能的栈
Q2 两个栈模拟一个队列
Q3 用递归完成栈的逆序
Q4 猫狗队列
Q5 用一个辅助栈实现栈的排序
Q7 生成窗口最大值数组
Q8 构造给定数组的MaxTree
Q9 求最大子矩阵的大小
Q10 Max-Min<=Num的子数组数量
Q11 两个队列模拟一个栈
Q12 给出栈的压入顺序,判断弹出顺序是否合法
Q13 实现具有getMin功能的队列

 

unit 1 Q1:设计一个有getMin功能的栈

剑指offer 30 牛客链接

  • 要求:以时间复杂度O(1)实现进栈、出栈、获取最小值的操作;
  • time:2019/06/25
  • 思路:
    1.设置两个栈,第二个栈一直存最小值,其高度随第一个栈一起向上涨。
    2.入栈时如果新入的数比第二栈的栈顶还要小,则把新数压入第二栈;
    反之新数小于第二栈栈顶,则把栈顶元素再压一次。
    3.这样就保证了第二栈栈顶元素永远最小,获取最小值直接对stackMin peek()即可

代码:

    private Stack<Integer> myStack=new Stack<Integer>();
    private Stack<Integer> minStack=new Stack<Integer>();
    
    public void push(int node) {
        myStack.push(node);
        if(minStack.empty())//空的就直接加进minStack
            minStack.push(node);
        else if(node<minStack.peek())//新加的小就加新的
            minStack.push(node);
        else//新加的大,则将原来栈顶的再加一遍
            minStack.push(minStack.peek());
    }
    
    public void pop() {
        myStack.pop();
        minStack.pop();
    }
    
    public int top() {
        return myStack.peek();
    }
    
    public int min() {
        return minStack.peek();
    }

 

unit 1 Q2:用两个栈完成队列的入队、出队操作

剑指offer 9 Leetcode 232 难度:简单

  • date:2019/06/25
  • 思路:
    1.设置两个栈,第一个是入栈,第二个是出栈;
    2.入栈时只能往栈1中倒数据,出栈时只能从栈2中弹出数据。
    3.入队时入栈1,若栈1满、栈2空,则将栈1依次出栈再入栈2。若栈1满且栈2非空,则队满;
    4.出队时从栈2弹数据,若栈2空、栈1非空,则将栈1的数据依次导入栈2再弹出。若栈2和栈1都空,则队空。

代码:U1Q2_Queue_formedBy_twoStack.java

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();//只能入栈
    Stack<Integer> stack2 = new Stack<Integer>();//只能出栈
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        //栈2不空,从栈2出栈
        if(!stack2.isEmpty())
            return stack2.pop();
        else if(stack2.isEmpty()&&!stack1.isEmpty()){
            //栈2空,栈1不空。栈1的倒入栈2,再从栈2出栈
            while(!stack1.isEmpty())
                stack2.push(stack1.pop());
            return stack2.pop();
        }else
            //空栈
            throw new RuntimeException("队为空");
    }
}

 

unit 1 Q3:用递归完成栈的逆序操作
  • time:2019/07/01
  • 思路:
    1.设置两个递归函数。
    第一个getAndRemoveLastElement()负责:a.获得栈底元素 b.把栈底元素删除;
    第二个reverse()负责栈的逆序。
    2.reverse()的思路:
    栈的逆序就是把最底下的元素翻到上面来。
    递归的每一层都通过getAndRemoveLastElement()取出当前栈底元素存下来,并从栈中删除;
    栈空时返回,将存的元素压栈,这样越下层的递归存的是越上层的元素,则越上层的元素越先入栈,达到逆序的目的。
    3.getAndRemoveLastElement()思路:
    递归函数的目的是:栈(4,3,2,1)经函数后返回1,栈变为(4,3,2)
    递归的每一层都获取当前栈顶元素存下来,栈不空时进入下一层递归;
    栈空了返回栈底元素,并且每层都一路返回栈底元素,达到获取栈底元素的目的;
    在返回的间隙将每一层的栈顶元素重新入栈,越顶层的元素越后入栈,所以除了栈底元素被移除了栈的顺序不变。

代码:U1Q3_StackReverse_byRecursion.java

 

unit 1 Q4:猫狗队列
  • 要求:宠物,猫,狗的类已经定义好了。要实现方法:add(),pollAll(),pollDog(),pollCat()方法。
  • time:2019/07/02
  • 思路:
    如果只要求出狗队和出猫队,那设置一个狗队列一个猫队列就足够了。但是又要求混合在一起出队,想到:
    1.为了实现混合出队,需要在设置猫队和狗队基础上设置一个计数器count,入队时记录当前count并自增。
    出队时比较猫队首与狗对首的count,count小者先入的队,因此出队时先出队。
    2.将count和Pet封装到一个PetQueue类中,Pet实例和count都作为PetQueue实例的属性,入队/出队以PetQueue实例为单位进行。

代码:U1Q4_CatDogQueue.java

 

unit 1 Q5:用一个辅助栈实现栈的排序
  • 要求:用一个辅助栈实现栈的从顶到底从大到小的排序
  • time:2019/07/03
  • 思路:
    如果能让辅助栈tempStack中元素从小到大的排序,那再全部导入原栈Stack就能实现从大到小的排序了
    1.每次从Stack中弹出一元素cur,
    因为tempStack要从顶至底从小到大,所以比当前tempStack栈顶元素大的cur进不去,将tempStack中比它小的元素都暂存至Stack中(待会儿还会再放回tempStack的)。
    直到找到cur应在的位置将cur入tempStack栈;
    2.比当前tempStack栈顶元素小的直接入tempStack即可。
    这一过程是为了保证tempStack永远是从小到大有序。
    3.Stack空时,则tempStack一定是从小到大有序的,挨个出栈再入Stack即完成从大到小的排序。

代码:U1Q5_SortStack_byOtherStack.java

 

unit 1 Q7:生成窗口最大值数组

剑指offer 59 牛客链接

  • 要求:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
    数组长度N,窗口大小w,要求时间复杂度O(N)
  • time:2019/07/05
  • 思路:
    *设置一个双端队列qmax,用其队头元素记录当前窗口最大值的下标,窗口每次右移都更新qmax,保证队头元素是窗口最大值的下标。
    *为什么用下标? A:窗口长度是固定的,如果用确切的数字,无法判断队头的最大值是不是很久以前的过期的。
    遍历数组,每次从数组arr中取一新元素。
    1.如果新元素比qmax当前队尾小,则放入队尾;
    如果新元素比qmax当前队尾大,则一直弹出当前队尾,直到新元素比qmax当前队尾小为止。将新元素放入。
    2.若队尾(下标)-队头(下标)>=窗口大小,则说明qmax的队头元素过期了,弹出队头元素。(滑动窗口已经滑过该最大值了)
    3.除了前(w-1)次遍历,其余每次的队头元素都是当前窗口的最大值,记录在数组res中。

代码:

    public ArrayList<Integer> maxInWindows(int[] arr,int size){
        ArrayList<Integer> res=new ArrayList<Integer>();//结果list
        if(arr==null || size<1 || arr.length<size) return res;
        //双端队列qmax,存数组下标,从尾到头依次递增。队头表示窗口最大值下标
        LinkedList<Integer> qmax = new LinkedList<Integer>();
        //每轮遍历(除了前(窗口-1)轮),都取一个最大值存入res list
        for(int i=0,index=0;i<arr.length;i++){
            //保证队列从尾到头对应的arr[i]递增情况下,从队尾插入i
            //如果arr[i]比当前队尾小,则将下标i直接插入队尾             
            if(qmax.isEmpty()||arr[i]<arr[qmax.peekLast()]) 
                qmax.addLast(i);
            else {
                //如果数组中arr[i]比当前队尾对应的大,则一直弹出当前队尾,直到arr[i]比当前队尾小为止
                while(!qmax.isEmpty()&&arr[i]>=arr[qmax.peekLast()])
                    qmax.pollLast();
                qmax.addLast(i);//将arr[i]下标i插入队尾,qmax从尾到头递增有序
            }
			//验证当前队头是否过期:
            //队尾(下标)-队头(下标)>=窗口大小,则说明qmax的队头元素过期了,弹出队头元素
            if((qmax.peekLast()-qmax.peekFirst())>=size) 
                qmax.pollFirst();

            //除了前(窗口-1)次,其他时候qmax的队头元素所对应的值,都是当前窗口的最大值
            //从第(窗口大小)次开始,更新res
            if(i>=size-1)
                res.add(arr[qmax.peekFirst()]);
        }
        return res;
    }

 

unit 1 Q8:构造给定数组的MaxTree
  • 要求:根据一无重复元素的数组,构造一颗大顶堆形式的二叉树MaxTree。
    时空复杂度均为O(N)
  • 思路:
    建树原则:找到该数向左第一个比它大的数和向右第一个比它大的数,取其小者作为该数的父节点;
    如果一个数向左和向右都没有比它大的数,则它就是根节点
    1.建立NodeArr数组存放节点;leftmax数组存每个数向左第一个比它大的数;rightmax数组存向右第一个比它大的数;stack栈用来以O(N)的时间复杂度寻找该数向左、向右第一个比它大的数
    2.遍历数组arr。辅助栈stack要求从顶至下需由小至大,每个新数需在栈中找到自己的位置(即如果栈顶元素比新数小,则一直弹出直到栈顶元素比新数大为止)
    新数位置找到后,新数下方的数即是该数向左数第一个比它大的数。将新数入栈之前取其下标放入leftmax[i]记录。
    3.同样方法求每个数向右数第一个比它大的数,存入rightmax数组。
    4.遍历数组为每个节点指定父节点以构造树。取leftmax[i]和rightmax[i]中较小者为该节点的父节点。

代码:U1Q8_getMaxTree_fromArray.java

 

unit 1 Q9:求最大子矩阵的大小

给定一个N × \times ×M型矩阵map,值为0或1,求最大的全为1的矩阵的面积

[ 1 0 1 1 1 1 1 1 1 1 1 0 ] \left[\begin{matrix} 1 & 0 & 1 & 1 \\ 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 0 \end{matrix} \right] 111011111110该最大的矩形的面积为6
要求:时间复杂度O(N × \times ×M)
time :2019/07/07
思路:
1.对矩阵进行遍历。假设遍历到如上所示第3行{1,1,1,0},将第3行每个数算做底,上面拥有1的个数看作高,试着看这个S=1 × \times ×h的矩形能不能向外扩成更大的矩形。

如:对第三行的{1,1,1,0},
第一个数字1:其高为3,以该矩形为核心向左向右均不能扩成更大的矩形,则对该数map[2][0]其扩展矩形面积为1 × \times × 3=3;
第二个数字1:其高为2,以该矩形为核心向右可扩展1、向左可扩展1,则其扩展矩形面积为3 × \times × 2=6;
第三个数字1:的扩展矩形面积为1 × \times × 3=3;
第四个数字是0,不满足题设矩形条件,面积为0;

记录遍历过程中最大的矩形面积。
2.如何计算拥有1的个数? 设置高度矩阵height[i][j],一行代码足矣

height[i][j] = (map[i][j] == 0) ? 0 : (height[i - 1][j] + map[i][j]);

2.如何以O(N × \times × M)的时间复杂度实现?
本质上找扩展矩形即是找该数向左数第一个比它小的数和向右数第一个比它大的数。使用栈来实现这一操作。思路同Q7、Q8相似。
求向左数第一个比它小的数:从左至右遍历数组,辅助栈存元素下标,下标对应的值从顶至底由大到小。
每个新数进来时找准位置(即如果栈顶比它大,则一直弹出直到栈顶元素比它大为止)
将新数进栈之前,记录当前栈顶元素,当前栈顶元素即为新数向左数第一个比它小的数。求向右数第一个比它小的数同理。
由于任何一个数进出栈都只有一次,所以每一行的时间复杂度为O(M),共N行,时间复杂度O(N × \times × M)。

代码:U1Q9_SizeOf_MaxSubmatrix.java

 

unit 1 Q10:Max-Min<=Num的子数组数量

要求:Max:子数组中的最大值; Min:子数组中的最小值; Num:给定整数
如果数组长度为N,要求时间复杂度为O(N)
time:2019/07/08
思路:

  1. 两个前提条件:

(a).如果一个数组满足(Max-Min)<=Num,则该数组的所有子数组一定都满足该条件;
(b).如果一个子数组不满足(Max-Min)<=Num,则它的所有扩充数组都不满足该条件。

2.步骤:

  • 设置两个双端队列,qmax记录子数组最大值、qmin记录子数组最小值。
    设置一i在前,j在后,用i和j划分子数组。i,j初值为0
  • i不动,j依次向右移动,每次移动更新qmax,qmin,移动至不能满足(Max-Min)<=Num为止。
    此时前j-i个以i为起始的数组都满足该条件,累加count+=j-i;
  • 以当前i为起始的数组,此后向后的j+1,j+2等都不能满足条件。所以i向右挪一个,j从原先位置接着右移,每次移动更新qmax,qmin,移动至不能满足(Max-Min)<=Num为止。记录count+=j-i。

Q:为啥新的j不从i开始右移?
A:前提条件已知,当i至j满足条件时,其余i+1/i+2至j为其子数组都满足条件。

  • j会先到头。i再一步步右移,i每次右移更新依次count+=j-i至i也到头为止。

3.如何以O(1)的时间复杂度更新双端队列qmax和qmin?

j更新:

  • qmax的队头永远是最大值,从队尾至队头要从小到大
    新数下标入队:
    若新数比队尾小,则新数下标入队尾;
    若新数比队尾大,则一直弹出队尾直至新数比当前队尾小为止,新数下标入队尾。
  • (b).qmin的队头永远是最小值,从队尾至队头要从大到小;
    道理同qmax。

i更新:
对qmax而言有两种情况,(qmin同理)

  • 一种是队头元素就是这个i,那直接弹出即可;
  • 另一种是arr[i]已经因为比较小之前被从队尾弹出了,则队头元素不是这个i,已经弹过了就不用弹了。

4.为啥时间复杂度为O(N)?
这样每个下标值仅分别进出qmax和qmin一次。i 和 j 也只增不减,所以时间复杂度为O(N)。

代码:U1Q10_numOfSubArr_maxMinusMinLessThanAnum.java
 

U1Q11:两个队列模拟一个栈

2019年西安交大考研真题(我做出来了hhh)
date:2019/11/11
在这里插入图片描述

 

U1Q12:给出栈的压入顺序,判断弹出顺序是否合法

剑指offer 31 牛客链接
要求:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
date:2020/02/12

思想:
验证出栈是否合理的方法就是先入栈再出栈
设置一个虚拟栈,一个for循环根据压栈序列向虚拟栈中模拟压栈。for循环中套一while循环,当虚拟栈不为空且栈顶元素与弹出序列中待弹元素相等,则虚拟栈模拟出栈。
出for循环后,如果栈为空则能弹完,合法;栈不为空则无法按照弹出顺序弹出,不合法。

代码:

    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack=new Stack<Integer>();
        if(pushA.length==0||popA.length==0)
            return false;
        for(int i=0,popIndex=0;i<pushA.length;++i){
            stack.push(pushA[i]);//每一轮for循环入栈一个
            //栈顶元素等于当前要弹出的且栈不为空:则一直从虚拟栈中弹出
            while(!stack.empty() && stack.peek()==popA[popIndex]){
                stack.pop();
                popIndex++;
            }
        }
        //验证出栈是否合理的方法就是先入栈再出栈
        return stack.isEmpty();
    }

 

U1Q13:实现具有getMax功能的队列

剑指offer 59_2 牛客链接
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1。
date:2020/02/22
思路:
跟Q7思路相似。设置一个qmax数组,从尾到头从小到大排列。
入队时,在确保qmax从尾到头从小到大排列的情况下,将value从尾部插入qmax.
出队时,如果待出队元素等于qmax队首元素,qmax队首元素出队。

代码:

class MaxQueue {
        LinkedList<Integer> qmax;
        Queue<Integer> queue;

    public MaxQueue() {
        qmax=new LinkedList<Integer>();//从尾到头,从小到大排列
        queue=new ArrayDeque<Integer>();
    }
    
    public int max_value() {
        if(qmax.isEmpty())
            return -1;
        return qmax.peekFirst();
    }
    
    public void push_back(int value) {
        queue.add(value);
        //如果当前qmax的尾元素比value小,不要尾元素,将其弹出
        while(!qmax.isEmpty()&&qmax.peekLast()<value)
            qmax.pollLast();
        qmax.addLast(value);
    }
    
    public int pop_front() {
        if(queue.isEmpty())
            return -1;
        int res=queue.poll();
        if(res==qmax.peekFirst())//如果和qmax的头元素相同,qmax也出队
            qmax.pollFirst();
        return res;
    }
}
/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值