单调栈结构???

704 阅读3分钟

前提假设

在数组中想找到一个数,左边和右边比这个数小、且离这个数最近的位置。比如:1 5 4 3 2 8 6, 1的左边最近比它小是无,右边最近比它小是无。5的左边最近比它小是1,右边最近比它小是4, 4的左边最近比它小是1,右边最近比它小是3...

传统的想法是遍历,这样的时间复杂度是O(N^2),那么有没有O(N)的方法呢?这就要用到单调栈结构了。

单调栈解答

当给的数中没有重复值时

image.png

相关代码

	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();
				//0位置储存左边最近最小的地址
				//1位置储存右边最近最小的地址
				res[popIndex][0] = leftLessIndex;
				res[popIndex][1] = i;
			}
			//栈为空,或者栈顶元素小于要加入的元素
			stack.push(i);
		}
		//最后的清算程序
		while (!stack.isEmpty()) {
			int popIndex = stack.pop();
			int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();
			res[popIndex][0] = leftLessIndex;
			//能到清算程序,说明没有要加入的元素了
			res[popIndex][1] = -1;
		}
		return res;
	}

当给的数中有重复值时

image.png

形成链表时不用int[],因为这个不支持扩展,可以用动态数组ArrayList或者双向链表LinkedList。

Stack<ArrayList<Integer>> 或者Stack<LinkedList<Integer>>

相关的代码

	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);
				//比如弹出的可能是很多位置的3,形成的链表,都要计算它的左边和右边
				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;
	}

时间复杂度

时间复杂度:因为每个数是只进一次出一次,所以是O(N)。

相关题目

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

思路解析

可以以数组中的每一位为参考,找到以当前位为最小值,左边界是最近比它小的数是它不能扩到的位置,右边界是最近比它小的数是它不能扩到的位置。就可以转换成单调栈的形式。

相关代码???没读太懂

	public static int max2(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];
		}
		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();
				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;
	}