单调栈 Histogram

简介:

这个题目是一个好朋友给我讲的方法,我按照自己的理解,敲出来代码。 所以把算法流程和代码贡献出来,希望和大家共同学习。

题目大意:

给出一个柱形统计图(histogram), 它的每个项目的宽度是1, 高度和具体问题有关。 现在编程求出在这个柱形图中的最大面积的长方形。

例如: 

7 2 1 4 5 1 3 3

 

7表示柱形图有7个数据,分别是 2 1 4 5 1 3 3, 对应的柱形图如下,最后求出来的面积最大的图如右图所示。


 

分析: 

如果采用枚举的方式,如果当前我们枚举项是 i = 0, 即 height = 2, 

我们用另外两个变量 j 和k 向左和向右两个方向搜素,找到第一个 小于 height的下标,这样我们就找到了用 i 项作为高度长方形了。

我们假设 -1位置,和最右高度都是无穷小。

例如:

i = 0, j = -1, k = 1, 最后的面积是 (k - j - 1) * height = 2

i = 1, j = -1, k = 7, 最后面积是( k - j - 1) * height = 7;

...

i = 3, j = 2, k = 5 面积是 ( k - j - 1) * height = 8 

枚举出所有的长方形的同时,然后得到最后的面积。

不过这样的程序的时间复杂度是 O(n^2)

我们如何能仅仅做一次,就求出这个面积呢?

观察:

当我们扫扫描到第一个高度 H1 = 2的时候,我可以标记它的起始位置1, 因为我们还不知道它将向右扩展到什么地方,所以继续扫面。

当遇到第二项 H2 = 1, 因为这项比之前的小,我们知道,用H1做高度的长方形结束了,算出它的面积。

同时这个时候,我们多了一个高度H2,用它做长方形高度的长方形起始位置应该是在哪里呢? 因为H1的高度比H2要高,所以这个起始位置自然是H1所在的位置。

 

为了模拟上面的过程,我们引入单调栈~

我们先定义我们我们要保存的每一项数据

struct Node

{

      int height;

      int startPosition;

};

用来描述某一个高度,和这个高度的起始位置。

然后我们按照高度来组织成单调栈。我们来看一下它是如何工作的。

为了不用考虑堆栈为空的情况,我们用插入栈底 一个高度(0, 0)的项。

数据: 

 2 1 4 5 1 3 3

 

这样初始化

(0 , 0)

I = 1

当扫描到(2, 1)时候,因为高度2 大于栈顶,插入

(0, 0),  (2, 1)

I = 2: 

当扫描到1的时候,因为1小于栈顶高度2, 我们认为栈顶的那个高度应不能再向右扩展了,所以我们将它弹出

这个时候扫描到 i = 2;

高度是 (i - 1(H1.startIndex)) * H1.height = 2;

我们得到一个面积是2的长方形。

同时我们发现高度是1的当前高度,可以扩展到 H1所在的下标,所以我们插入( 1, 1) 堆栈变成

(0, 0), (1, 1) 因为(2, 1)已经不能向右伸展了,已经被弹出了

 

i = 3

(0, 0), (1, 1), ( 4 3)

i = 4

(0, 0), (1, 1), (4, 3), (5, 4)

i = 5 

这个时候当前高度小于栈顶高度,我们认为栈顶已经不能向右扩展,所以弹出,并且获得面积 ( i  - H5.startindex) * H5.height = (5 - 4 ) * 5 = 5

弹出这个元素后,其实(4, 3)的高度也要比 1 大,所以把这个也弹出来,同样方式获得面积 8.

最后我们的堆栈是

(0, 0) , (1, 1)

i  = 6

(0, 0), (1, 1), ( 3, 6)

i = 7

(0, 0), (1, 1), (3, 6)

i = 8

最后一步是有点特殊的,因为我们必须要把所有的元素都弹出来,因为栈里面的高度,都坚持到了最后,我们要把这些高度组成的长方形拿出来检测。

我们可以假设扫面到8的时候,高度是0,(最小值)

弹出(3,6)获得面积 (8 - 6 ) * 3 = 6

弹出(1, 1)获得面积(8 - 1) * 1 = 7

 

最后的面积是8.

 

代码如下:

 

[cpp]  view plain copy
    1. Memory: 2116K       Time: 454MS  
    2. Language: C++       Result: Accepted  
    3. Source Code  
    4. #include <stdio.h>  
    5. #include <stack>  
    6. using namespace std;  
    7.   
    8.   
    9. struct Node  
    10. {  
    11.     long long height;//一个高度值  
    12.     int startIdx; //这个高度值的起始位置  
    13.   
    14.     Node(long long _height, int _idx):height(_height), startIdx(_idx)  
    15.     {  
    16.   
    17.     }  
    18. };  
    19. long long gHeights[100000];  
    20.   
    21. long long GetMaxArea(int nItem)  
    22. {  
    23.     int i;  
    24.     stack<Node> s;  
    25.     long long height;  
    26.   
    27.     s.push(Node(-1, 0));//将最小高度加入堆栈,防止堆栈弹空  
    28.   
    29.     int currentPosition;  
    30.     long long maxArea = 0;//记录最大面积  
    31.     long long curArea;  
    32.     for( i = 0; i <= nItem ; i++)  
    33.     {  
    34.         currentPosition = i + 1;//获得当前 位置  
    35.         if( i == nItem)//这时候,我们认为到达最后,我们要弹空栈  
    36.         {  
    37.             height = 0;  
    38.         }  
    39.         else  
    40.         {  
    41.             height = gHeights[currentPosition-1];  
    42.         }  
    43.         Node t(height, currentPosition);//当前节点  
    44.         while( s.top().height > height)  
    45.         {  
    46.             t = s.top();  
    47.             s.pop();  
    48.   
    49.             curArea = (currentPosition - t.startIdx) * t.height;//按照某个高度的 开始和结束的位置,获得面积  
    50.             if(curArea > maxArea)  
    51.             {  
    52.                 maxArea = curArea;  
    53.             }  
    54.         }  
    55.         s.push(Node(height, t.startIdx));  
    56.   
    57.     }  
    58.     return maxArea;  
    59. }  
    60. int main()  
    61. {  
    62.   
    63.     int nItem;  
    64.   
    65.     while(scanf("%d", &nItem) != EOF && nItem)  
    66.     {  
    67.   
    68.   
    69.   
    70.         int i;  
    71.         for( i = 0; i < nItem; i++)  
    72.         {  
    73.             scanf("%lld", gHeights + i);  
    74.         }  
    75.         printf("%lld\n", GetMaxArea(nItem));  
    76.     }  
    77.       
    78.   
    79.     return 0;  
    80.   
    81. }  

本文转自博客园知识天地的博客,原文链接:单调栈 Histogram,如需转载请自行联系原博主。

相关文章
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
252 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
41 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
75 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
2月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
2月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
56 4
|
2月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
2月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
57 0
|
3月前
数据结构(栈与列队)
数据结构(栈与列队)
26 1
|
3月前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
21 0