技巧:使用哨兵(sentienel),避免特殊情况的讨论,比如单链表虚拟头结点、插入排序等。

84.柱状图中最大矩形.png

暴力破解

先确定高度,再确定宽度:循环遍历数组,即先确定高度,再向左、右确定每个序号的左、右边界,即宽度 width,那么面积 area = width * length。

  1. // 暴力法
  2. public int largestRectangleArea2(int[] heights) {
  3. if (heights.length == 0) return 0;
  4. int left, right;
  5. int maxArea = Integer.MIN_VALUE;
  6. for (int i = 0; i < heights.length; i++) {
  7. // 确定左指针
  8. left = i;
  9. right = i;
  10. while (left > 0 && heights[left - 1] >= heights[i]) {
  11. left--;
  12. }
  13. // 确定右指针
  14. while (right < heights.length -1 && heights[right + 1] >= heights[i]) {
  15. right++;
  16. }
  17. // 计算面积
  18. int a = (right - left + 1) * heights[i];
  19. maxArea = Math.max(maxArea, a);
  20. }
  21. return maxArea;
  22. }

单调栈

image.png
image.png

使用一个栈保持元素

  1. public int largestRectangleArea(int[] heights) {
  2. if (heights.length == 0) return 0;
  3. int maxArea = Integer.MIN_VALUE;
  4. int len = heights.length;
  5. Stack<Integer> stack = new Stack<>();
  6. for (int i = 0; i < len; i++) {
  7. // 出栈操作
  8. while (!stack.isEmpty() && heights[stack.peek()] > heights[i]) {
  9. // 栈顶>外部元素,证明找到栈顶的右边界
  10. int height = heights[stack.pop()];
  11. int width;
  12. if (stack.isEmpty()) {
  13. width = i; // 注意
  14. } else {
  15. width = i - stack.peek() - 1;
  16. }
  17. maxArea = Math.max(maxArea, width * height);
  18. }
  19. stack.push(i);
  20. }
  21. while (!stack.isEmpty()) {
  22. int height = heights[stack.pop()];
  23. int width;
  24. if (stack.isEmpty()) {
  25. width = len; // 注意
  26. } else {
  27. width = len - stack.peek() - 1;
  28. }
  29. maxArea = Math.max(maxArea, width * height);
  30. }
  31. return maxArea;
  32. }

优化:单调栈+哨兵(sentinel)

以上参考代码 2 需要考虑两种特殊的情况:
弹栈的时候,栈为空;
遍历完成以后,栈中还有元素;
为此可以我们可以在输入数组的两端加上两个高度为 0 (或者是 0.5,只要比 1 严格小都行)的柱形,可以回避上面这两种分类讨论。
这两个站在两边的柱形有一个很形象的名词,叫做哨兵(Sentinel)。
有两个柱形:

  1. 左边的柱形(第 1 个柱形)由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;
  2. 右边的柱形(第 2 个柱形)也正是因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)。 ```java public int largestRectangleArea(int[] heights) { if (heights.length == 0) return 0; int area = 0; int len = heights.length + 2; // 增加两个哨兵 int[] newHeights = new int[len]; newHeights[0] = 0; newHeights[len-1] = 0; System.arraycopy(heights, 0, newHeights, 1, len-2);

    Deque stack = new ArrayDeque<>(); stack.addLast(0); // 放入哨兵0 for (int i = 1; i < len; i++) {

    1. // 满足条件进行出栈计算:栈顶元素>外部元素
    2. int height, width;
    3. while (newHeights[stack.peekLast()] > newHeights[i]) {
    4. height = newHeights[stack.pollLast()];
    5. width = i - stack.peekLast() - 1;
    6. area = Math.max(area, height * width);
    7. }
    8. stack.addLast(i); // 入栈

    }

    return area;

} ```

单调栈练习

题目 解题思路
42. 接雨水(困难) 暴力解法、动态规划、双指针、单调栈(Java)
739.每日温度 暴力解法、单调栈
496. 下一个更大元素 I(简单) 暴力解法、单调栈
316. 去除重复字母(困难 栈 + 哨兵技巧
901. 股票价格跨度(中等) 单调栈
402. 移掉K位数字

581. 最短无序连续子数组

84.柱状图中最大矩形.png