Monotonic Stack

求某个区间的最大(小)值,可以使用单调队列

单调栈的作用:存放之前遍历过的元素i,当比nums[i]更大(小)的元素出现时,计算结果,并将i弹出

239. 滑动窗口最大值

  1. 维护一个单调递减的双端队列,队列大小为[0, k],队列存储数组下标,从而更好判断滑动窗口区间

  2. 存储答案的队列res大小为n - k + 1

  3. 遍历数组nums

  4. 当队列不为空且当前元素大于队尾元素在nums中对应的值时,将队尾元素弹出,从而保持队列的单调递减性

    1
    2
    while (!queue.isEmpty() && nums[i] > nums[queue.peekLast()])
    queue.pollLast();
  5. 完成步骤4后,将当前元素nums[i]加入队列

  6. 当队首元素等于i - k,说明已经遍历过k轮,滑动窗口的左边界已经超过队首元素了,弹出队首元素

    1
    2
    if (queue.peekFirst() == i - k)
    queue.pollFirst();
  7. 只有当i >= k - 1时,才记录结果res

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n - k + 1];
Deque<Integer> queue = new LinkedList();
for (int i = 0; i < n; ++i) {
while (!queue.isEmpty() && nums[i] > nums[queue.peekLast()])
queue.pollLast();
queue.offerLast(i);
if (queue.peekFirst() == i - k)
queue.pollFirst();
if (i >= k - 1)
res[i - k + 1] = nums[queue.peekFirst()];
}
return res;
}
}

单调栈是一种基于栈的数据结构,所谓的单调就是满足单调递增(单调递减)的栈。主要用于解决 下一个更大的元素问题,也就是找到下一个更大的元素。

单调栈的意义:用 O(n) 复杂度的一重遍历找到每个元素前后最近的更小/大元素位置

739. 每日温度

方法一:单调栈

  1. 创建一个栈,存储元素下标
  2. 遍历数组元素
  3. 当栈不为空且数组元素值 > 栈顶元素对应数组的值
    1. 弹出栈顶元素并用变量i - topIndex;接收
    2. topIndex的下一个更大值的距离为i - topIndex;
  4. 将i压栈

遍历完for循环,如果栈不为空,说明栈里的元素不存在比它更大的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] res = new int[n];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; ++i) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int topIndex = stack.pop();
res[topIndex] = i - topIndex;
}
stack.push(i);
}
return res;
}
}

496. 下一个更大元素 I

方法一:单调栈 + HashMap

res[i] = map.getOrDefault(nums1[i], -1);假设nums1[i]在nums2中的下标为j,如果map查不到nums1[i],说明nums2[j]在j之后没有比nums2[j]更大的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Stack<Integer> stack = new Stack<>();
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums2.length; ++i) {
while (!stack.isEmpty() && nums2[i] > nums2[stack.peek()]) {
int topIndex = stack.pop();
map.put(nums2[topIndex], nums2[i]);
}
stack.push(i);
}
int[] res = new int[nums1.length];
for (int i = 0; i < nums1.length; ++i) {
res[i] = map.getOrDefault(nums1[i], -1);
}
return res;
}
}

503. 下一个更大元素 II

方法一:单调栈

  1. 初始res数组为-1很重要!默认所有元素都没有更大的下一个元素

1
Arrays.fill(res, -1);

  1. 循环数组遍历两次,即使第一轮遍历已经被赋值,第二轮也只会赋相同的值,不影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
Stack<Integer> stack = new Stack<>();
int[] res = new int[n];
Arrays.fill(res, -1);
for (int i = 0; i < 2 * n; ++i) {
while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
int topIndex = stack.pop();
res[topIndex] = nums[i % n];
}
stack.push(i % n);
}
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
class Solution {
public String removeDuplicateLetters(String s) {
int[] count = new int[26];
for (int i = 0; i < s.length(); ++i)
++count[s.charAt(i) - 'a'];
boolean[] visited = new boolean[26];
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
--count[ch - 'a'];
if (visited[ch - 'a'])
continue;
while(!stack.isEmpty() && s.charAt(stack.peek()) > ch && count[s.charAt(stack.peek()) - 'a'] > 0) {
visited[s.charAt(stack.peek()) - 'a'] = false;
stack.pop();
}
visited[ch - 'a'] = true;
stack.push(i);
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty())
sb.append(s.charAt(stack.pollLast()));
return sb.toString();
}
}

方法一:单调栈

思路

  1. 对于一个高度,如果能得到向左和向右的边界
  2. 那么就能对每个高度求一次面积
  3. 遍历所有高度,即可得出最大面积
  4. 使用单调栈,在出栈操作时得到前后边界并计算面积

注意边界问题

1
2
Arrays.fill(rights, n);
Arrays.fill(left, -1);
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
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] rights = new int[n];
Arrays.fill(rights, n);
Deque<Integer> stack = new LinkedList();
for (int i = 0 ; i < n ; i++){
while (!stack.isEmpty() && heights[stack.getLast()] > heights[i]) {
int top = stack.pollLast();
rights[top] = i;
}
stack.addLast(i);
}
int[] left = new int[n];
Arrays.fill(left, -1);
stack.clear();
for (int i = n - 1 ; i >= 0 ; i--) {
while (!stack.isEmpty() && heights[stack.getLast()] > heights[i]) {
int top = stack.pollLast();
left[top] = i;
}
stack.addLast(i);
}
int res = 0;
for (int i = 0 ; i < n ; i++) {
res = Math.max(res, (rights[i] - left[i] - 1) * heights[i]);
}
return res;
}
}

方法一:单调栈

优质题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int trap(int[] height) {
int n = height.length, index = 0, res = 0;
Stack<Integer> stack = new Stack<>();
while (index < n) {
while (!stack.isEmpty() && height[index] > height[stack.peek()]) {
int topIndex = stack.pop();
int h = height[topIndex];
if (stack.isEmpty())
break;
int min = Math.min(height[index], height[stack.peek()]);
int width = index - stack.peek() - 1;
res += (min - h) * width;
}
stack.push(index++);
}
return res;
}
}

394. 字符串解码 (普通栈模拟)

方法一:双栈模拟

创建两个栈:1.ns(num stack)存放数字,2.is(index stack)存放StringBuilder的下标,下面会详细解释两个栈的用法。

遍历字符串s的每个字符ch,有四种情况:

  1. 数字:压入数字栈ns中

  2. 字符:放入StringBuilder中

  3. 左括号[:将StringBuilder的长度压入下标栈is中

  4. 右括号]:将数字,也就是重复次数从数字栈ns中取出来,记作t。从下标栈is中取出要复制子串的初始位置index,

    将子串sb.substring(index, sb.toString())复制t - 1次(stringbuilder中已经有一份数据了,所以是t-1)

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
class Solution {
public String decodeString(String s) {
int n = s.length();
Stack<Integer> ns = new Stack<>(), is = new Stack<>(); // numStack, indexStack
StringBuilder sb = new StringBuilder();
boolean preIsNum = false;
for (int i = 0; i < n; ++i) {
char ch = s.charAt(i);
if (ch - '0' >= 0 && ch - '0' <= 9) {
if (preIsNum) {
int pre = ns.pop();
ns.push(pre * 10 + ch - '0');
}
else
ns.push(ch - '0');
preIsNum = true;
}
else {
preIsNum = false;
if (ch == '[')
is.add(sb.length());
else if (ch == ']') {
int index = is.pop(), t = ns.pop();
String temp = sb.substring(index, sb.length());
for (int j = 0; j < t - 1; ++j) {
sb.append(temp);
}
}
else
sb.append(ch);
}
}
return sb.toString();
}
}

901. 股票价格跨度

方法一:单调栈

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
class StockSpanner {

Stack<Integer> stack;
List<Integer> nums, count;

public StockSpanner() {
stack = new Stack();
nums = new ArrayList();
count = new ArrayList();
}

public int next(int price) {
int cnt = 1;
while (!stack.isEmpty() && price >= nums.get(stack.peek()))
cnt += count.get(stack.pop());
nums.add(price);
count.add(cnt);
stack.push(nums.size() - 1);
return cnt;
}
}

/**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner obj = new StockSpanner();
* int param_1 = obj.next(price);
*/

Monotonic Stack
https://leopol1d.github.io/2023/06/02/monotonic-stack/
作者
Leopold
发布于
2023年6月2日
许可协议