leetcode之Largest Rectangle in Histogram
问题来源:Largest Rectangle in Histogram
问题描述:给定一个长度为n的直方图,我们可以在直方图高低不同的长方形之间画一个更大的长方形,求该长方形的最大面积。例如,给定下述直方图,
我们可以以高度5宽度2画一个更大的长方形,如下图,该长方形即是面积最大的长方形。该问题是难度比较大的问题,但是很出名,经常作为面试题出现。最近陈利人老师给出该问题的一个O(n)解法,非常巧妙,并从二维三维角度对问题进行了扩展。我们在陈老师的基础上,对该问题进行深入分析,给出多种方法,拓展大家的视野。
1. 解法一
如果在面试的过程中被问到这个题目,除非之前见过,否则一时很难想到解法。我们不妨从最笨的解法入手。在我看来,能把最笨的解法写出来也是很不错的,毕竟很多人见到这种题一下就蒙了。
最笨的解法是什么呢,就是遍历所有的起始和结束位置,然后计算该区域内长方形的面积,找到最大的。具体实现过程中,从第0个位置开始遍历起点,选定起点之后,从起点到末尾都可以当做终点,所以总共有O(n2)种可能。在固定了起点之后,后续对终点的遍历有一个特点:构成长方形的高度都不高于之前所构造长方形的高度,所以长方形的高度即是到当前终点为止的最小值,宽度即是终点位置减去起点位置加1。按照这个思路实现的C++代码如下:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int len=heights.size();
int max_size=0;
for(int i=0;i<len;i++)
{
int min_height=heights[i];
int current_size=min_height;
for(int j=i;j<len;j++)
{
if(heights[j]<min_height) min_height=heights[j];
current_size=min_height*(j-i+1);
if(current_size>max_size) max_size=current_size;
}
}
return max_size;
}
};
上述代码可以通过几乎所有的测试用例,但是当测试用例为0到19999的连续整数时会超时。说明O(n2)的复杂度还是过高,需要进一步优化。下面我们看一下陈利人老师的解法,原文在《很神奇的解法!怎么求柱状图中的最大矩形?》。
新解法的核心在于考虑了直方图两个相邻长方形AB之间的关系。如果前一个长方形A低后一个长方形B高,则A肯定不会是某个大长方形的终点,因为我们可以安全地在A后面添加更高的B,使大长方形的宽度加1。如果A高B低,则A是可能的终点,假设我们就用A当做终点,并且以该长方形的高度当做大长方形的高度,看看可以往前延伸多长。根据上面这两条性质,我们可以维护一个递增序列(实际为非递减,当前后两个长方形的高度一样时,前一个长方形同样也不可能是终点,在此为了解释方便假定前后高度都不一样),当B高时就将B的位置添加到序列中,否则就弹出A的位置,并用A的位置作为终点,A的高度作为大长方形的高度计算面积。起点怎么确定呢,由于我们维护的是一个递增序列,在弹出A之后,序列中A的前一个位置所对应的长方形高度肯定低于A的高度,所以A的前一个长方形的位置加1即是大长方形的起点。因为我们每次都是对序列的末尾进行操作,所以可以用一个栈来维护此递增序列。大家可以通过下图仔细体会上面的分析。如果还是不理解,可以阅读上面提到的原文。
陈老师的博客中给出的是python代码,我们将其改写成C++代码:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
heights.push_back(-1);
int max_size=0;
int index=0;
stack<int> s;
while(index<heights.size())
{
if(s.size()==0||heights[s.top()]<=heights[index])
{
s.push(index);
index++;
}
else
{
int top=s.top();
s.pop();
int size=0;
if(s.size()==0)
{
size=heights[top]*index;
}
else
{
size=heights[top]*(index-s.top()-1);
}
if(size>max_size) max_size=size;
}
}
return max_size;
}
};
上述代码的核心就是判断前后两个长方形的高度,后一个高就添加到堆栈中,否则就弹出计算面积。该代码提交到leetcode上运行时间是24ms。上述代码用了STL中的stack,我们也可以用数组代替,此时代码可以修改如下:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
heights.push_back(-1);
int max_size=0;
int index=0;
int *s=(int*)malloc(sizeof(int)*heights.size());
int stack_index=0;
while(index<heights.size())
{
if(stack_index==0||heights[s[stack_index-1]]<=heights[index])
{
s[stack_index++]=index;
index++;
}
else
{
int top=s[--stack_index];
int size=0;
if(stack_index==0)
{
size=heights[top]*index;
}
else
{
size=heights[top]*(index-s[stack_index-1]-1);
}
if(size>max_size) max_size=size;
}
}
free(s);
return max_size;
}
};
由于少了系统调用,上述代码运行时间降为16ms。需要注意的一点是,stack_index指向的是堆栈头的上一个位置。
上面的两个代码虽然都能正确运行,但是有一个坏处,破坏了原始的输入数组。为什么要向原数组中添加一个-1呢?这个也比较容易理解,假如原始数组是递增的,我们不可能只添加不弹出,添加一个-1就可以弹出所有的元素。此处也可以对代码进行修改,避免破坏原始数组。方法就是遍历完所有的元素之后,判断堆栈是否为空,不为空就弹出并计算面积,比较简单,请大家自己实现。
2. 解法二
不知道大家对最长回文子串的几种解法是否了解。如果不考虑最优解法,最长回文子串问题可以有两种不同的思路:1. 确定头和尾,判断该子串是否为回文串;2. 指定回文串的中点,看能往两侧延伸多长。在最大矩形问题中,上面的解法一和回文子串的思路一相似,同理,我们也可以仿照思路二来解决最大矩形问题。我们可以将直方图的任意一个长方形当做中点,然后以该长方形的高度当做大长方形的高度,看可以往两侧延伸多长。这种思路其实更符合大家的思维方式。很明显,这种方法的复杂度也是O(n2),提交代码还是超时,我们对其进行优化。
2.1 基于堆栈的解法
上面原始解法慢的原因也是没有考虑直方图相邻长方形之间的关系,我们分下面两种情况考虑,看是否有优化余地。当出现A这种情形时,其实我们可以获得一些有用的信息,这表明第i个长方形不能再往左扩展(以第i个长方形的高度往两侧扩展),进而我们可以求得left[i](在此left有两种不同的含义,既可以为向左扩展到的位置,也可以为向左扩展的长度,后续代码实现按第一种理解方式)。当出现B情形时,表明第i-1个长方形高,第i个长方形可以继续往左扩展,直到遇到A情形,然后计算left[i]。在实际情况中,由于A情形和B情形随机出现,所以前后两个长方形的位置并不一定相邻。采用数学归纳法我们可以求得所有的left值。可以看出,A情形只往末尾添加元素,B情形只在末尾弹出元素,从而我们可以用一个栈来维护单调递增(实际为非递减)队列。
同理,我们可以通过倒序遍历数组来计算right。按照上述思路实现的代码如下:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n=heights.size();
int* stack=new int[n];
int* right=new int[n];
int* left=new int[n];
int s=0;
for(int i=0;i<n;i++)
{
while(s>0&&heights[stack[s-1]]>=heights[i]) s--;
left[i]=(s==0?0:stack[s-1]+1);
stack[s++]=i;
}
s=0;
for(int i=n-1;i>=0;i--)
{
while(s>0&&heights[stack[s-1]]>=heights[i]) s--;
right[i]=(s==0?n:stack[s-1]);
stack[s++]=i;
}
int size=0,max_size=0;
for (int i=0;i<n;i++)
{
size=(right[i]-left[i])*heights[i];
if(size>max_size)max_size=size;
}
delete[] stack;
delete[] right;
delete[] left;
return max_size;
}
};
上面代码看似是两重循环,但其实每个元素只进入堆栈一次,通过均摊分析可以得到复杂度为O(n)。提交到leetcode上运行时间为16ms。
2.2 基于单调队列的解法
除了上面巧妙的堆栈解法外,还可以用单调队列来解决。单调队列维护数列的下标,队列内的元素满足:
设单调队列从头部开始的元素值为xi,则xi<xi+1且axi<axi+1。
简单来说单调队列就是下标对应的元素是严格递增的顺序。当然在实际应用过程中,可能不严格单调。
在该问题中,该怎样应用单调队列呢?我们还是分两种情形考虑。考虑方法正好和基于堆栈的方法相反。假设我们维护了递增的单调队列(实际为非递减),当出现B情形时,我们其实可以知道第i-1个长方形不能再往右扩展,从而可以求得right[i-1];当出现A情形时,第i-1个长方形还可以继续向右扩展。只要高度递增,就往单调队列末尾添加元素,否则就计算单调队列末尾元素的right值。当我们遍历完所有的元素之后,单调队列中存在着一个递增的序列,表示剩余位置可以从队列头扩展到队列尾。依次计算扩展长度。
同理,我们可以通过倒序遍历数组来计算left。按照上述思路实现的代码如下:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int* deq=new int[heights.size()];
int* right=new int[heights.size()];
int* left=new int[heights.size()];
int n=heights.size();
int s=0,t=0;
for(int i=0;i<n;i++)
{
while(s<t&&heights[deq[t-1]]>heights[i])
{
right[deq[t-1]]=i;
t--;
}
deq[t++]=i;
}
while (s<t)
{
right[deq[s]]=deq[t-1]+1;
s++;
}
s=0;
t=0;
for (int i=n-1;i>=0;i--)
{
while (s<t&&heights[deq[t-1]]>heights[i])
{
left[deq[t-1]]=i+1;
t--;
}
deq[t++]=i;
}
while (s<t)
{
left[deq[s]]=deq[t-1];
s++;
}
int size=0,max_size=0;
for (int i=0;i<n;i++)
{
size=(right[i]-left[i])*heights[i];
if(size>max_size)max_size=size;
}
delete[] deq;
delete[] right;
delete[] left;
return max_size;
}
};
代码的整体逻辑和基于堆栈的实现类似,只是由首先计算left变为首先求right。复杂度也是O(n),提交到leetcode上运行时间也是16ms。
3. 扩展
该问题非常有趣,可以做很多扩展。例如将长方形的宽度由1变为任意宽度,再或者将二维直方图变为三维直方图求最大长方体。针对这两个扩展的解法,大家可以参考陈利人老师“待字闺中”微信公众号中的分析。先给出两个原始链接:《举一反三,从一道面试题说起》和《快看,快看!求3D柱状图中的最大长方体有解了》。
针对第一个扩展,可以很容易采用上面的解法二实现。首先遍历一遍所有的长方形,累积长方形的宽度得到每个长方形在不同宽度下的起始位置。然后继续采用解法二中任意一种实现获得每个长方形往两侧的扩展位置。最后利用计算的起始位置和扩展位置即可计算最大面积,复杂度还是O(n)。
leetcode之Largest Rectangle in Histogram的更多相关文章
- LeetCode 84. Largest Rectangle in Histogram 单调栈应用
LeetCode 84. Largest Rectangle in Histogram 单调栈应用 leetcode+ 循环数组,求右边第一个大的数字 求一个数组中右边第一个比他大的数(单调栈 Lee ...
- Java for LeetCode 084 Largest Rectangle in Histogram【HARD】
For example, Given height = [2,1,5,6,2,3], return 10. 解题思路: 参考Problem H: Largest Rectangle in a Hist ...
- 关于LeetCode的Largest Rectangle in Histogram的低级解法
在某篇博客见到的Largest Rectangle in Histogram的题目,感觉蛮好玩的,于是想呀想呀,怎么求解呢? 还是先把题目贴上来吧 题目写的很直观,就是找直方图的最大矩形面积,不知道是 ...
- [LeetCode] 84. Largest Rectangle in Histogram 直方图中最大的矩形
Given n non-negative integers representing the histogram's bar height where the width of each bar is ...
- LeetCode之Largest Rectangle in Histogram浅析
首先上题目 Given n non-negative integers representing the histogram's bar height where the width of each ...
- [LeetCode OJ] Largest Rectangle in Histogram
Given n non-negative integers representing the histogram's bar height where the width of each bar is ...
- [LeetCode#84]Largest Rectangle in Histogram
Problem: Given n non-negative integers representing the histogram's bar height where the width of ea ...
- LeetCode 84. Largest Rectangle in Histogram 直方图里的最大长方形
原题 Given n non-negative integers representing the histogram's bar height where the width of each bar ...
- [leetcode]84. Largest Rectangle in Histogram直方图中的最大矩形
Given n non-negative integers representing the histogram's bar height where the width of each bar is ...
随机推荐
- 在python后台如何将客户端提交的form表单数据提取出来?
1.获取客户端提交的表达数据,数据类型为ImmutableMultiDictformData = request.form2.将提取的数据转化成字典formDict = formData.to_dic ...
- vue+iview实现动态路由和权限验证
github上关于vue动态添加路由的例子很多,本项目参考了部分项目后,在iview框架基础上完成了动态路由的动态添加和菜单刷新.为了帮助其他需要的朋友,现分享出实现逻辑,欢迎一起交流学习. Gith ...
- HTC Vive 叠影器无法创建设备
今天使用笔记本电脑打开SteamVR发生错误:SteamVR启动失败,"Shared IPC Compositor Connected Fail(306)",然后启动失败,在UI界 ...
- JavaScript数据结构与算法(四) 循环队列的实现
实现击鼓传花,需要用到上一章所述队列类Queue TypeScript方式实现源码 let hotPotato = (nameList, num) => { let queue = new Qu ...
- [LeetCode] Base 7 基数七
Given an integer, return its base 7 string representation. Example 1: Input: 100 Output: "202&q ...
- 3.如何搭建Appium自动化测试环境
整个APP自动化环境安装可以参照虫师博客安装 附以下链接: http://www.cnblogs.com/fnng/category/695788.html 下面介绍运用到工作中遇到的一些问题 1.如 ...
- 《C++ Primer》学习笔记:3.3.3其他vector操作
<C++ Primer>(第五版)中计算vector对象中的索引这一小节中,举例要求计算各个分数段各有多少个成绩. 代码如下: #include <iostream> #inc ...
- [TJOI 2016&HEOI 2016]排序
Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这 ...
- [HNOI2009]无归岛
Description Neverland是个神奇的地方,它由一些岛屿环形排列组成,每个岛上都生活着之中与众不同的物种.但是这些物种都有一个共同的生活习性:对于同一个岛上的任意两个生物,他们有且仅有一 ...
- Codeforces Round #460 D. Karen and Cards
Description Karen just got home from the supermarket, and is getting ready to go to sleep. After tak ...