import java.util.Stack;

public class LargestRectangleInHistorgram {
    //方法一:暴力法(遍历所以可能的宽度)
    public int largestRectangleArea1(int[] heights) {
        //定义变量保存最大面积
        int largestArea= 0;

        //遍历数组,作为矩形左边界
        for (int left = 0; left <heights.length ; left++) {
            //定义变量保存当前矩形高度
            int currHeight = heights[left];

            //遍历数组,选取矩形右边界
            for (int right = left;right<heights.length;right++){
                //确定 当前矩形高度
                currHeight = (heights[right]<currHeight)?heights[right]:currHeight;
                //计算当前矩形面积
                int currArea = (right-left+1)*currHeight;
                //更新最大面积
                largestArea = (currArea>largestArea)?currArea:largestArea;
            }
        }
        return largestArea;
    }

    //方法二:双指针(遍历所以可能的高度)
    public int largestRectangleArea2(int[] heights) {
        //定义变量保存最大面积
        int largestArea= 0;

        //遍历数组,以每个柱子高度作为最终矩形的高度
        for (int i = 0; i <heights.length ; i++) {
            //保存当前高度
            int height = heights[i];

            //定义左右指针
            int left = i;
            int right = i;

            //寻找左右边界,左指针左移
            while(left>=0){
                if(heights[left]<height){
                    break;
                }
                left--;
            }

            //寻找左右边界,右指针右移
            while(right<heights.length){
                if(heights[right]<height){
                    break;
                }
                right++;
            }

            //计算宽度
            int width = right-left-1;

            //计算面积
            int currArea = height*width;

            largestArea = currArea>largestArea?currArea:largestArea;
        }
        return largestArea;
    }
    //方法三:双指针法改进
    public int largestRectangleArea3(int[] heights) {
        //定义变量保存最大面积
        int largestArea= 0;

        //定义两个数组,保存每个柱子对应的左右边界
        int n = heights.length;
        int[] lefts = new int[n];
        int[] rights = new int[n];

        //遍历数组,计算左边界
        for (int i = 0; i <n ; i++) {
            //保存当前高度
            int height = heights[i];

            //定义左右指针
            int left = i -1;

            //左指针左移,寻找左边界
            //寻找左右边界,左指针左移
            while(left>=0){
                if(heights[left]<height){
                    break;
                }
                left = lefts[left];//如果左边柱子更高,就直接跳到左边界柱子判断
            }

            lefts[i]= left;
        }
        //遍历数组,计算右边界
        for (int i = n-1; i >=0 ; i--) {
            //保存当前高度
            int height = heights[i];

            //定义左右指针
            int right = i+1;

            //右指针右移,寻找右边界
            while(right<n){
                if(heights[right]<height){
                    break;
                }
                right = rights[right];//如果左边柱子更高,就直接跳到左边界柱子判断
            }

            rights[i]= right;
        }

        //遍历所有柱子计算面积
        for (int i = 0; i <n ; i++) {
            int currArea = (rights[i]-lefts[i]-1)*heights[i];
            largestArea = currArea>largestArea?currArea:largestArea;
        }
        return largestArea;
    }

    //方法四:单调栈
    public int largestRectangleArea4(int[] heights) {
        //定义变量保存最大面积
        int largestArea= 0;

        //定义两个数组,保存每个柱子对应的左右边界
        int n = heights.length;
        int[] lefts = new int[n];
        int[] rights = new int[n];

        //定义一个栈
        Stack<Integer> stack = new Stack<>();

        //遍历所有柱子,作为当前高度,先找左边界
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty()&&heights[stack.peek()]>=heights[i]){
                stack.pop();
            }

            //所有大于等于当前高度的元素全部弹出,找到了左边界
            lefts[i] = stack.isEmpty()?-1:stack.peek();

            stack.push(i);
        }
        stack.clear();
        //遍历所有柱子,作为当前高度,先找右边边界
        for (int i = n-1; i>=0; i--) {
            while (!stack.isEmpty()&&heights[stack.peek()]>=heights[i]){
                stack.pop();
            }
            //所有大于等于当前高度的元素全部弹出,找到了左边界
            rights[i] = stack.isEmpty()?n:stack.peek();

            stack.push(i);
        }
        //遍历所有柱子计算面积
        for (int i = 0; i <n ; i++) {
            int currArea = (rights[i]-lefts[i]-1)*heights[i];
            largestArea = currArea>largestArea?currArea:largestArea;
        }
        return largestArea;
    }
    //方法五:单调栈优化
    public int largestRectangleArea(int[] heights) {
        //定义变量保存最大面积
        int largestArea= 0;

        //定义两个数组,保存每个柱子对应的左右边界
        int n = heights.length;
        int[] lefts = new int[n];
        int[] rights = new int[n];

        //初始化rights为右哨兵n
        for (int i = 0; i <n ; i++) {
            rights[i] = n;
        }

        //定义一个栈
        Stack<Integer> stack = new Stack<>();

        //遍历所有柱子,作为当前高度,先找左边界
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty()&&heights[stack.peek()]>=heights[i]){
                //当前栈顶元素如果小于当前元素,那么它的右边界就是当前元素
                rights[stack.peek()]=i;
                stack.pop();
            }

            //所有大于等于当前高度的元素全部弹出,找到了左边界
            lefts[i] = stack.isEmpty()?-1:stack.peek();

            stack.push(i);
        }
        //遍历所有柱子计算面积
        for (int i = 0; i <n ; i++) {
            int currArea = (rights[i]-lefts[i]-1)*heights[i];
            largestArea = currArea>largestArea?currArea:largestArea;
        }
        return largestArea;
    }

    public static void main(String[] args) {
        LargestRectangleInHistorgram largestRectangleInHistorgram = new LargestRectangleInHistorgram();
        int[] heights = {2,1,5,6,2,3};
        System.out.println(largestRectangleInHistorgram.largestRectangleArea(heights));
    }
}