算法与数据结构——单调栈(Java)(b站左程云课程笔记总结)

单调栈

解决问题:一个数组中每一个数,得到左边最近的比它大的和右边最近的比它大的数(小的话同理),代价尽量低

image-20220624170138776

经典方法:来到每一个数左边遍历,右边遍历,O(N^2)

单调栈流程(求大的)

准备一个栈:要求栈的规则是从栈底到栈头是大到小

如果数组中的数加入栈的时候能满足规则就直接加入,不满足规则时,每弹出一个数就可以收集信息了,弹出的数的左边最近的比它大的数就是栈中该数的下一个,右边最近的比它大的数就是准备加入栈的数(谁让你弹出的数)

数组遍历完时如果栈中还有数据,就进入清算阶段,依次弹出栈中的数并收集信息,此时栈中的每一条数据都没有右边最近的比它大的数据

时间复杂度:每个数据都只进栈一次,出栈一次,所以为O(N)

image-20220624170618836

public int[][] getNearBiggerNoRepeat(int[] arr){
    int[][] res=new int[arr.length][2];
    Stack<Integer> stack=new Stack<>();
    for(int i=0;i<arr.length;i++){
        while(!stack.isEmpty()&&arr[stack.peek()]<arr[i]){
            int popIndex=stack.pop();
            int leftBiggerIndex=stack.isEmpty()?-1:stack.peek();
            res[popIndex][0]=leftLessIndex;
            res[popIndex][1]=i;
        }
        stack.push(i);//添加操作不要忘记了
    }
    while(!stack.isEmpty()){
        int popIndex=stack.pop();
        int leftBiggerIndex=stack.isEmpty()?-1:stack.peek();
        res[popIndex][0]=leftLessIndex;
        res[popIndex][1]=-1;
    }
    return res;
}

以上是针对没有重复值的数组

如果是有重复值的数组:

栈中存链表,链表中存索引,过程类似,收集信息的时候收集的是链表中最后一个索引,两个数相等时索引存在链表后面

image-20220624172302229

public int[][] getNearBigger(int[] arr){
    int[][] res=new int[arr.length][2];
    Stack<ArrayList<Integer>> stack=new Stack<>();
    for(int i=0;i<arr.length;i++){
        while(!stack.isEmpty()&&arr[stack.peek().get(0)]<arr[i]){
            ArrayList<Integer> popList=stack.pop();
            int leftBiggerIndex=stack.isEmpty()?-1:stack.peek().get(stack.peek().size()-1);
            for(Integer num:popList){
                res[num][0]=leftBiggerIndex;
                res[num][1]=i;
            }
        }
        //添加操作
        if(!stack.isEmpty()&&arr[stack.peek().get(0)]==arr[i]){
            stack.peek().add(Integer.valueOf(i));
        }else{
            ArrayList<Integer> list=new ArrayList<>();
            list.add(i);
            stack.push(list);
        }
    }
    
    while(!stack.isEmpty()){
        ArrayList<Integer> list=stack.pop();
        int leftBiggerIndex=stack.isEmpty()?-1:stack.peek().get(stack.peek().size()-1);
        for(Integer num:list){
            res[num][0]=leftBiggerIndex;
            res[num][1]=-1;
        }
    }
    return res;
}

题目

image-20220624180225054

要求:数组中的数都是正数

大流程:在每个数都是自己子数组的最小值的前提下,哪个子数组能求得最大累加和,答案一定在其中

单调栈(小到大)

每个数的左边最近的比它小的数是不能扩到的位置,右边最近的比它小的数是不能扩到的位置,通过这两个信息就可以得到每个数能扩的范围

image-20220624181320933

public int getMax(int[] arr){
    int size=arr.length;
    int[] sums=new int[size];
    sums[0]=arr[0];
    for(int i=1;i<size;i++){
        sums[i]=sums[i-1]+arr[i];//sums数组中存的是每个数之前的数的累加和
    }
    int max=Integer.MIN_VALUE;
    Stack<Integer> stack=new Stack<>();
    for(int i=0;i<size;i++){
        while(!stack.isEmpty()&&arr[stack.peek()]>=arr[i]){//小到大规则(相等的时候也进行操作)
            int j=stack.pop();
            max=Math.max(max,(stack.isEmpty()?sums[i-1]:(sums[i-1]-sums[stack.peek()]))*arr[j]);//如果栈是空的说明左边没有比它小的数,而此时进来的数比它小,所以该数最小的子数组的值即为sums数组中[i-1]位置上对应的值*arr[j],否则使用(sums[i-1]-sums[stack.peek()])*arr[j]
        }
        stack.push(i);
    }
    while(!stack.isEmpty()){
        int j=stack.pop();
        max=Math.max(max,(stack.isEmpty()?sums[size-1]:(sums[size-1]-sums[stack.peek()]))*arr[j]);//此时在栈中的数右边都没有比它小的数,如果栈为空说明它是数组中最小的数;栈不为空说明左边有比它小的数
    }
    return max;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值