Skip to content

Latest commit

 

History

History
66 lines (50 loc) · 4.18 KB

File metadata and controls

66 lines (50 loc) · 4.18 KB

单调栈

单调栈要求栈中的元素是单调递增的或者单调递减的。

  1. 单调递增(不减)栈可以找到左边第一个比当前出栈元素小(包含等于)的元素。
  2. 单调递减(不增)栈可以找到左边第一个比当前出栈元素大(包含等于)的元素。

是否严格递增或递减可以根据实际情况来。

以左边为栈底,右边为栈顶为例:

  • [1,2,3] 就是一个单调递减栈(因为此时的出栈顺序是 3,2,1。下同,不再赘述)
  • [3,2,1] 就是一个单调递增栈
  • [1,3,2] 就不是一个合法的单调栈

适用场景

单调栈适合的题目是求解下一个大于 x 或者下一个小于 x 的问题。

比如我们需要依次将数组 [1,3,4,5,2,9,6] 压入单调栈。

  1. 首先压入 1,此时的栈为:[1]
  2. 继续压入 3,此时的栈为:[1,3]
  3. 继续压入 4,此时的栈为:[1,3,4]
  4. 继续压入 5,此时的栈为:[1,3,4,5]
  5. 如果继续压入 2,此时的栈为:[1,3,4,5,2] 不满足单调递减栈的特性, 因此需要调整。如何调整?由于栈只有 pop 操作,因此我们只好不断 pop,直到满足单调递减为止。
  6. 上面其实我们并没有压入 2,而是先 pop,pop 到压入 2 依然可以保持单调递减再 压入 2,此时的栈为:[1,2]
  7. 继续压入 9,此时的栈为:[1,2,9]
  8. 如果继续压入 6,则不满足单调递减栈的特性, 我们故技重施,不断 pop,直到满足单调递减为止。此时的栈为:[1,2,6]

注意这里的栈仍然是非空的,如果有的题目需要用到所有数组的信息,那么很有可能因没有考虑边界而不能通过所有的测试用例。 这里介绍一个技巧 - 哨兵法,这个技巧经常用在单调栈的算法中。

对于上面的例子,我可以在原数组 [1,3,4,5,2,9,6] 的右侧添加一个小于数组中最小值的项即可,比如 -1。此时的数组是 [1,3,4,5,2,9,6,-1]。这种技巧可以简化代码逻辑,大家尽量掌握。

上面的例子如果你明白了,就不难理解为啥单调栈适合求解下一个大于 x 或者下一个小于 x 这种题目了。比如上面的例子,我们就可以很容易地求出在其之后第一个小于其本身的位置。比如 3 的索引是 1,小于 3 的第一个索引是 4。也比如 2 的索引 4,小于 2 的第一个索引是 0,但是其在 2 的索引 4 之后,因此不符合条件,也就是不存在在 2 之后第一个小于 2 本身的位置

上面的例子,我们在第 6 步开始 pop,第一个被 pop 出来的是 5,因此 5 之后的第一个小于 5 的索引就是 4。同理被 pop 出来的 3,4,5 也都是 4。

如果用 ans 来表示在其之后第一个小于其本身的位置,ans[i] 表示 arr[i] 之后第一个小于 arr[i] 的位置, ans[i] 为 -1 表示这样的位置不存在,比如前文提到的 2。那么此时的 ans 是 [-1,4,4,4,-1,-1,-1]。

第 8 步,我们又开始 pop 了。此时 pop 出来的是 9,因此 9 之后第一个小于 9 的索引就是 6。

这个算法的过程用一句话总结就是,如果压栈之后仍然可以保持单调性,那么直接压。否则先弹出栈的元素,直到压入之后可以保持单调性。 这个算法的原理用一句话总结就是,被弹出的元素都是大于当前元素的,并且由于栈是单调增的,因此在其之后小于其本身的最近的元素就是当前元素了

伪代码

class Solution {
    public int[] monostoneStack(int[] arr) {
        int n = arr.length;
        int[] answer = new int[n];
        Deque<Integer> stack = new LinkedList();
        for(int i = 0; i < n; i++) {
            while (stack.size() != 0 && arr[stack.peek()] < arr[i]) {
                int index = stack.pop();
                answer[index] = i - index;
            }
            stack.push(i);
        }
        return answer;
    }
}

题目推荐

下面几个题帮助你理解单调栈, 并让你明白什么时候可以用单调栈进行算法优化。