每日算法总结——单调栈

单调栈解决的问题

在数组中想找到一个数,左边和右边比这个数大/小、且离这个数最近的位置。

  • 比如数组[5,4,6,7,2,3]中:
    • 5:左边没有比它大的数,右边比它大的数为6
    • 4:左边比它大的数为5,右边比它大的数为6
  • 经典解法是遍历每个数,以这个数为中心向左右两边寻找,复杂度为 O ( N 2 ) O(N^2) O(N2)

如果对每一个数都想求这样的信息,能不能整体代价达到$O(N) $?需要使用到单调栈结构

单调栈结构的原理和实现

准备一个无重复元素的数组,假如对于数组的数,我们要求的是左边和右边比这个数大、且离这个数最近的位置。

准备一个栈stack,这个栈要保证从栈底到栈顶由大到小的,栈中存放的是数字在数组中的下标

  • 遍历数组,假设当前的数为arr[i]
  • 如果栈为空arr[i] < stack.peek(),则将arr[i]压入栈中(保证了栈的单调性)
  • 如果arr[i] > stack.peek(),这时候再压栈就会破坏单调性,所以需要弹出一些数据。当数据被弹出,它的信息(左右最大最近的数)就开始生成。假设a = stack.peek()就是即将要弹出的数
    • a左边最近且比它大的数为:栈中a后面的数(如果a是栈底元素的话,就没有左最近最大的数)。
    • a右边最近且比它大的数为:即将要压入的数,即arr[i]
  • 如果数组中的数已经遍历完,且栈不为空,则依次弹出栈中元素并生成信息,此时栈中弹出的所有的数都没有右边最近且比其大的数

复杂度分析:每个元素只进栈一次、出栈一次,所以时间复杂度为 O ( N ) O(N) O(N)

现在来证明一下
在这里插入图片描述

  • 假设此时c要进栈且c>aa要弹出且生成信息
  • 为什么a右最近且比其大的数就是c
    • 首先在数组中,c一定在a的右边(因为我们是从左往右依次遍历的a先于c进栈),ac之间可能存在一些其他的值。
    • ac之间是否可能存在一个数k满足k>a,即ka右最近且比其大的数
      • 不可能,因为如果k存在的话,k入栈的时候,a就会被弹出。
  • 为什么a左最近且比其大的数就是b
    • 栈是单调的,所以一定存在b > a
    • ba之间是否存在一个数k满足k>a,即ka左最近且比其大的数
      • 不可能,因为如果存在的话,放在a后面的就是不是b而是k

上面说的是数组无重复值的情况,那是否可以解决数组有重复值的情况?

  • 重复值放在一起即可,比如栈中放入链表,每个链表上的节点都是具有重复的值的下标

JavaCode:

public class GetLeftAndRightMax {
    /**
     * @param arr 要求的数组
     * @return 长度与arr相同
     *          · res[i][0]表示左边比这个数大,且离这个数最近的位置
     *          · res[i][1]表示右边比这个数大,且离这个数最近的位置
     */
    public static int[][] getLeftAndRightMax(int[] arr) {
        int[][] res = new int[arr.length][2];
        Stack<ArrayList<Integer>> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) {
            // 弹出栈中小于arr[i]的元素,并生成res中的信息
            while (!stack.isEmpty() && arr[stack.peek().get(0)] < arr[i]) {
                ArrayList<Integer> cur = stack.pop();
                for (Integer index : cur) {
                    res[index][0] = stack.isEmpty()?-1:stack.peek().get(0);
                    res[index][1] = i;
                }
            }
            // 向栈中压入arr[i]
            if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
                // 栈顶元素的值与arr[i]相等
                stack.peek().add(i);
            } else {
                // 栈顶元素的值与arr[i]不相等
                ArrayList<Integer> tmp = new ArrayList<>();
                tmp.add(i);
                stack.push(tmp);
            }
        }
        // 把栈中剩余的元素弹出并生成信息
        while (!stack.isEmpty()) {
            ArrayList<Integer> cur = stack.pop();
            for (Integer index : cur) {
                res[index][0] = stack.isEmpty()?-1:stack.peek().get(0);
                res[index][1] = -1;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(Arrays.deepToString(getLeftAndRightMax(new int[]{5, 4, 6, 5, 2, 3})));	// [[-1, 2], [0, 2], [-1, -1], [2, -1], [3, 5], [3, -1]]
    }
}
单调栈应用

【题目描述】:

  • 定义:数组中累积和与最小值的乘积,假设叫做指标A。
  • 给定一个数组,该数组元素均为正数,请返回子数组中,指标A最大的值。

【解题思路】:

  • 遍历数组中的元素,将遍历到的元素arr[i]看做是子数组中的最小值,则指标A最大时,就是子数组元素个数最多时。
  • 利用单调栈,找到arr[i]左边和右边最近且比它小的数,这两个数就是arr[i]对应子数组的左右边界(不包括),计算出子数组的指标A
  • 返回指标A最大的值
class Solution {
    public static int getMaxA(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        Stack<ArrayList<Integer>> stack = new Stack<>();
        int[][] arrSec = new int[arr.length][2];
        for (int i = 0; i < arr.length; i++) {
            while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
                List<Integer> list = stack.pop();
                for (Integer integer : list) {
                    arrSec[integer][0] = stack.isEmpty() ? 0 : stack.peek().get(0) + 1;
                    arrSec[integer][1] = i - 1;
                }
            }
            if (!stack.isEmpty() && arr[i] == arr[stack.peek().get(0)]) {
                stack.peek().add(i);
            } else {
                ArrayList<Integer> list = new ArrayList<>();
                list.add(i);
                stack.push(list);
            }
        }
        while (!stack.isEmpty()) {
            List<Integer> list = stack.pop();
            for (Integer integer : list) {
                arrSec[integer][0] = stack.isEmpty() ? 0 : stack.peek().get(0) + 1;
                arrSec[integer][1] = arr.length - 1;
            }
        }
        int max = -1;
        for (int i = 0; i < arrSec.length; i++) {
            int sum = 0;
            for (int j = arrSec[i][0]; j <= arrSec[i][1]; j++) {
                sum += arr[j];
            }
            max = Math.max(max, sum * arr[i]);
        }
        return max;
    }

    public static void main(String[] args) {
        System.out.println(getMaxA(new int[]{3, 4, 4, 1}));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值