✿-10例题 栈、哨兵--柱状图中最大的矩形-84
一、题目
二、Java代码
package shuJuJieGou.Zhan; import java.util.ArrayDeque; import java.util.Deque; /** * @Author : ASUS and xinrong * @Version : 2020/11/9 & 1.0 * 栈、哨兵 * 柱状图中最大的矩形-84 * * 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 * 求在该柱状图中,能够勾勒出来的矩形的最大面积。 * * 思考: * 这道题和盛水最多的容器很像, * 不同的是盛水问题只关注两端的柱子高度即可,而勾勒出矩形问题是要确保中间无悬空,这使问题加大了难度。 * 1)审题:能够勾勒出的矩形height取决于最短的柱子的高(注意勾勒矩形不能有镂空),宽则取决于柱子的个数。 * 2)解法: * (1)暴力破解:枚举出基于各个柱子能形成的最大矩形面积,两边延伸探测左右边界 * (2)栈(单调栈):主要的思想还是和枚举的相似,都是要得到每个柱子的左右边界。 * 维护一个栈(栈底到栈顶,元素升序排列),栈内存的是位置信息 * 即将入栈的元素如果比栈顶元素大,入栈 。因为栈顶元素的右边界还可以再延伸。 * 即将入栈的元素如果比栈顶元素小,出栈 。因为栈顶元素的右边界到此确定了, * 左边界即为出栈后的栈顶元素。 */ public class LargestRectangleInHistogram { /** * 法一:单调栈 (时间复杂度:O(n)-输入数组的各个元素入栈、出栈各一次;空间复杂度:O(n)-栈的空间最多为n) * @param heights * @return */ public int largestRectangleArea(int[] heights) { //特殊情况判断 if (heights == null) { return 0; } //在柱体数组的头和尾加两个高度为0的柱体 int[] tmp = new int[heights.length + 2]; // Object src : 原数组 // int srcPos : 从元数据的起始位置开始 // Object dest : 目标数组 // int destPos : 目标数组的开始起始位置 // int length : 要copy的数组的长度 System.arraycopy(heights, 0, tmp, 1, heights.length); //栈内,存的是柱体的位置 Deque<Integer> deque = new ArrayDeque<>(); //因为下面的while循环里的非空判断仅在最开始栈空的情况下有用,所以可用哨兵省略它 deque.addFirst(0); int area = 0; //开始遍历每个柱子的左右边界 for (int i = 0; i < tmp.length; i++) { //对于栈中柱体来说,栈中的下一个柱体的位置就代表它的左边界 //因此,以栈顶柱体为高的矩形的左右边宽度边界就确定了,接下来要计算面积。 while (tmp[i]<tmp[deque.peekFirst()]) { int h = tmp[deque.pollFirst()]; //出栈就意味着这个柱子的左右边界已确定,可以求出以它为基础能够勾勒出来的矩形的最大面积 area = Math.max(area, (i - deque.peekFirst() - 1) * h); } deque.addFirst(i); } return area; } /** * 法二:暴力破解法-枚举 * @param heights * @return * 复杂度分析: * 时间复杂度:O(N^2),这里 N 是输入数组的长度。 * 遍历每个柱子为O(n);每个柱子左右探测其边界为O(n)--最糟糕情况:可能全都需要探测一下 * 空间复杂度:O(1)。 */ public int largestRectangleArea2(int[] heights) { //特殊情况: if (heights == null) { return 0; } //勾勒出来的矩形的最大面积 int max = 0, length = heights.length; for (int i = 0; i < length ; i++) { int height = heights[i]; //左右逐渐探测 int left = i; while (left > 0 && heights[left-1] >= height) { left--; } int right = i; while (right < length - 1 && heights[right+1] >= height) { right++; } max = Math.max(heights[i] * (right - left + 1), max); } return max; } public static void main(String[] args) { LargestRectangleInHistogram largestRectangleInHistogram = new LargestRectangleInHistogram(); System.out.println(largestRectangleInHistogram.largestRectangleArea(new int[]{2,1,5,6,2,3})); System.out.println(largestRectangleInHistogram.largestRectangleArea(new int[]{1})); } }
三、帮助理解
while (tmp[i]<tmp[deque.peekFirst()]) { int h = tmp[deque.pollFirst()]; //出栈就意味着这个柱子的左右边界已确定,可以求出以它为基础能够勾勒出来的矩形的最大面积 area = Math.max(area, (i - deque.peekFirst() - 1) * h); //问题-1:怎么就确定出栈的元素左侧就没有大于等于它的元素? // 因为:入栈的时候就是只进入比栈顶元素大或等于的元素 //问题-2:对于中间小的柱子,怎么确定? // 中间小的,可对栈内比他大的元素进行出栈操作,之后栈内它前面的元素一定比他小, // 而且他俩之间的位置差再-1就是之前大于等于这个矮柱子的元素(可以一起勾勒了矩形)个数 //问题-3:为什么要-1呢? // 右边界所对应的元素小于左侧柱子大小,因此减去它 }
图解(程序加哨兵前)