【唐叔在学习】第四天:单调栈-有序之美的奥秘

4 阅读5分钟

大家好,我是唐叔,今天要给大家带来的是单调栈算法的详细介绍。如果你对算法有研究,那么你一定知道单调栈在处理某些特定类型的问题时有着非常高的效率。本文将从单调栈的基本概念讲起,逐步深入到具体的应用场景,并通过实例来加深理解。希望通过这篇文章,你能对单调栈有一个全面的认识,并能够在解决实际问题时灵活运用。

单调栈是什么?

基本概念

单调栈是一种特殊的栈结构,它只允许元素单调递增或单调递减。具体来说,有两种类型的单调栈:

  1. 「单调递增栈」:栈内元素从栈底到栈顶是单调递增的。
  2. 「单调递减栈」:栈内元素从栈底到栈顶是单调递减的。

在单调栈中,新元素入栈时,会与栈顶元素进行比较,根据单调性的要求,可能会弹出一些元素,以保持栈的单调性。

应用场景

单调栈通常用于以下几种情况:

  • 问题涉及到元素的相对顺序、下标索引等。
  • 寻找每个元素右边第一个比它大/小的元素
  • 计算直方图中最大的矩形面积
  • 求解滑动窗口的最大值

如何使用单调栈

使用步骤

  1. 「初始化一个空栈」:用于存放元素或元素的索引。

  2. 「遍历输入序列」:对于每一个元素,执行如下操作:

    • 如果栈为空,则直接将当前元素(或其索引)压入栈。
    • 如果当前元素满足入栈条件(根据题目需求决定是单调递增还是单调递减),则将其压入栈。
    • 否则,持续弹出栈顶元素,直到满足入栈条件为止,在此过程中,可以根据需要更新答案。
  3. 「处理剩余元素」:遍历结束后,栈中可能还有剩余元素,根据具体问题处理这些元素。

注意事项

  • 栈中存储的既可以是实际的元素值,也可以是元素的索引,这取决于具体的题目需求。
  • 在处理边界情况时要特别小心,例如当栈为空时,可能需要特殊处理。

LeetCode实战

入门题目 - 496. 下一个更大元素 I

题目描述

nums1 中数字 x「下一个更大元素」 是指 xnums2 中对应位置 「右侧」「第一个」x 大的元素。

给你两个 「没有重复元素」 的数组 nums1nums2 ,下标从 「0」 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]「下一个更大元素」 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 「下一个更大元素」

解题思路

我们使用哈希表 map 存储每个元素及其下一个更大元素,使用单调递减栈 stack 存储遍历到的元素。遍历数组 nums2,当遇到一个更大的元素时,更新 map 并弹出栈顶元素。遍历数组 nums1,从 map 中获取每个元素的下一个更大元素。

Java代码实现

public int[] nextGreaterElement(int[] nums1, int[] nums2) {
    Map<Integer, Integer> map = new HashMap<>();
    Stack<Integer> stack = new Stack<>();
    for (int num : nums2) {
        while (!stack.isEmpty() && num > stack.peek()) {
            map.put(stack.pop(), num);
        }
        stack.push(num);
    }
    for (int i = 0; i < nums1.length; i++) {
        nums1[i] = map.getOrDefault(nums1[i], -1);
    }
    return nums1;
}

中等难度题目 - 84. 柱状图中最大的矩形

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子的宽度为 1,求这个柱状图中最大的矩形面积。

解题思路

这个问题可以通过单调栈来优化求解。我们维护一个单调递增的栈,当遇到一个比栈顶元素小的高度时,说明找到了一个可能形成最大矩形的右边界。此时,我们不断弹出栈顶元素,计算以该高度为最小高度的矩形面积,直至栈顶元素不再大于当前高度。

Java代码实现

public int largestRectangleArea(int[] heights) {
    Deque stack = new ArrayDeque<>();
    int maxArea = 0;
    for (int i = 0; i <= heights.length; i++) {
        while (!stack.isEmpty() && (i == heights.length || heights[stack.peek()] > heights[i])) {
            int height = heights[stack.pop()];
            int width = stack.isEmpty() ? i : i - stack.peek() - 1;
            maxArea = Math.max(maxArea, height * width);
        }
        stack.push(i);
    }
    return maxArea;
}

更多LeetCode题目推荐

如果您对单调栈算法感兴趣,希望挑战更多题目,以下是一些LeetCode上推荐的题目:

结语

单调栈以其简洁明快的特点,在算法设计中占据了重要的一席之地。通过上述实例,相信你已经对单调栈有了更加深刻的理解。当然,算法的学习之路永无止境,希望各位在探索的路上不断前行,发现更多有趣的知识和技术。如果你有任何想法或者疑问,欢迎在评论区与我交流探讨。让我们共同进步,享受编程带来的乐趣!


如果你喜欢这篇文章,不妨点赞、收藏或转发给你的朋友们哦!也欢迎关注我的微信公众号【唐叔在学习】,获取更多技术文章和学习资料。我是唐叔,我们下次再见!