单调栈的详解

目录

单调栈的引入

单调栈的详解

单调栈的应用 

单调栈的引入

       单调栈解决的问题是在数组中想找到一个数,左边和右边比这个数大或者小、且离这个数最近的位置。 如果对每一个数都想求这样的信息,能不能整体代价达到O(N)?

       对于这个问题的求解就需要用到单调栈的结构。

单调栈的详解

       单调栈的实现我们以找最小值为例,首先建立一个栈,栈从下到上维持从小到大的顺序,先考虑第一种情况,就是数组中没有重复值,此时从数组的第一个元素开始,压入栈内,栈内压入数组中元素的下标,以链表的形式保存,然后数组的第二个元素压入,如果第二个元素的值小于第一个元素的值,那么此时栈内的元素弹出栈,元素弹出栈时保留信息,此时它的左侧的最小值就是无,右侧离它最近的最小值就是要压入栈的值,因为只有比它小才会让它要弹出栈,同时也是离它最近的。如果不比它小,那么压入栈。对于后续元素的压入同样按照这样的流程,最终当没有元素再让栈中的元素弹出时,对栈中剩余的元素进行处理,栈中的栈顶元素弹出,此时它左侧的离它最近的最小值就是它下面压着的元素,如果下面没有元素就是空,右侧离它最近的最小值为空,同样它下面元素的右侧的最小值也是空。

        而如果数组中的元素有重复的值,那么同样的操作,只是当同样的值压入栈时,先进行检验,然后压入栈,如果下面有相同的值,那么它们放入同一个链表保存,弹出统计信息的操作也都是相同的。

       对于单调栈的结构能够实现对于每一个数可以在时间复杂度O\left ( N \right )的情况下找出离它最近的左侧和右侧的最小值是因为数组中的每一个值最多都进行了一次入栈和出栈操作,所以时间复杂度较低。而栈一直维持着单调的结构,所以找到的都是每个数离它最近的最小值。具体实现的代码如下:

    public static int[][] getNearLessNoRepeat(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 leftLessIndex = stack.isEmpty() ? -1 : stack.peek();//如果此时栈内的元素弹出以后栈为空,那么此时弹出元素的左侧的最小值为空,用-1代替,否则左侧最小值就是新的栈顶元素
				res[popIndex][0] = leftLessIndex;
				res[popIndex][1] = i;
			}
			stack.push(i);//栈中的元素都比i大时,i压入栈
		}
		while (!stack.isEmpty()) {//对栈中剩余的元素进行处理
			int popIndex = stack.pop();//弹出栈中的元素
			int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();//如果栈不为空,左边的离它最近的最小值为此时栈顶元素
			res[popIndex][0] = leftLessIndex;
			res[popIndex][1] = -1;//栈中元素右边的最小值无,记录为-1
		}
		return res;
	}

	public static int[][] getNearLess(int[] arr) {//得到有重复数组中每个值最近的左侧和右侧的最小值
		int[][] res = new int[arr.length][2];
		Stack<List<Integer>> stack = new Stack<>();
		for (int i = 0; i < arr.length; i++) {
			while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {//当栈不为空并且栈顶元素链表中第一个位置的值比要加入的值大时,栈顶元素弹出
				List<Integer> popIs = stack.pop();
				// 取位于下面位置的列表中,最晚加入的那个
				int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(
						stack.peek().size() - 1);
				for (Integer popi : popIs) {
					res[popi][0] = leftLessIndex;
					res[popi][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()) {
			List<Integer> popIs = stack.pop();
			// 取位于下面位置的列表中,最晚加入的那个
			int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(
					stack.peek().size() - 1);
			for (Integer popi : popIs) {
				res[popi][0] = leftLessIndex;
				res[popi][1] = -1;
			}
		}
		return res;
	}

单调栈的应用 

       定义:数组中累加和与最小值的乘积,假设叫做指标A。 给定一个都是正数的数组,请返回子数组中,指标A最大的值。

        对于这个问题的解决采用的是单调栈结构,从数组的第一个元素开始,保持以数组的每一个元素作为最小值扩充,得到指标A。具体实现的代码如下:

    public static int max(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[i]记录截止到目前的前面元素加自身的累加和
		int max = Integer.MIN_VALUE;
		Stack<Integer> stack = new Stack<Integer>();
		for (int i = 0; i < size; i++) {
			while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {//当栈不为空且栈顶元素大于等于要加入栈的元素时
				int j = stack.pop();//j记录弹出的弹出的栈顶元素
				max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (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;
	}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值