题目分析
题目链接:https://leetcode.com/problems...
一开始,我尝试通过枚举所有左右边界,来依次计算夹在其中的最高矩形的面积,但是很快发现这种方式非常低效。由于左右边界没有提供关于高度的信息,每次枚举出一对左右边界以后,还要遍历两个边界之间的所有条形(O(n)
),才能找出矩形可以达到的最大高度。并且,所有左右边界的组合达到n^2
量级。总的时间复杂度将达到O(n^3)
。
题解1:维护信息树
按照矩形中的瓶颈点位置来枚举,比如,下图中4
就是瓶颈点,组成矩形的所有柱体中它是最低的,它是矩形的高度的瓶颈所在。
枚举出瓶颈点的位置以后,向左、向右扩展边界,得到一个尽可能大的矩形。
这里还可以使用一个技巧,为了更快地找到左右边界,我们可以按照高度从小到大进行枚举。这样,当要计算高度为h的矩形的边界时,所有小于h的位置都在此之前枚举过了。
为了保存已经枚举过的柱体的面积,可以维护一颗二叉搜索树(C++中的Map一般是用红黑树实现的),以index为键,可以在O(logn)的时间内找到想要的index,并且插入时间可以缩短至O(1)。
class Solution {
public:
// 按照高度从小到大排序的比较函数
static bool cmp_height(vector<int>& v1, vector<int>& v2) {
return v1[1] < v2[1];
}
// 按照下标从小到大排序的比较函数
struct cmp_index {
bool operator()(const int& v1, const int& v2) const { return v1 < v2; }
};
int largestRectangleArea(vector<int>& heights) {
int max_area = 0;
vector<vector<int>> sorting; // (index, height, count)
int last = -1;
for (int i = 0; i < heights.size(); ++i) {
// 合并相邻、相同高度的柱体
if (last != heights[i]) {
sorting.push_back(vector<int>{i, heights[i], 1});
} else {
sorting.back()[2]++;
}
}
// 按照柱体高度从小到大排序
sort(sorting.begin(), sorting.end(), cmp_height);
// m用来存储已经枚举出来的瓶颈点,按照下标从左到右排序
map<int, vector<int>&, cmp_index> m;
// 按照柱体高度从小到大枚举
for (int i = 0; i < sorting.size(); ++i) {
// m中已有的柱体都是高度小于sorting[i]的
// 在这些柱体中找出index比它小一些和它大一些的,作为矩形的左右边界
auto it = m.lower_bound(sorting[i][0]); // O(logn)
int left_boundary = -1, right_boundary = heights.size();
if (it != m.end()) {
right_boundary = (it->second)[0];
}
if (it != m.begin()) {
auto it2 = it;
it2--;
left_boundary = (it2->second)[0] + (it2->second)[2] - 1;
}
int area = (right_boundary - left_boundary - 1) * sorting[i][1];
if (area > max_area) max_area = area;
m.insert(it, pair<int, vector<int>&>(sorting[i][0], sorting[i])); // O(1)
}
return max_area;
}
};
该实现的时间复杂度为O(nlogn)。在leetcode的排名中仍属于末端,这是因为通过维护信息栈而不是树,能使时间复杂度降低至O(n)。
题解2:维护信息栈
class Solution {
public:
int largestRectangleArea(vector<int> &heights)
{
int max_res = 0;
int n = heights.size();
// 在i左边第一个比heights[i]小的元素
vector<int> left_smaller(n, -1);
// 在i右边第一个比heights[i]小的元素
vector<int> right_smaller(n, n);
stack<int> sta;
for (int i = 0; i < n; ++i)
{
while (!sta.empty() && heights[sta.top()] >= heights[i])
{
sta.pop();
}
if (!sta.empty())
left_smaller[i] = sta.top();
sta.push(i);
}
stack<int> sta2;
for (int i = n - 1; i >= 0; --i)
{
while (!sta2.empty() && heights[sta2.top()] >= heights[i])
{
sta2.pop();
}
if (!sta2.empty())
right_smaller[i] = sta2.top();
sta2.push(i);
}
for (int i = 0; i < n; ++i)
{
// 枚举每个i作为瓶颈点
// 以i为瓶颈点,找到最大矩形的左右边界
int l = left_smaller[i], r = right_smaller[i];
int size = (r - l - 1) * heights[i];
max_res = max(max_res, size);
}
return max_res;
}
};
时间复杂度为O(n),虽然有嵌套循环,但是每个柱体只会被push/pop一次。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。