返回

单调栈:发现数字的邻居

闲谈

单调栈的介绍

单调栈本质上是一个栈,但它多了个小巧思——数据排列的单调性。具体来说,单调栈要么单调递增,要么单调递减,这让我们能更快地找到某些元素在特定条件下的位置。
举个例子,在一个单调递增的栈中,我们可以轻易地找出栈顶元素作为当前的最大值。如果想找到当前的最小值,则只需在一个单调递减的栈中取栈顶。

栈的操作

单调栈作为一种特殊的栈,支持进栈、出栈、栈顶操作。

  • 进栈:将元素压入栈顶,同时维护单调性。如果栈是递增的,那么新元素必须大于栈顶元素;如果是递减的,则新元素必须小于栈顶元素。

  • 出栈:移除栈顶元素,并确保单调性不被破坏。

  • 栈顶:获取栈顶元素,而无需移除它。

认识你的应用

单调栈的用途广泛,让我们在数据处理和算法中大显身手:

  • 查找窗口中的最大值/最小值: 给定一个数组和一个窗口大小,单调栈可以高效地找到每个窗口中的最大值或最小值。

  • 寻找下一个更大元素: 给定一个数组,单调栈可以快速地找到每个元素的下一个更大元素,即第一个大于它的元素。

  • 寻找下一个更小元素: 与上一个类似,但这次是寻找每个元素的下一个更小元素。

  • 计算跨度: 数组中的跨度定义为每个元素右边第一个比它大的元素的索引与它自己的索引之差。单调栈可以帮助我们有效地计算跨度。

  • 维护一个有序数组: 使用单调栈,我们可以将元素压入一个有序的数组,并高效地维护单调性。

  • 求逆序数: 单调栈可以帮助我们快速地计算一个数组的逆序数,即元素与它后面的元素交换位置的次数。

单调栈是利器,使用需谨慎

在使用单调栈时,你需要注意以下几点:

  • 理解单调性:在使用单调栈之前,你需要明确你所处理的数据是单调递增还是单调递减的。

  • 维护单调性:在进行任何操作时,确保单调性不被破坏。这可以通过在进栈时比较元素的大小来实现。

  • 结合其他算法:单调栈通常与其他算法配合使用,以解决更复杂的问题。例如,在查找窗口中的最大值/最小值时,单调栈可以与滑动窗口算法结合使用。

编程语言中的单调栈实现

在许多编程语言中,单调栈都可以很容易地实现。这里提供一些示例:

  • Python:
class MonotonicStack:
    def __init__(self, increasing=True):
        self.stack = []
        self.increasing = increasing

    def push(self, x):
        while self.stack and (self.increasing and x < self.stack[-1]) or (not self.increasing and x > self.stack[-1]):
            self.stack.pop()
        self.stack.append(x)

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

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

    def is_empty(self):
        return len(self.stack) == 0
  • Java:
class MonotonicStack {
    private Deque<Integer> stack;
    private boolean increasing;

    public MonotonicStack(boolean increasing) {
        this.stack = new ArrayDeque<>();
        this.increasing = increasing;
    }

    public void push(int x) {
        while (!stack.isEmpty() && (increasing && x < stack.peek()) || (!increasing && x > stack.peek())) {
            stack.pop();
        }
        stack.push(x);
    }

    public int pop() {
        if (!stack.isEmpty()) {
            return stack.pop();
        }
        return -1;
    }

    public int top() {
        if (!stack.isEmpty()) {
            return stack.peek();
        }
        return -1;
    }

    public boolean isEmpty() {
        return stack.isEmpty();
    }
}

踏上单调栈探索之旅

单调栈的应用场景众多,挑战也不少。如果你想深入学习和掌握单调栈,可以尝试以下步骤:

  • 首先,学习单调栈的基本概念和原理。网上有许多资源可以帮助你理解这些内容。

  • 然后,尝试实现自己的单调栈类。这将帮助你巩固对概念的理解。

  • 最后,开始解决一些与单调栈相关的算法问题。LeetCode和HackerRank等平台上有很多这样的问题。

总结

单调栈是一种强大的数据结构,在许多算法和数据处理问题中都有着广泛的应用。它通过维护元素的单调性,帮助我们快速地找到某些元素在特定条件下的位置。如果你想成为一名优秀的数据结构和算法工程师,掌握单调栈是必不可少的。