栈与队列最优解总结

  • 栈与队列
    • 实现最小栈
      • 题目描述
      • 解题思路
    • 由两个栈组成的队列
      • 题目描述
      • 解题思路
    • 两个队列实现栈
      • 题目描述
      • 解题思路
    • 使用递归函数和栈操作逆序一个栈
      • 题目描述
      • 思路解析
    • 用一个栈实现另一个栈的排序
      • 题目描述
      • 思路解析
    • 生成窗口最大值数组
      • 题目描述
      • 思路解析
    • 求最大子矩阵的大小
      • 题目描述
      • 思路解析
    • 最大值值减去最小值小于或等于num的子数组的数量
      • 题目描述
      • 思路解析

栈与队列

实现最小栈

题目描述

  • 实现一个特殊的栈,在实现栈的基本功能的基础上,再返回栈的最小元素的操作
  • 要求:pop(),push(),getMin()时间复杂度都是O(1)

解题思路

  • 设计上使用两个栈,一个栈保存当前栈中的元素记为stackData;另外一个栈用于保存每一步的最小值,记为minStack;
    1. 压入数据规则
      设当前数据为num,先将其压入StackData,然后判断minStack是否为空,若为空,压入minstack,否则比较num与minStack的栈顶元素,当小于栈顶元素时,压入minStack
    2. 弹出数据规则
      先在stackData中弹出栈顶元素,记为value,然后比较minStack中的栈顶元素,如果minStack栈顶元素等于value,minStack的栈顶元素也弹出,否则不进行处理
    3. 查询最小值
      minStack始终保存stackData中的最小值
public class Stack1{
    private StackdataStack;
    private Stack minStack;

    public void push(int num){
        if(this.minStack.isEmpty()){
            this.minStack.push(num);
        }else if(num<=this.getMin()){
            this.minStack.push(num);
        }
        this.dataStack.push(num);
    }
    public int pop(){
        int value=this.dataStack.pop();
        if(value==this.minStack.peek()){
            this.minStack.pop();
        }
        return value;
    }
    public int getMin(){
        return this.minStack.peek();
    }
}

由两个栈组成的队列

题目描述

编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek)

解题思路

由于栈的特点是先入后出,队列的特点是先进先出,使用两个栈正好能把顺序反过来;设计上使用两个栈,一个栈负责压入数据,记为pushStack,另外一个栈负责弹出数据popStack;
1. 压入数据的规则
pushStack正常压入数据,但是如果popStack不为空,不可将pushStack的数据压入到popStack;同时pushStack的数据要往popStack压入数据时,必须要一次性的将pushStack数据全部压入,add方法
2. 弹出数据规则
popStack()正常弹出数据即可。即poll,与peek方法,
3. 向popStack压入数据在poll,peek方法处执行

public class TwoStackQueue{
    private Stack pushStack;
    private Stack popStack;

    public void add(int num){
        pushStack.push(num);
    }
    public int poll(){
        if(pushStack.empty()&&stackPush.empty()){
            throw new RuntimeException("Queue is Empty");
        }
        else if(popStack.empty()){
            while(!pushStack.empty()){
                popStack.push(pushStack.pop());
            }
        }
        return popStack.pop();
    }
    public int peek(){
        if(pushStack.empty()&&stackPush.empty()){
            throw new RuntimeException("Queue is Empty");
        }
        else if(popStack.empty()){
            while(!pushStack.empty()){
                popStack.push(pushStack.pop());
            }
        }
        return popStack.peek();
    }
}

两个队列实现栈

题目描述

编写一个类,用两个队列实现栈的功能,实现栈的push,pop方法

解题思路

定义两个队列queue1,queue2
1. push方法实现
判断queue1,queue2是否为空,将需要进栈的元素num,添加到不为空的一个队列的队尾,
2. pop方法实现
判断queue1,queue2是否为空,为说明方便,假设queue1队列不为空,要获得栈顶元素(队尾的元素,记为value),将队列中的元素,从队首到队尾的前一个元素依次弹出,添加到queue2(初始状态为空队列)队尾位置,最后弹出queue1队尾元素value,此时queue2队列为空,

public class TwoQueueStack{
    private LinkedList queue1;
    private LinkedList queue2;

    public void push(int num){
        if(queue1.isEmpty()){
            queue2.add(num);
        }
        if(queue2.isEmpty()){
            queue2.add(num);
        }
    }
    public int pop(){
        if(queue2.isEmpty()&&queue2.isEmpty()){
            throw new RuntimeException("Stack is Empty");
        }
        if(queue1.isEmpty()){
            while(queue2.size()>1){
                queue1.add(queue2.poll());
            }
            int value= queue2.poll();
        }
        else if{//queue2.isEmpty()
            while(queue1.size()>1){
                queue1.add(queue1.poll());
            }
            int value= queue1.poll();
        }
        return value;
    }
}

使用递归函数和栈操作逆序一个栈

题目描述

只能用递归函数来实现栈的逆序的操作

思路解析

两个递归函数:
- 递归函数1:将栈的栈底元素删除并且返回
- 递归函数2:逆序一个栈,将栈底元素删除之后的栈进行逆序,然后将栈底元素重新压入栈中

public class ReverseStack{
    private Stack stack;

    public int getAndRemoveLastElement(){
        int result=stack.pop();
        if(stack.isEmpty()){
            return result;
        }else{
            int res=getAndRemoveLastElement();
            stack.push(result);
            return res;
        }
    }
    public void reverseStack(){
        if(stack.isEmpty()){
            return;
        }
        int res=getAndRemoveLastElement();
        reverseStack();
        stack.push(res);
    }
}

用一个栈实现另一个栈的排序

题目描述

一个栈中的元素类型为整型,将该栈从栈顶到栈底从大到小进行排序,只允许申请一个栈,除此之外,可以申请新的变量,不能申请额外的数据结构

思路解析

将要排序的栈记为stack,辅助栈为help,比较操作在help栈中实现,具体操作如下:
1. 在stack进行pop操作,弹出的元素记为cur
2. 比较cur,help的栈顶元素
- 如果cur小于help的栈顶元素,直接将cur压入help栈中
- 如果cur大于help的栈顶元素,则将help栈中的元素逐一弹出,逐一压入stack中,直到cur小于或者等于help的栈顶元素;
3. 最后将help的元素压入stack中

public void sortStackByStack(Stack stack){
    Stack help=new Stack<>();
    while(!stack.isEmpty()){
        int cur=stack.pop();
        while(!help.isEmpty()&&cur>help.peek()){
            stack.push(help.pop());
        }
        help.push(cur);
    }
    while(!help.isEmpty()){
        stack.push(help.pop());
    }
}

生成窗口最大值数组

题目描述

有一个整型数组arr,和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。

例如arr=[4,3,5,4,3,3,6,7],窗口大小为3
[4  3  5] 4  3  3  6  7  窗口的最大值为5
 4 [3  5  4] 3  3  6  7  窗口的最大值为5
 4  3 [5  4  3] 3  6  7  窗口的最大值为5
 4  3  5 [4  3  3] 6  7  窗口的最大值为4
 4  3  5  4 [3  3  6] 7  窗口的最大值为6
 4  3  5  4  3  [3 6  7] 窗口的最大值为7
数组长度为n,窗口为w,将产生n-w+1
输出为:[5 5 5 4 6 7]

思路解析

使用双端队列实现窗口最大值的更新,双端队列qmax保存的是数组的索引。队首即为窗口的最大值的索引。
1. 假设遍历到arr[i],qmax的放入规,则:
- qmax为空,直接放入i,
- qmax不为空,取出当前qmax的队尾存放的下标j(其实是队列的最小值)
- 如果arr[i]>=arr[j],直接将j从qmax队列弹出,继续qmax的放入规则,说明arr[j]不可能是之后向右滑动窗口的最大值
- 如果arr[j]>arr[i],将小标i,加入到qmax队尾中,由于arr[i]可能是之后向右滑动窗口的最大值,
2. 要清除队列中最大值的失效值。当遍历的数组元素下标i,qmax队首元素为j,窗口的长度w,之间满足j=i-w;也就是元素arr[j]最大值已经失效了
3. 只要遍历数组元素下标i满足i>w-1;将qmax队首元素为某个窗口的最大值,保存即可。

public int[] getMaxWindow(int[]arr,int w){
    if(arr==null||arr.length0)
        return null;
    int index=0;
    int res=new int[arr.length-w+1];
    LinkedList qmax=new LinkedList<>();
    for(int i=0;iwhile(!qmax.isEmpty()&&arr[i]>=arr[qmax.peekLast()])
    {
            qmax.pollLast();
    }
    qmax.addLast(i);
    if(i-w==qmax.peekFirst())//清除失效最大值
        qmax.pollFirst();
    if(i>w-1)
        res[index++]=arr[qmax.peekFirst()];
    }
    return res;
}

求最大子矩阵的大小

题目描述

给定一个整型矩阵map,其中的值只有0,1;求其中全是1的所有矩形区域中,最大的矩形区域为1的数量。

例如 1 1 1 0 ,最大的矩形区域有3个1,返回1
map=[1 0 1 1 
     1 1 0 1 
     1 1 1 1]
其中最大的矩形区域有4个1,返回4

思路解析

如果矩阵的大小为O(NM),本题的时间复杂度为O(NM);
1. 矩阵的行数为N,以每一行做切割,统计以当前作为底的情况,每个位置上为1的数量,使用高度数组height表示,
- 以题目描述的map为例,从第一行做切割,height=[1 0 1 1]
- 以第二行做切割,更新height;height=[2 1 0 2];height的更新操作为height[i]=map[i][j]==0?0:height[j]+1;
- 以第三行做切割,更新height;height=[3 2 1 3]
2. 对于每次切割,利用更新的height数组求出以每一行为底的情况下,最大的矩形是什么
- 求解当前height数组组成的最大矩形,比如{3,2 ,3, 1},看出一个数组组成的直方图
1. 第1根高度为3的柱子无法向左扩展,右边为2, 也无法向右左扩展,则以第1根柱子为高度的矩形面积为3*1=3;
2. 第2根高度为2的柱子向左可以扩展一个距离,向右扩展1个距离,则以第2根柱子为高度的矩形面积为2*3=6;
3. 同理,以第3根柱子为高度的矩形面积为3*1=3
4. 同理,以第4根柱子为高度的矩形面积为1*4=4

 - 使用栈来实现上述操作

     1. 生成一个栈,记为stack,每遍历一个位置都会把位置压入到stack中
     2. 当遍历到arr[i],栈为空时,直接压入,若栈不为空,比较栈顶位置所代表的值与arr[i]大小,若arr[i]较大,直接压入stack中;否则则把栈中存的位置不断弹出,直到某一个栈顶所代表的值小于arr[i],再把位置i压入
     3. 在遍历到数组arr[i],弹出的栈顶位置为j,弹出栈顶之后,新的栈顶元素为k,那么可以知道以arr[j]为高度的柱子向右最多可以扩展到(i-1),因为j之所以被弹出,是由于遇到了**第一个**比位置j值小的位置;向左最多扩展到k+1;这是因为栈中k,j位置是相邻的,并且从栈顶到栈底的位置所代表的的值依次递减并且无重复的,由于k位置在栈中,arr[k+1...j-1]都是既大于arr[k],否则k会被弹出;同时由于j在栈中,并且与k相邻,所以arr[k+1...j-1]没有小于arr[j],否则j不可能与k相邻。
     4. j位置的柱子能扩出来的最大矩形为(i-k-1)arr[j]
public int maxSize(int[][]map){
    if(map==null|||map.length==0||map[0].length==0)
        return 0;
    int maxArea=0;
    int []height=new int[map[0].length];
    for(int i=0;ifor(int j=0;j0].length;j++){
            height[j]=map[i][j]==0?0:height[j]+1;
        }
        maxArea=Math.max(maxArea,maxSizeFromBottom(height));
    }
    return maxArea;
}
public int maxSizeFromBottom(int [] height){
    if(height==null||height.length==0)
        return 0;
    int maxArea=0;
    Stack stack=new Stack<>();
    for(int i=0;iwhile(!stack.isEmpty()&&height[i]<=height[stack.peek()]){
            int j=stack.pop();
            int k=stack.isEmpty()?-1:stack.peek();
            int cur=height[j]*(i-k-1);
            maxArea=Math.max(cur,maxArea);
        }
        stack.push(i);
    }
        while(!stack.isEmpty()){
            int j=stack.pop();
            int k=stack.isEmpty()?-1:stack.peek();
            int cur=(height.length-k-1)*height[j];
            maxArea=Math.max(cur,maxArea);
        }
        reuturn maxArea;
}

最大值值减去最小值小于或等于num的子数组的数量

题目描述

给定数组arr和整数num,共返回多少个数组子数组的满足如下情况:
max(arr[i...j])-min(arr[i..j])<=num
要求:
    如果数组的长度为N,时间复杂度为ON)的解法

思路解析

使用双端队列实现,qmax维护窗口arr[i…j]的最大值更新的结构,qmin维护窗口子数组arr[i..j]最小值更新结构,
两个基本的结论:
- 如果子数组arr[i…j]满足条件,那么arr[i..j]的子数组arrk…l都满足条件
- 反之arr[i…j]不满足条件,那么包含该数组arr[i..j]的子数组必然不满足条件。

具体实现步骤:

  1. 生成双端队列qmax,qmin.生成两个整型变量i,j表示子数组的arr[i..j]
  2. 令j不断向右移动位置,不断跟新qmax,qmin结构,保证qmax,qmin位置窗口的最大值与最小值,一旦出现arr[i…j]不满足条件,说明arr[i…j-1],arr[i..j-2]….arr[i…i]都是满足条件的
  3. 执行完步骤2,令i++;并对qmax,qmin进行调整,qmax,qmin变为arr[i+1…j]的最大值,最小值的更新结构
  4. 根据步骤2,3,得到以arr[0]….arr[N-1]作为第一个元素的子数组满足条件的数量分别是多少
public int getNum(int[] arr,int num){
    if(arr==null||arr.length==0){
        return 0;
    }
    LinkedList qmin=new LinkedList<>();
    LinkedList qmax=new LinkedList<>();
    int i=0,j=0;
    int res=0;
    while(iwhile(jwhile(!qmin.isEmpty()&&arr[j]<=arr[qmin.peekLast()]){
                qmin.pollLast();
        }
            qmin.addLast(j);
            while(!qmax.isEmpty()&&arr[j]>=arr[qmax.peekLast()]){
                qmax.pollLast();
            }
            qmax.addLast(j);
            if(arr[qmax.peekFirst()]-arr[qmin.peekFirst()]>num)
                break;
            j++;
        }
        if(qmin.peekFist()==i)
            qmin.pollFirst();
        if(qmax.peekFirst()==i)
            qmax.pollFirst();
        res+=j-i;
        i++;
    }
    return res;
}

你可能感兴趣的:(算法与数据结构,栈,队列,最优化)