文章目录
23, 84. 柱状图中最大的矩形
思路1: 暴力解法
思路2:双指针法
思路3:思路2优化:跳过不需要的指针递减或者递增
思路4: 单调栈
思路5:思路4优化:一次遍历,同时找到左右边界
package com.shangguigu.dachang.algrithm.A07_queue_and_stack;
import java.util.Stack;
/**
* @author : 不二
* @date : 2022/4/16-上午11:34
* @desc : 84. 柱状图中最大的矩形
* https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
**/
public class A73_largestRectangleArea {
public static void main(String[] args) {
int[] heights = {2, 1, 5, 6, 2, 3};
// int[] heights = {2, 3};
// int[] heights = {2, 4};
// int[] heights = {1, 1};
// int i = largestRectangleArea(heights);
// int i = largestRectangleArea_v1(heights);
// int i = largestRectangleArea_v2(heights);
// int i = largestRectangleArea_v3(heights);
int i = largestRectangleArea_v4(heights);
System.out.println("结果是:" + i);
}
/**
* 思路5:思路4优化:一次遍历,同时找到左右边界
* 遍历求出左边界的同时:应该知道:
* 第一次弹出栈的那个数据就是被弹索引数据的右边界
*
* 这个好难理解。。。。。。
*
*/
public static int largestRectangleArea_v4(int[] heights){
int maxArea = 0;
int[] leftBoundary = new int[heights.length];
int[] rightBoundary = new int[heights.length];
// 先初始化右边界。只要没有被弹出来,说明右边的数据都比当前的这个大,右边界就是结尾。
for (int i = 0; i < heights.length; i++) {
rightBoundary[i] = heights.length-1;
}
Stack<Integer> stack = new Stack<>();
// 根据栈结构进行计算左边界
for (int i = 0; i < heights.length; i++) {
// 如果当前栈顶的元素是大于等于 当前元素的 --> 说明是值下降来
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
// 这里是优化点:被弹的数据的有边界必然是i
// 因为i数据小于peek数据,peek是i的左边界
// 相对的:peek 数据 大于i数据,那么从右往左看, peek的右边界就是i-1
rightBoundary[stack.peek()] = i-1;
// 把当前元素弹出
stack.pop();
}
// 当弹出结束后,此时栈顶的元素就是:左边界的索引了
// 如果栈中为空了,那么左边界就是从0开始
leftBoundary[i] = stack.isEmpty() ? 0 : stack.peek() + 1;
// 把索引i放入栈中
stack.push(i);
}
// 这里直接在第一次遍历的时候直接计算出右边界,这里就不需要了
// 到这里的话,stack的作用已经完成,左边界已经计算结束
/*stack.clear();
// 根据栈结构计算右边界
for (int i = heights.length - 1; i >= 0; i--) {
// 如果当前栈顶的元素是大于等于 当前元素的 --> 说明是值下降来
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
// 把当前元素弹出
stack.pop();
}
// 当弹出结束后,此时栈顶的元素就是:左边界的索引了
// 如果栈中为空了,说明刚才遍历过的所有值都比当前值大,那么左边界就是从最右开始
rightBoundary[i] = stack.isEmpty() ? heights.length-1 : stack.peek() - 1;
// 把索引i放入栈中
stack.push(i);
}*/
// System.out.println("-------");
// 根据左右边界和高度计算面积
for (int i = 0; i < heights.length; i++) {
int height = heights[i];
int left = leftBoundary[i];
int right = rightBoundary[i];
int area = height * (right - left + 1);
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
/**
* 思路4: 单调栈
*
* 把寻找左右边界的过程使用栈来实现
*
*/
public static int largestRectangleArea_v3(int[] heights){
int maxArea = 0;
int[] leftBoundary = new int[heights.length];
int[] rightBoundary = new int[heights.length];
Stack<Integer> stack = new Stack<>();
// 根据栈结构进行计算左边界
for (int i = 0; i < heights.length; i++) {
// 如果当前栈顶的元素是大于等于 当前元素的 --> 说明是值下降来
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
// 把当前元素弹出
stack.pop();
}
// 当弹出结束后,此时栈顶的元素就是:左边界的索引了
// 如果栈中为空了,那么左边界就是从0开始
leftBoundary[i] = stack.isEmpty() ? 0 : stack.peek() + 1;
// 把索引i放入栈中
stack.push(i);
}
// 到这里的话,stack的作用已经完成,左边界已经计算结束
stack.clear();
// 根据栈结构计算右边界
for (int i = heights.length - 1; i >= 0; i--) {
// 如果当前栈顶的元素是大于等于 当前元素的 --> 说明是值下降来
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
// 把当前元素弹出
stack.pop();
}
// 当弹出结束后,此时栈顶的元素就是:左边界的索引了
// 如果栈中为空了,说明刚才遍历过的所有值都比当前值大,那么左边界就是从最右开始
rightBoundary[i] = stack.isEmpty() ? heights.length-1 : stack.peek() - 1;
// 把索引i放入栈中
stack.push(i);
}
// System.out.println("-------");
// 根据左右边界和高度计算面积
for (int i = 0; i < heights.length; i++) {
int height = heights[i];
int left = leftBoundary[i];
int right = rightBoundary[i];
int area = height * (right - left + 1);
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
/**
* 思路3:思路2优化
* 思路2的时间复杂度也是O(n*n)
* 如果左边数字比当前位置大,那么直接跳到左边数字的左边界
* 如果右边数字也比当前位置大,那么直接跳到右边数字大右边界
*/
public static int largestRectangleArea_v2(int[] heights){
int maxArea = 0;
int[] leftBoundary = new int[heights.length];
int[] rightBoundary = new int[heights.length];
// 先找左边界,记录下来----得从左往右找
for (int i = 0; i < heights.length; i++) {
int left = i;
while (left > 0 && heights[i] <= heights[left-1]) {
// left--;
// 这里重点!!!
left = leftBoundary[left - 1];
}
// 如果走出来,说明已经找到左边界
leftBoundary[i] = left;
}
// 再找右边界,记录下来----得从右往左找
for (int i = heights.length - 1; i >= 0; i--) {
// int height = heights[i];
int right = i;
while (right < heights.length - 1 && heights[i] <= heights[right + 1]) {
// left--;
// 这里重点!!!
right = rightBoundary[right + 1];
}
// 如果走出来,说明已经找到左边界
rightBoundary[i] = right;
}
// 根据左右边界和高度计算面积
for (int i = 0; i < heights.length; i++) {
int height = heights[i];
int left = leftBoundary[i];
int right = rightBoundary[i];
int area = height * (right - left + 1);
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
/**
* 思路2:双指针法
* 指定高度,横向扩展看下能扩展到什么地方
*
*/
public static int largestRectangleArea_v1(int[] heights){
int leftPointer,rightPointer;
int maxArea = 0;
for (int i = 0; i < heights.length; i++) {
// 把i位置的数据当成高度(也是是最低),横向扩展。
int theHeight = heights[i];
leftPointer = rightPointer = i;
while (leftPointer > 0 && heights[i] <= heights[leftPointer - 1]) {
leftPointer--;
}
while (rightPointer < heights.length-1 && heights[i] <= heights[rightPointer + 1]) {
rightPointer++;
}
int wide = (rightPointer-leftPointer)+1;
int area = theHeight * wide;
maxArea = Math.max(area, maxArea);
}
return maxArea;
}
/**
* 思路1:暴力解法
* 这里是根据遍历宽度,找到对应的高度
*
*/
public static int largestRectangleArea(int[] heights) {
int area = 0;
for (int i = 0; i < heights.length; i++) {
int minHeight = Integer.MAX_VALUE;
// 遍历宽度,每个宽度找到其对应的高度,算出面积
for (int j = i; j < heights.length; j++) {
// 找到这组数据中最小的高度
minHeight = Math.min(heights[j], minHeight);
int wide = j - i + 1;
int areaS = wide * minHeight;
area = Math.max(area, areaS);
}
}
return area;
}
}