一、单调栈

单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。

单调栈模板

维护一个单调栈结构:

  1. 对于每一个元素,对栈进行处理使得加入遍历完成该元素后依然能保持单调性。
  2. 结果+将该元素存入单调栈中。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
vector<int> nextGreaterElement(vector<int>& nums) {
    vector<int> res(nums.size()); // 存放答案的数组
    stack<int> s;
    // 倒着往栈里放
    for (int i = nums.size() - 1; i >= 0; i--) {
        // 判定个子高矮
        while (!s.empty() && s.top() <= nums[i]) {
            // 矮个起开,反正也被挡着了。。。
            s.pop();
        }
        // nums[i] 身后的 next great number
        res[i] = s.empty() ? -1 : s.top();
        s.push(nums[i]);
    }
    return res;
}

496. 下一个更大元素 I

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        HashMap<Integer, Integer> map = new HashMap<>();
        int[] temp = new int[nums2.length];
        int[] res = new int[nums1.length];
        for (int i = 0; i < nums2.length; i++) {
            map.put(nums2[i], i);
        }
        ArrayDeque<Integer> q = new ArrayDeque<>();
        for (int i = nums2.length-1; i >= 0; i--) {
            while (!q.isEmpty() && nums2[i] >= q.peek()) {
                q.pop();
            }
            temp[i] = q.isEmpty() ? -1 : q.peek();
            q.push(nums2[i]);
        }
        for (int i = 0; i < nums1.length; i++) {
            int index = map.get(nums1[i]);
            res[i] = temp[index];
        }
        return res;
    }
}

503. 下一个更大元素 II

对于循环数组来说,可以用多种方式。

  1. 进行数组翻倍,这是一个常用套路。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n);
        stack<int> s;
        // 假装这个数组长度翻倍了
        for (int i = 2 * n - 1; i >= 0; i--) {
            // 索引要求模,其他的和模板一样
            while (!s.empty() && s.top() <= nums[i % n])
                s.pop();
            res[i % n] = s.empty() ? -1 : s.top();
            s.push(nums[i % n]);
        }
        return res;
    }
    
  2. 首先找到最大值索引,从这里开始进行栈的构建。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    class Solution {
        public int[] nextGreaterElements(int[] nums) {
            if (nums == null || nums.length == 0)
                return null;
            int max = nums[0], index = 0;
            for (int i = 1; i < nums.length; i++) {
                if (max < nums[i]) {
                    max = nums[i];
                    index = i;
                }
            }
            int[] res = new int[nums.length];
            res[index] = -1;
            ArrayDeque<Integer> q = new ArrayDeque<>();
            q.push(max);
            int i = index - 1;
            while (i != index) {
                if (i == -1)
                    i = nums.length - 1;
                if (i == index) break;
                while (!q.isEmpty() && nums[i] >= q.peek())
                    q.pop();
                res[i] = q.isEmpty() ? -1 : q.peek();
                q.push(nums[i]);
                i--;
            }
            return res;
        }
    }
    

二、单调队列

与单调栈一样,就是一个队列结构,只不过维护成使得队列中的元素都是单调的。

单调栈主要解决 Next Great Number一类的算法问题,而单调队列可以解决滑动窗口问题。

239. 滑动窗口最大值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length < k)
            return null;
        ArrayDeque<Integer> q = new ArrayDeque<>();
        int[] res = new int[nums.length + 1 - k];
        for (int i = 0; i < k; i++) {
            while (!q.isEmpty() && nums[i] > q.getLast())
                q.pollLast();
            q.addLast(nums[i]);
        }
        res[0] = q.getFirst();
        for (int i = 1; i <= nums.length - k; i++) {
            int left = nums[i-1], right = nums[i+k-1];
            if (left == q.getFirst())
                q.pollFirst();
            while (!q.isEmpty() && right > q.getLast())
                q.pollLast();
            q.addLast(right);
            
            res[i] = q.getFirst();
        }
        return res;
    }
}

关于单调队列的数据结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* 单调队列的实现 */
class MonotonicQueue {
    LinkedList<Integer> q = new LinkedList<>();
    public void push(int n) {
        // 将小于 n 的元素全部删除
        while (!q.isEmpty() && q.getLast() < n) {
            q.pollLast();
        }
        // 然后将 n 加入尾部
        q.addLast(n);
    }
    
    public int max() {
        return q.getFirst();
    }
    
    public void pop(int n) {
        if (n == q.getFirst()) {
            q.pollFirst();
        }
    }
}

/* 解题函数的实现 */
int[] maxSlidingWindow(int[] nums, int k) {
    MonotonicQueue window = new MonotonicQueue();
    List<Integer> res = new ArrayList<>();
    
    for (int i = 0; i < nums.length; i++) {
        if (i < k - 1) {
            //先填满窗口的前 k - 1
            window.push(nums[i]);
        } else {
            // 窗口向前滑动,加入新数字
            window.push(nums[i]);
            // 记录当前窗口的最大值
            res.add(window.max());
            // 移出旧数字
            window.pop(nums[i - k + 1]);
        }
    }
    // 需要转成 int[] 数组再返回
    int[] arr = new int[res.size()];
    for (int i = 0; i < res.size(); i++) {
        arr[i] = res.get(i);
    }
    return arr;
}