代码随想录day52|84柱形图中最大的矩形|01笔记

189 阅读4分钟

image.png

  • 观察图片所示。我们可以发现,所有的矩形的的高度都是由其横跨的柱的高度中最小的柱子来决定的。因此,我们只要遍历所有柱子作为最小柱的矩形进行比较,就能找到答案。那么我们找到以当前柱子为高度的矩形时,如何找到宽度呢?因为当前柱子要为最小柱,所以只要向两边查找,当出现比当前柱子小的时我们停止,那么根据两边停留的位置我们就可以知道宽了。
  • 暴力解法代码如下
  •     func largestRectangleArea(heights []int) int {
            res := 0
            for i:=0;i<len(heights);i++ {
                left:=i
                right:=i
                for ;left>=0;left-- {
                    if heights[left] < heights[i] {
                        break
                    }
                }
                
                for ;right<len(heights);right++{
                    if heights[right] < heights[i] {
                        break
                    }
                }
                
                w := right-left -1
                h := heights[i]
                res = max(res, w * h)
            }
            return res
        }
        
        func max(x int, y int) int {
            if x>y {
                return x
            } else {
                return y
            }
        }
    
  • 但是这种办法是超时的,时间复杂度为O(n^2)
  • 双指针

  • 为了省去在每个位置向两头查找的时间。我们可以存储两个数组,代表每个元素左右两边距离最近的小于它的柱子的交标。这样我们就可以直接计算每一个位置作为最低值的面积了。
  •     func largestRectangleArea(heights []int) int {
            len := len(heights)
            leftminIndex := make([]int, len)
            rightminIbdex := make([]int, len)
              // 记录每个柱子 左边第一个小于该柱子的下标
            leftminIndex[0] = -1//注意这里初始化,防止下面死循环
            for i:=1;i<len;i++ {
                t := i-1 
                for t>=0 && heights[t]>=heights[i] {
                    t = leftminIndex[t]
                }
                leftminIndex[i] = t
            }
            // 记录每个柱子 右边第一个小于该柱子的下标
            rightminIbdex[len-1] = len// 注意这里初始化,防止下面死循环
            for i:=len-2;i>=0;i-- {
                t := i + 1
                for t < len && heights[t] >= heights[i] {
                    t = rightminIbdex[t]
                }
                rightminIbdex[i] = t
            }
            result := 0
            for i:=0;i<len;i++ {
                area := heights[i] * (rightminIbdex[i] - leftminIndex[i] -1)
                result = max(result, area)
            }
            return result
        }
        
        func max(x int, y int) int {
            if x>y {
                return x
            } else {
                return y
            }
        }
    
  • 单调栈

  • 使用单调栈可以找到当前元素下一个出现的大于(小于)的元素。而我们能找到的事大于还是小于,取决于单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小
  • 本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!这样当出现第一个小于的元素是,我们就可以触发连续的弹出操作。
    image.png
  • 大家可以发现栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度
  • 接下来就是分析清楚如下三种情况:
  • 情况一:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
    • 直接压入
  • 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
    • 直接压入
  • 情况三:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况
    • 依次弹出,并计算相应的面积
  • 需要注意的是,如果我们的heights数组出现了单调递增或递减的情况,哪门我们会得不到所有的结果。
    • 全部单调递增时,无法触发弹出操作

image.png -
- 我们只要在heights结尾加一个0,就会让栈里的所有元素,走到情况三的逻辑。
- 如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),rigt(6),但是得不到 left。

image.png - 所以我们需要在 height数组前面也加一个元素0。所以我们需要在 height数组前后各加一个元素0。

  • 解题代码

  •     func largestRectangleArea(heights []int) int {
         // 声明max并初始化为0
         max := 0
         // 使用切片实现栈
         stack := make([]int, 0)
         // 数组头部加入0
         heights = append([]int{0}, heights...)
         // 数组尾部加入0
         heights = append(heights, 0)
         // 初始化栈,序号从0开始
         stack = append(stack, 0)
         for i := 1; i < len(heights); i++ {
          // 结束循环条件为:当即将入栈元素>top元素,也就是形成非单调递增的趋势
          for heights[stack[len(stack)-1]] > heights[i] {
           // mid 是top
           mid := stack[len(stack)-1]
           // 出栈
           stack = stack[0 : len(stack)-1]
           // left是top的下一位元素,i是将要入栈的元素
           left := stack[len(stack)-1]
           // 高度x宽度
           tmp := heights[mid] * (i - left - 1)
           if tmp > max {
            max = tmp
           }
          }
          stack = append(stack, i)
         }
         return max
        }