返回

单调栈:解决排序难题的强大武器

后端

单调栈:数据结构界的魔力工具

想象一下你的办公室是一个堆满文件和文件夹的房间。你总是从最上面的文件开始处理,把处理完的文件放在最下面。这就是栈的工作原理——先进后出,也称为 LIFO。

但是,单调栈 可不是普通的栈。它更像一个组织良好的房间,里面的文件按升序或降序整齐排列。这使得它成为解决特定类型排序问题的理想工具。

单调栈的奇效

寻找最大值和最小值:

假设你想要找到一个数组中最大的数字。你可以使用单调栈来构建一个递减栈,其中栈顶元素始终是最大的数字。随着你遍历数组,如果遇到一个比栈顶数字更大的数字,就把它压入栈中;否则,就弹出栈顶数字,因为它不再是最大值。

寻找前一个和后一个最大值:

你还可以用单调栈来找到每个数字的前一个和后一个最大值。使用两个单调栈,一个递增,一个递减。递增栈存储每个数字及其右侧最大的数字,递减栈存储每个数字及其左侧最大的数字。

单调栈的实现

你可以使用数组或链表来实现单调栈。数组可以让你将栈顶元素存储在开头,栈底元素存储在末尾。链表则将栈顶元素存储在头节点,栈底元素存储在尾节点。

单调栈的应用

求解最近的更大元素问题:

给定一个数组,找到每个元素的下一个更大元素(即右侧第一个比它大的元素)。用单调栈从左到右遍历数组,压入每个元素。如果栈顶元素小于当前元素,就弹出栈顶元素,直到栈顶元素大于当前元素或栈为空。然后压入当前元素。栈中剩下的元素就是每个元素的下一个更大元素。

求解最大子数组和问题:

给定一个数组,找出连续子数组的最大和。同样,从左到右遍历数组,压入每个元素。如果栈顶元素小于当前元素,就弹出栈顶元素,直到栈顶元素大于当前元素或栈为空。然后压入当前元素。栈中剩下的元素是最大子数组和的起始和结束元素。

代码示例:

class MonotonicStack:
    def __init__(self):
        self.stack = []

    def push(self, value):
        while self.stack and value < self.stack[-1]:
            self.stack.pop()
        self.stack.append(value)

    def pop(self):
        if self.stack:
            return self.stack.pop()

    def top(self):
        if self.stack:
            return self.stack[-1]

# 寻找下一个更大元素
def next_greater_element(nums):
    stack = MonotonicStack()
    result = [-1] * len(nums)
    for i, num in enumerate(nums):
        while stack.stack and num > stack.top():
            result[stack.pop()] = num
        stack.push(i)
    return result

# 寻找最大子数组和
def max_subarray_sum(nums):
    stack = MonotonicStack()
    max_sum = 0
    for num in nums:
        stack.push(num)
        max_sum = max(max_sum, stack.top())
    return max_sum

结论

单调栈是一个强大且多才多艺的数据结构,可以帮助你解决各种排序问题。通过掌握单调栈的原理及其应用,你将成为解决算法难题的大师。

常见问题解答

  1. 单调栈的用途是什么?
    单调栈主要用于解决排序问题,例如寻找最大值、最小值、前一个更大元素和最大子数组和。
  2. 如何实现单调栈?
    可以使用数组或链表来实现单调栈。数组存储栈顶元素在开头,栈底元素在末尾;链表存储栈顶元素在头节点,栈底元素在尾节点。
  3. 单调栈和普通栈有什么区别?
    单调栈中的元素是单调递增或递减的,而普通栈中的元素没有这种限制。
  4. 单调栈的典型应用是什么?
    单调栈的典型应用包括求解最近的更大元素问题、最大子数组和问题和求解滑动窗口的最大值问题。
  5. 何时使用单调栈?
    当需要解决排序问题且元素具有单调性时,可以使用单调栈。