【单调栈】一文彻底搞懂单调栈!

本文介绍了单调栈的概念,它在查找元素两侧距离最近的较大或较小值中的应用,并给出了O(n)时间复杂度的实现方法。作者通过实例和代码详细讲解了如何使用单调递减栈解决特定问题,以及如何处理相等元素的情况。
摘要由CSDN通过智能技术生成

前面的文章我们练习数十道 动态规划 的题目。相信小伙伴们对于动态规划的题目已经写的 得心应手 了。

还没看过的小伙伴赶快关注在 「动态规划」 集合里查看哦!

接下来我们开启一个新的篇章 —— 「单调栈」

单调栈

顾名思义,单调栈是一种 单调的栈(说的跟没说一样!哈哈)。

即:栈内的元素具有单调性。因此可以分为 单调递增的栈单调递减的栈

有什么用处呢?

最直接的作用就是:能够在 O ( N ) O(N) O(N) 的时间复杂度下,返回所有元素中任意一个数左和右两边 距离最近 的首个比该数大或小的位置在哪。(暴力解法 O ( n ∗ n ) O(n*n) O(nn))。

求解比该数 的位置就需要 单调递减的栈

求解比该数 的位置就需要 单调递增的栈

如上图:

比 4 的左右首个元素是 7 和 8 。(使用 单减栈 解决)

比 4 的左右首个元素是 2 和 1 。(使用 单增栈 解决)

实现思路

下面我们以寻找两侧距离最近的且 大于 某元素,即 单调递减的栈 为例(栈底大 -> 栈顶小):

  1. 栈的含义: 栈中存放的是按序排列且暂时没有求出结果的元素的 下标

  2. 维护单调性: 栈内元素严格递减,新来到的元素只需要和栈顶元素进行比较。

  3. 新元素的处理: 当一个新元素到来时,先与栈顶元素进行比较。若比栈顶元素小,则直接压栈保存;若比栈顶元素大,则需要弹出栈顶元素,直到栈空或比栈顶元素小后压栈。

  4. 弹出时更新信息: 当弹出栈顶元素时,此时次栈顶元素和新来元素就是所求的左右两侧距离最近的且大于该元素的值,记录其下标等信息

  5. 时间复杂度: 时间复杂度为 O ( n ) O(n) O(n),因为每个元素最多被压入和弹出栈一次。

其他元素的求解可以按照流程依次模拟一遍。该方法具体证明过程就不再赘述了,感兴趣的小伙伴可以自己列举情况分析哦!

代码

public static int[][] getNearLessNoRepeat(int[] arr) {
    int n = arr.length;
    // n 行 2 列数组,存储 n 个元素左右两侧的下标
    int[][] res = new int[n][2];
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
            int j = stack.pop();
            // 记录左侧下标
            res[j][0] = stack.isEmpty() ? -1 : stack.peek();
            // 记录右侧下标
            res[j][1] = i;
        }
        stack.push(i);
    }
    // 没有新来元素后,栈不为空,依次弹出
    while (!stack.isEmpty()) {
        int j = stack.pop();
        res[j][0] = stack.isEmpty() ? -1 : stack.peek();
        res[j][1] = -1;
    }
    return res;
}

代码解释

使用 int[n][2] 记录每个元素中左右两侧符合条件的下标值。

从左到右依次遍历元素:

  1. 当栈不为空,且栈顶元素大于 arr[i] 时,根据单调减的栈的规定,需要弹出栈顶元素并记录该元素的左右符合要求的值。因此,在弹出栈顶元素后,若栈为空,说明左侧 没有比该元素大的值了,返回 -1 。右侧 即当前新到元素下标 i 。

  2. 当没有新元素后,若栈不为空,依次弹出所有的栈中元素。此时说明,右侧 没有比该元素大的值了,返回 -1;左侧 为次栈顶元素下标。


为了帮助小伙伴更好理解,下面是几种特殊情况的举例。

相信小伙伴这次都能够轻松理解啦 ~

讲到这里,可能有小伙伴提出疑问了,这里说的都是元素不同的情况。那如果遇到 两个相等的元素 时,又该如何应对呢?

下篇文章我们为大家揭晓!敬请期待吧 ~


~ 点赞 ~ 关注 ~ 星标 ~ 不迷路 ~!!!

关注回复「ACM紫书」获取 ACM 算法书籍~

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作封装在对象中,通过对象之间的交互实现程序的设计和开发。下面是一些关键概念,帮助你更好地理解Python面向对象编程。 1. 类(Class):类是对象的蓝图或模板,描述了对象的属性和行为。它定义了对象的特征和方法。例如,我们可以定义一个名为"Car"的类来表示汽车,其中包含属性(如颜色、型号)和方法(如加速、刹车)。 2. 对象(Object):对象是类的实例,是具体的实体。通过实例化类,我们可以创建一个对象。例如,我们可以创建一个名为"my_car"的对象,它是基于"Car"类的实例。 3. 属性(Attribute):属性是对象的特征,用于描述对象的状态。每个对象都可以具有一组属性。例如,"Car"类的属性可以包括颜色、型号等。 4. 方法(Method):方法是对象的行为,用于定义对象的操作。每个对象都可以具有一组方法。例如,"Car"类的方法可以包括加速、刹车等。 5. 继承(Inheritance):继承是一种机制,允许我们创建一个新类(称为子类),从现有类(称为父类)继承属性和方法。子类可以扩展或修改父类的功能。继承可以实现代码重用和层次化设计。 6. 多态(Polymorphism):多态是一种特性,允许不同类的对象对同一方法做出不同的响应。多态提高了代码的灵活性和可扩展性。 7. 封装(Encapsulation):封装是一种将数据和操作封装在对象中的机制,隐藏了对象的内部实现细节,只暴露必要的接口给外部使用。这样可以保护数据的安全性,提供了更好的模块化和代码复用性。 通过理解这些概念,你可以更好地掌握Python面向对象编程。在实践中,你可以使用类来创建对象,操作对象的属性和调用对象的方法,通过继承和多态实现代码的灵活性和可扩展性,通过封装保护数据的安全性和提高代码的可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

强连通子图

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值