Monotonic Stacks and Queues

As described in the Introduction, we can use a stack as an auxiliary data structure to solve problems and improve overall time complexity.

Let's use Daily Temperatures as our example.

A brute force approach would consist of checking every possible temperature:

from typing import List


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        res = [0] * len(temperatures)

        for i in range(n):
            for j in range(i + 1, n):
                t1 = temperatures[i]
                t2 = temperatures[j]
                if t1 < t2:
                    res[i] = j - i
                    break

        return res

Which has the following complexity:

  • Time:
  • Space:

Where is the size of the temperatures list.

Can we do better? Of course, yes.

Suppose to have an increasing sequence like [1, 2, 3, 4, 5]. The result would be [1, 1, 1, 1, 0]. In fact, the i-th temperature is always less compared to the i+1 one, except for the last one, which has no higher value.

Another example could be [1, 1, 1, 1, 100]. The corresponding result would be [4, 3, 2, 1, 0]. This example suggests that the last value is the target value for all.

So we could start from the last position and keep a monotonic increasing stack to keep track of the temperatures. In this way, the stack will contain a list of possible candidates for our target temperature.

For example, for [100, 30, 40, 50, 60, 200] we can notice that once we process the first element, the stack is:

30
40
50
60
200

In fact, since we iterate backward, we will process the first element in the last iteration. At this point, we can pop from the stack until we find a higher temperature.

Lastly, to calculate the actual distance, we also need to store the index of the i-th temperature. So, the previous stack would look something like:

(30, 1)
(40, 2)
(50, 3)
(60, 4)
(200, 5)

The optimal solution is:

from typing import List, Tuple


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        res = [0] * len(temperatures)
        stack: List[Tuple[int, int]] = []

        for i in range(n - 1, -1, -1):
            temperature = temperatures[i]

            while stack and stack[-1][0] <= temperature:
                stack.pop()

            if stack:
                res[i] = stack[-1][1] - i

            stack.append((temperature, i))

        return res

Which has the following complexity:

  • Time:
  • Space:

Where is the size of the temperatures list.