C语言之单调栈

单调栈

What

单调栈也是栈的一种,满足先进后出的原则,另外,单调栈中的元素是有序的,分为两种

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小

  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。

  • 10入栈时,栈为空,直接入栈,栈内元素为10。

  • 3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。

  • 7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。

  • 4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。

  • 12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。

Why

单调栈在解决一些问题时,比暴力循环方法要简单的多,比如遇到,求一个数组在满足元素比较条件下的统计数据时,可以考虑使用单调栈的做法,典型的就是后面第一次出现大于或者小于当前元素,要计算什么什么的时候。

How

适用单调栈的几个条件:

  • 求一个数组在满足元素比较条件下的统计数据,比如求和,求面积等

  • 由比较条件推出的单调栈的类型,然后栈中新添加的元素不符合时,可以对当前及其前向元素进行单独统计后出栈,且不影响后续元素统计。

伪代码如下:

// 此处一般需要给数组最后添加结束标志符,来满足全部出栈
for (遍历这个数组)
{
    if (栈空 || 栈顶元素大于等于当前比较元素)
    {
        入栈;
    } else {
        while (栈不为空 && 栈顶元素小于当前元素) {
            栈顶元素出栈;
            更新结果;
        }
        当前数据入栈;
    }
}
// 如果没有结束标志符,此处要一般要单独判断全部出栈

Example

视野总和

先上题目和代码

/*
描叙:有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发现总和是多少。
输入:4 3 7 1
输出:2
解释:个子为4的可以看到个子为3的发型,个子为7可以看到个子为1的身高,所以1+1=2
*/
#define STACK_NUM_MAX 500

int VisionSumGet(int *arr, int arrSize)
{
    int stack[STACK_NUM_MAX] = {0}; /* 记录元素位置的单调栈 */
    int top = -1; /* 此单调栈栈顶的索引, 初始位为-1, 栈为空*/
    int sum = 0;

    for (int i = 0; i < arrSize; i++) { /* 遍历数组 */
        while ((top >= 0) && (arr[i] >= arr[stack[top]])) { /* 栈不为空, 且遇到数组元素相等或者大于栈顶对应的数组元素, 则看不到了,进行统计 */
            sum += (i - stack[top] - 1); /* 统计栈顶对应的数组元素看到的人数,并累加*/
            top--; /* 栈顶元素出,判断下一个栈顶元素是否该统计累加 */
        }
        
        top++; /* 把当前的数组元素索引入栈 */
        stack[top] = i;
    }

    /* 最后还要进行一次计算 */
    while (top >= 0) {
        sum += (arrSize - stack[top] - 1);
        top--;
    }

    return sum;
}

首先,套一下单调栈的适用条件

  • 这是一个数组,数组里的元素均向自己看,能看到的是比自己小的,不能看到的是比自己大的或者相等的,而且是要求每个元素能看到的所有元素的个数,显然满足第一个条件。
  • 一个元素右边能看到的是比自己小的,如果右边有个比自己大的或者相等的,那么是不是可以统计此元素的情况,统计后,把此元素排除,是不是不会影响其右边元素。因为统计数据与该元素的位置相关,如果我们记录元素位置,显示是符合第二个条件的。

其他可以对照代码注释进行理解。

柱状图中的最大矩形

描述:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

解释:


Snipaste_2022-09-04_16-25-50.png

Snipaste_2022-09-04_16-26-03.png

同理,首先,套一下单调栈的适用条件

  • 这是一个数组,这里的条件比较隐晦,可以这样理解,数组里的元素遇到比自己大的或者相等的,可以向右勾勒,但是遇到比自己小的,不能向右勾勒,然后求每个元素的勾勒面积的最大值,是满足第一个条件的。
  • 如果遇到比自己小的,不能向右勾勒,可以统计此元素的勾勒面积吗,那就要看左边了,左边也都是有序且小于等于此元素的,宽度只与元素位置有关,高度只与此元素值有关,可以统计。统计后排除此元素,会对后面元素的统计有影响吗,这里要注意,如果排除对后面元素是可能有影响的,因为后面元素的统计宽度与此元素的位置可能有关,比如2 1 5 6 2 3,2 > 1, 统计2然后排除,2的位置将丢失,在统计1的时候,1是可以勾勒2的位置的。那怎么办,只需要保留2的位置就行了,试想如果2和1之间有多个2,我们应该保留哪一个?我们应该保留距离1最左边的那个2的位置就行了,这样计算1的宽度才是正确的,为了保证有序性,我们把2统计完成后,我们把1左边的2全部排出,然后保留一个最左边位置的且把它的值修改成1,当前的1也不需要进行入栈了,因为它的面积肯迪东比修改成1的那个面积统计的要小。

结合代码,可以好好理解一下:

/*
柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
*/

#define STACK_NUM_MAX 100000

int largestRectangleArea(int *heights, int heightsSize)
{
    int stack[STACK_NUM_MAX] = {0}; /* 记录元素位置的单调栈 */
    int top = -1; /* 此单调栈栈顶的索引, 初始位为-1, 栈为空*/
    int max = 0;
    int area = 0;

    for (int i = 0; i < heightsSize; i++) { /* 遍历数组 */
        /* 栈为空, 或者遇到的元素大于等于栈顶对应的元素, 还可以继续向右勾勒*/
        if ((top < 0) || (heights[i] >= heights[stack[top]])) {
            top++;
            stack[top] = i;
            continue;
        }

        /* 栈不为空, 且遇到的元素小栈顶对应的元素, 统计栈顶对应的元素的面积*/
        while ((top >= 0) && (heights[i] < heights[stack[top]])) {
            area = heights[stack[top]] * (i - stack[top]);
            if (area > max) {
                max = area;
            }
            top--; /* 统计栈顶后出栈 */
        }
        /* 保留最后一次的栈顶, 并修改对应的元素的值为当前遇到的元素值, 当前的元素值也不必入栈, 因为它统计的面积必然小一些*/
        top++;
        heights[stack[top]] = heights[i];
    }

    /* 最后还要进行一次计算 */
    while (top >= 0) {
        area = heights[stack[top]] * (heightsSize - stack[top]);
        if (area > max) {
            max = area;
        }
        top--;
    }

    return max;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容