Avrêbarra~
LN3: Monotonic Stack
- Written on December 12, 2023

Welcome to the third post for the series Learning Notes.

Bear with me; there are many things I have to take notes on in this world.

(also, you don’t have to read these, though, haha).

Monotonic Stack Concepts

For now, it’s about monotonic stack.

So monotonic stack is when we’re doing a type of stack that only allows elements in non-decreasing or non-increasing order, facilitating efficient computations related to finding nearest larger/smaller values in a series.

Stack in itself is a data structure. But monotonic stack is not a specific data structure because it doesn’t actually store data differently than stack nor enforcing a rule; instead, it’s more like a technique.

How It Is Useful

This technique is useful in these cases:

So basically around finding next larger or smaller items in series effectively.

Example Code

Example solution to finding the next larger number in a series, we can do it like this:

const findNextLarger = (arr) => {
    const result = [];
    const stack = [];

    for (let i = 0; i < arr.length; i++) {
        const cur = arr[i];
        console.log(`Processing element ${arr[i]} at index ${i}`);
        console.log("Stack:", stack);

        while (stack.length && arr[i] > arr[stack[stack.length - 1]]) {
            const index = stack.pop();
            result[index] = arr[i]; // Next larger element found
            console.log(
                `   Found next larger element ${arr[i]} for index ${index}`
            );
        }

        stack.push(i);
        console.log(`   Pushed index ${i} onto the stack`);
        console.log("Result:", result);
    }

    // Remaining elements in stack have no larger elements
    while (stack.length) {
        const index = stack.pop();
        result[index] = -1; // No larger element found
        console.log(`   No larger element found for index ${index}`);
    }

    return result;
};

const result = findNextLarger([2, 4, 1, 2, 5, 1]);
console.log("Final Result:", result);
Output:
Processing element 2 at index 0
Stack: []
   Pushed index 0 onto the stack
Result: []
Processing element 4 at index 1
Stack: [ 0 ]
   Found next larger element 4 for index 0
   Pushed index 1 onto the stack
Result: [ 4 ]
Processing element 1 at index 2
Stack: [ 1 ]
   Pushed index 2 onto the stack
Result: [ 4 ]
Processing element 2 at index 3
Stack: [ 1, 2 ]
   Found next larger element 2 for index 2
   Pushed index 3 onto the stack
Result: [ 4, <1 empty item>, 2 ]
Processing element 5 at index 4
Stack: [ 1, 3 ]
   Found next larger element 5 for index 3
   Found next larger element 5 for index 1
   Pushed index 4 onto the stack
Result: [ 4, 5, 2, 5 ]
Processing element 1 at index 5
Stack: [ 4 ]
   Pushed index 5 onto the stack
Result: [ 4, 5, 2, 5 ]
   No larger element found for index 5
   No larger element found for index 4
Final Result: [ 4, 5, 2, 5, -1, -1 ]

Initially, I struggled to grasp the process, but after jotting it down, I realized basically what it did are:

List of LeetCode problems that relate to this concept:

Closing Words

Basically, this technique revolves around finding the nearest larger or smaller value changes of a rule in a series, allowing for efficient computation in various scenarios.

I’m not gonna glorify this one; I really don’t know if there are any other uses for this, haha.

But yeah, still, a nice day for another tool in our arsenal!

Let’s meet again in next notes.