赞
踩
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
}
};
题意
两层循环,依次向后遍历,一边遍历一边记录最小的高度,计算面积,记录最大面积
遍历结束后,所有的单个柱子的面积以及组合柱子的面积全部计算了一遍,那么最大面积也得到了,算法的时间复杂度是 O ( N 2 ) O(N^2) O(N2)
class Solution { public: int largestRectangleArea(vector<int>& heights) { int length = heights.size(); int minHeight, maxArea = 0, width = 1; for (int i = 0; i < length; ++i) { // 单个柱子的面积 maxArea = std::max(maxArea, heights[i] * 1); // 重置 高和宽 minHeight = heights[i]; width = 1; // 组合柱子的面积 for (int j = i + 1; j < length; ++j) { minHeight = std::min(minHeight, heights[i]); ++width; maxArea = std::max(maxArea, minHeight * width); } } return maxArea; } };
class Solution { public: int largestRectangleArea(vector<int>& heights) { int n = heights.size(); int ans = 0; // 枚举左边界 for (int left = 0; left < n; ++left) { int minHeight = INT_MAX; // 枚举右边界 for (int right = left; right < n; ++right) { // 确定高度 minHeight = min(minHeight, heights[right]); // 计算面积 ans = max(ans, (right - left + 1) * minHeight); } } return ans; } };
很明显,暴力解法1存在大量的重复计算和没必要的计算,因为内层循环的第一次遍历结束后,所有的柱子都遍历一次了,也就是计算机知道了每个柱子的高度。
那么计算最大面积时,可以跳过一些情况,不进行计算。
我们可以枚举每一个柱子,寻找它的左右边界,计算面积并记录最大面积,那么那些不是柱子的左右边界的地方,并没有计算面积,虽然算法的时间复杂度仍然是O(n^2),但相比于暴力求解法1,减少了大量的计算。
简单来说,就是枚举以每个矩形为高度的最大矩形的面积。
具体来说就是:依次遍历柱形的高度,对每一个高度分别向两边扩散,求出以当前高度为矩形的最大宽度是多少。
为此,我们需要:
对于每一个位置,我们都这样操作,得到一个矩形面积,求出它们的最大值。
class Solution { public: int largestRectangleArea(vector<int>& heights) { int size = heights.size(); int ans = 0; for (int mid = 0; mid < size; ++mid) { // 枚举高 int height = heights[mid]; int left = mid, right = mid; // 确定左右边界 while (left - 1 >= 0 && heights[left - 1] >= height) { --left; } while (right + 1 >= 0 && heights[right + 1] >= height) { ++right; } // 计算面积 ans = max(ans, (right - left + 1) * height); } return ans; } };
复杂度分析:
超时,有没有方法可以一次遍历,不需要中心扩散就能够计算出每一个高度所对应的那个最大面积矩形的面积呢?
很容易想到的优化的思路就是「以空间换时间」。我们需要在遍历的过程中记录一些信息。
记录什么信息呢?记录高度是不是可以呢?其实是不够的,因为计算矩形还需要计算宽度,很容易知道宽度是由下标确定的,记录了下标其实对应的高度就可以直接从输入数组中得出,因此,应该记录的是下标。
举个例子:[2, 1, 5, 6, 2, 3]
2
,这个时候以这个2
为高度的最大面积的矩形还不能确定,我们需要继续往右遍历,如下图:1
的矩形,因为1
比2
小,所以2
不能继续往右扩张了2
应该结算答案,我们计算一下必须以2
为高度的最大矩形的面积,得到了2
。因为下必须以2
为高度的最大矩形的面积已经计算出来了,所以我们可以无视它了5
的矩形,5
比1
大,还是可以继续向右扩张的
这个算法经过一次遍历,在每一次计算最大宽度的时候,没有去遍历,而是使用了栈里存放的下标信息,以 O(1)O(1) 的时间复杂度计算最大宽度。
class Solution { public: int largestRectangleArea(vector<int>& heights) { int size = heights.size(); if(size == 0){ return 0; } int maxArea = 0; std::stack<int> stack; for (int i = 0; i < size; ++i) { while (!stack.empty() && heights[i] <= heights[stack.top()]){ int j = stack.top(); stack.pop(); int k = stack.empty() ? -1 : stack.top(); int currArea = (i - k - 1) * heights[j]; maxArea = std::max(maxArea, currArea); } stack.push(i); } while (stack.empty()){ int j = stack.top(); stack.pop(); int k = stack.empty() ? -1 : stack.top(); int currArea = (size - k - 1) * heights[j]; maxArea = std::max(maxArea, currArea); } return maxArea; } };
当然,也可以使用数组栈
class Solution { public: int largestRectangleArea(vector<int>& heights) { int size = heights.size(); if(size == 0){ return 0; } int maxArea = 0; std::vector<int> stack(size); int si = -1; for (int i = 0; i < size; ++i) { while (si != -1&& heights[i] <= heights[stack[si]]){ int j = stack[si--]; int k = si == -1 ? -1 : stack[si]; int currArea = (i - k - 1) * heights[j]; maxArea = std::max(maxArea, currArea); } stack[++si] = i; } while (si != -1){ int j = stack[si--]; int k = si == -1 ? -1 : stack[si]; int currArea = (size - k - 1) * heights[j]; maxArea = std::max(maxArea, currArea); } return maxArea; } };
上面代码我们需要考虑两种特殊情况:
为此我们可以在输入数组的两端加上两个高度为 0 (或者是 0.5,只要比 1 严格小都行)的柱形,可以回避上面这两种分类讨论。
这两个站在两边的柱形有一个很形象的名词,叫做哨兵(Sentinel)。 有了这两个矩形:
这里栈对应高度,呈单调增加不减的形态,因此称为单调栈(Monotone Stack)。它是暴力解法的优化,计算每个柱形的高度对应的最大矩形的顺序由出栈顺序决定。
class Solution { public: int largestRectangleArea(vector<int>& heights) { if(heights.empty()){ return 0; } heights.insert(heights.begin(), 0); heights.insert(heights.end(), 0); int size = heights.size(); int maxArea = 0; std::stack<int> stack; stack.push(0); for (int i = 1; i < size; ++i) { while (heights[i] < heights[stack.top()]){ int j = stack.top(); stack.pop(); int k = stack.top(); int currArea = (i - k - 1) * heights[j]; maxArea = std::max(maxArea, currArea); } stack.push(i); } return maxArea; } };
题目 | 思路 |
---|---|
leetcode:84. 柱状图中最大的矩形面积 Largest Rectangle in Histogram | 最小栈 |
leetcode:85. 全是1的最大子矩形面积 Maximal Rectangle | 子矩阵必须以第0行作为地基的情况下(往上看),哪个子矩阵含有的1最多;子矩阵必须以第1行作为地基的情况下(往上看),哪个子矩阵含有的1最多;。。。。。 |
leetcode:221. 全是 ‘1‘ 的最大正方形的面积 Maximal Square | dp[i][j]表示以(i,j)为右下角的正方形的最长边长 |
leetcode:1139. 最大的以 1 为边界的正方形 Largest 1-Bordered Square | 统计连续1的个数:注意这里不是累加和数组,而且到当前位置为止的连续1的个数,需要分为两个方向,水平和竖直。 |
leetcode:1504. 统计全 1 子矩形的个数 | 必须以第0行为底的矩阵,其内部全是1的有多少个;必须以第1行为底的矩阵,其内部全是1的有多少个… |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。