Introduction to Monotonic Stack – Data Structure and Algorithm Tutorials
What is a Monotonic Stack?
A monotonic stack is a stack whose elements are monotonically increasing or decreasing. It contains all qualities that a typical stack has and its elements are all monotonic decreasing or increasing.
Below are the features of a monotonic stack:
- It is a range of queries in an array situation
- The minima/maxima elements
- When an element is popped from the monotonic stack, it will never be utilised again.
The monotonic stack problem is mainly the previous/next smaller/larger problem. It maintains monotonicity while popping elements when a new item is pushed into the stack.
Monotonic Stack
Let’s understand the term Monotonic Stacks by breaking it down.
Monotonic: It is a word for mathematics functions. A function y = f(x) is monotonically increasing or decreasing when it follows the below conditions:Â
- As x increases, y also increases always, then it’s a monotonically increasing function.Â
- As x increases, y decreases always, then it’s a monotonically decreasing function.
See the below examples:
- y = 2x +5, it’s a monotonically increasing function.
- y = -(2x), it’s a monotonically decreasing function. Â
Similarly, A stack is called a monotonic stack if all the elements starting from the bottom of the stack is either in increasing or in decreasing order.
Note: Follow the given link to know about How to Identify and Solve Monotonic Stack Problems
Types of Monotonic Stack:
There are 2 types of monotonic stacks:
- Monotonic Increasing Stack
- Monotonic Decreasing Stack
Monotonic Increasing Stack:
It is a stack in which the elements are in increasing order from the bottom to the top of the stack.Â
Example: 1, 3, 10, 15, 17
How to achieve Monotonic Increasing Stack?
To create a Monotonic Increasing Stack, start with an empty stack, then, while iterating through elements in a sequence, keep removing elements from the stack as long as they are smaller than the current element, and push the current element onto the stack. This process ensures the stack maintains a strictly increasing order from bottom to top.
Steps to implement:
- As we need monotonically increasing stack, we should not have a smaller element at top of a bigger element.
- So Iterate the given list of elements one by one :
- Before pushing into the stack, POP all the elements till either of one condition fails:
- Stack is not empty
- Stack’s top is bigger than the element to be inserted.
- Then push the element into the stack.
See the illustration below to understand the idea:
Consider an array Arr[] = {1, 4, 5, 3, 12, 10}
For i = 0: stk = {1}
For i = 1: stk = {1, 4}
For i = 2: stk = {1, 4, 5}
For i = 3: stk = {1, 3} Â [pop 4 and 5 as 4 > 3 and 5 > 3]
For i = 4: stk = {1, 3, 12}
For i = 5: stk = {1, 3, 10} [pop 12 as 12 > 10]Â
Below is the code for the above approach:
C++
#include <bits/stdc++.h>
using namespace std;
void increasingStack( int arr[], int N)
{
stack< int > stk;
for ( int i = 0; i < N; i++) {
while (stk.size() > 0 && stk.top() > arr[i]) {
stk.pop();
}
stk.push(arr[i]);
}
int N2 = stk.size();
int ans[N2] = { 0 };
int j = N2 - 1;
while (!stk.empty()) {
ans[j] = stk.top();
stk.pop();
j--;
}
cout << "The Array: " ;
for ( int i = 0; i < N; i++) {
cout << arr[i] << " " ;
}
cout << endl;
cout << "The Stack: " ;
for ( int i = 0; i < N2; i++) {
cout << ans[i] << " " ;
}
cout << endl;
}
int main()
{
int arr[] = { 1, 4, 5, 3, 12, 10 };
int N = sizeof (arr) / sizeof (arr[0]);
increasingStack(arr, N);
return 0;
}
|
Java
import java.io.*;
import java.util.*;
class GFG {
static void increasingStack( int [] arr, int N)
{
Stack<Integer> stk = new Stack<>();
for ( int i = 0 ; i < N; i++) {
while (stk.size() > 0 && stk.peek() > arr[i]) {
stk.pop();
}
stk.push(arr[i]);
}
int N2 = stk.size();
int [] ans = new int [N2];
Arrays.fill(ans, 0 );
int j = N2 - 1 ;
while (!stk.isEmpty()) {
ans[j] = stk.peek();
stk.pop();
j--;
}
System.out.print( "The Array: " );
for ( int i = 0 ; i < N; i++) {
System.out.print(arr[i] + " " );
}
System.out.println();
System.out.print( "The Stack: " );
for ( int i = 0 ; i < N2; i++) {
System.out.print(ans[i] + " " );
}
System.out.println();
}
public static void main(String[] args)
{
int [] arr = { 1 , 4 , 5 , 3 , 12 , 10 };
int N = arr.length;
increasingStack(arr, N);
}
}
|
Python3
def increasingStack(arr, N):
stk = []
for i in range (N):
while ( len (stk) > 0 and stk[ len (stk) - 1 ] > arr[i]):
stk.pop()
stk.append(arr[i])
N2 = len (stk)
ans = [ 0 ] * N2
j = N2 - 1
while ( len (stk) ! = 0 ):
ans[j] = stk[ len (stk) - 1 ]
stk.pop()
j = j - 1
print ( "The Array: " ,end = "")
for i in range (N):
print (arr[i],end = " " )
print ()
print ( "The Stack: " ,end = "")
for i in range (N2):
print (ans[i],end = " " )
print ()
arr = [ 1 , 4 , 5 , 3 , 12 , 10 ]
N = len (arr)
increasingStack(arr,N)
|
C#
using System;
using System.Collections.Generic;
public class GFG{
static void increasingStack( int [] arr, int N)
{
Stack< int > stk = new Stack< int >();
for ( int i = 0; i < N; i++)
{
while (stk.Count > 0 && stk.Peek() > arr[i])
{
stk.Pop();
}
stk.Push(arr[i]);
}
int N2 = stk.Count;
int [] ans = new int [N2];
Array.Fill(ans, 0);
int j = N2 - 1;
while (stk.Count > 0)
{
ans[j] = stk.Peek();
stk.Pop();
j--;
}
Console.Write( "The Array: " );
for ( int i = 0; i < N; i++)
{
Console.Write(arr[i] + " " );
}
Console.WriteLine();
Console.Write( "The Stack: " );
for ( int i = 0; i < N2; i++)
{
Console.Write(ans[i] + " " );
}
Console.WriteLine();
}
static public void Main (){
int [] arr = { 1, 4, 5, 3, 12, 10 };
int N = arr.Length;
increasingStack(arr, N);
}
}
|
Javascript
function increasingStack(a, N) {
let stk = [];
for (let i = 0; i < N; i++) {
while (stk.length > 0 && stk[stk.length - 1] > arr[i]) {
stk.pop();
}
stk.push(arr[i]);
}
let N2 = stk.length;
let ans = new Array(N2);
let j = N2 - 1;
while (stk.length != 0) {
ans[j] = stk[stk.length - 1];
stk.pop();
j--;
}
console.log( "The Array: " );
for (let i = 0; i < N; i++) {
console.log(arr[i] + " " );
}
console.log( "<br>" );
console.log( "The Stack: " );
for (let i = 0; i < N2; i++) {
console.log(ans[i] + " " );
}
console.log( "<br>" );
}
let arr = [1, 4, 5, 3, 12, 10];
let N = arr.length;
increasingStack(arr, N);
|
Output
The Array: 1 4 5 3 12 10
The Stack: 1 3 10
Time Complexity: O(N)
Auxiliary Space: O(N)
Monotonic Decreasing Stack:
A stack is monotonically decreasing if It’s elements are in decreasing order from the bottom to the top of the stack.Â
Example: 17, 14, 10, 5, 1
How to achieve Monotonic Decreasing Stack?
To create a Monotonic Decreasing Stack, begin with an empty stack, then, while iterating through elements in a sequence, continuously remove elements from the stack as long as they are smaller than or equal to the current element, and finally, push the current element onto the stack. This process ensures the stack maintains a monotonic decreasing order from bottom to top.
Steps to implement:
- As we need monotonically decreasing stack, we should not have a bigger element at top of a smaller element.
- So Iterate the elements of the list one by one:
- Before pushing into the stack, POP all the elements till either of one condition fails:
- Stack is not empty
- Stack’s top is smaller than the element to be Inserted.
- Then push the element into the stack.
See the below illustration for a better understanding:
Consider an array: arr[] = {15, 17, 12, 13, 14, 10}
For i = 0: stk = {15}
For i = 1: stk = {17} [pop 15 as 15 < 17]
For i = 2: stk = {17, 12}
For i = 3: stk = {17, 13} Â [pop 12 as 12 < 13]
For i = 4: stk = {17, 14} Â [pop 13 as 13 < 14]
For i = 5: stk = {17, 14, 10}
Below is the implementation of the above approach:
C++
#include <bits/stdc++.h>
using namespace std;
void decreasingStack( int arr[], int N)
{
stack< int > stk;
for ( int i = 0; i < N; i++) {
while (stk.size() > 0 && stk.top() < arr[i]) {
stk.pop();
}
stk.push(arr[i]);
}
int N2 = stk.size();
int ans[N2] = { 0 };
int j = N2 - 1;
while (!stk.empty()) {
ans[j] = stk.top();
stk.pop();
j--;
}
cout << "The Array: " ;
for ( int i = 0; i < N; i++) {
cout << arr[i] << " " ;
}
cout << endl;
cout << "The Stack: " ;
for ( int i = 0; i < N2; i++) {
cout << ans[i] << " " ;
}
cout << endl;
}
int main()
{
int arr[] = { 15, 17, 12, 13, 14, 10 };
int N = sizeof (arr) / sizeof (arr[0]);
decreasingStack(arr, N);
return 0;
}
|
Java
import java.util.*;
public class Main {
static void decreasingStack( int arr[], int N)
{
Stack<Integer> stk = new Stack<Integer>();
for ( int i = 0 ; i < N; i++) {
while (stk.size() > 0 && stk.peek() < arr[i]) {
stk.pop();
}
stk.push(arr[i]);
}
int N2 = stk.size();
int ans[] = new int [N2];
int j = N2 - 1 ;
while (!stk.empty()) {
ans[j] = stk.peek();
stk.pop();
j--;
}
System.out.print( "The Array: " );
for ( int i = 0 ; i < N; i++) {
System.out.print(arr[i] + " " );
}
System.out.println();
System.out.print( "The Stack: " );
for ( int i = 0 ; i < N2; i++) {
System.out.print(ans[i] + " " );
}
System.out.println();
}
public static void main(String args[])
{
int arr[] = { 15 , 17 , 12 , 13 , 14 , 10 };
int N = arr.length;
decreasingStack(arr, N);
}
}
|
Python3
def decreasingStack(arr, N):
stack = []
for i in range (N):
while len (stack)> 0 and stack[ - 1 ] < arr[i]:
stack.pop()
stack.append(arr[i])
N2 = len (stack)
ans = [ 0 ] * N2
j = N2 - 1
while stack ! = []:
ans[j] = stack.pop()
j - = 1
print ( 'The array: ' ,end = ' ' )
for i in range (N):
print (arr[i],end = ' ' )
print ()
print ( 'The array: ' ,end = ' ' )
for i in range (N2):
print (ans[i],end = ' ' )
print ()
arr = [ 15 , 17 , 12 , 13 , 14 , 10 ]
N = len (arr)
decreasingStack(arr, N)
|
C#
using System;
using System.Collections.Generic;
public class GFG {
static void decreasingStack( int [] arr, int N)
{
Stack< int > stk = new Stack< int >();
for ( int i = 0; i < N; i++) {
while (stk.Count > 0 && stk.Peek() < arr[i]) {
stk.Pop();
}
stk.Push(arr[i]);
}
int N2 = stk.Count;
int [] ans = new int [N2];
int j = N2 - 1;
while (stk.Count > 0) {
ans[j] = stk.Peek();
stk.Pop();
j--;
}
Console.Write( "The Array: " );
for ( int i = 0; i < N; i++) {
Console.Write(arr[i] + " " );
}
Console.WriteLine();
Console.Write( "The Stack: " );
for ( int i = 0; i < N2; i++) {
Console.Write(ans[i] + " " );
}
Console.WriteLine();
}
public static void Main( string [] args)
{
int [] arr = { 15, 17, 12, 13, 14, 10 };
int N = arr.Length;
decreasingStack(arr, N);
}
}
|
Javascript
function decreasingStack(arr, N)
{
let stk = [];
for (let i = 0; i < N; i++)
{
while (stk.length > 0 && stk[0] < arr[i]) {
stk.shift();
}
stk.unshift(arr[i]);
}
let N2 = stk.length;
let ans = Array(N2).fill(0);
let j = N2 - 1;
while (stk.length != 0) {
ans[j] = stk[0];
stk.shift();
j--;
}
console.log( "The Array: " );
for (let i = 0; i < N; i++) {
console.log(arr[i]);
}
console.log( "The Stack: " );
for (let i = 0; i < N2; i++) {
console.log(ans[i]);
}
}
let arr = [15, 17, 12, 13, 14, 10];
let N = arr.length;
decreasingStack(arr, N);
|
Output
The Array: 15 17 12 13 14 10
The Stack: 17 14 10
Time Complexity: O(N)
Auxiliary Space: O(N)Â
Note that this implementation assumes that all elements in the input array are distinct. If there are duplicates, we need to modify the implementation to handle them correctly.
Applications of Monotonic Stack :
- Monotonic stack is generally used to deal with a typical problem like Next Greater Element. NGE (Find the first value on the right that is greater than the element.
- Also can be used for its varieties.
- Next Smaller Element
- Previous Greater Element
- Previous Smaller Element
- Also, we use it to get the greatest or smallest array or string by the given conditions (remaining size k/ no duplicate).
- To understand the optimization power of monotonic stacks, let’s take this example problem: Minimum Cost Tree From Leaf Values. This problem can be solved in 3 different algorithm ways, out of which the monotonic stack is the most optimized approach.
- Dynamic Programming Algorithmic Approach: O(N^3) Time O(N^2) Space
- Greedy Algorithmic Approach: O(N^2) Time O(1) Space
- Monotonic Stack Algorithmic Approach: O(N) Time O(N) Space
Follow the link to know How to Identify and Solve the Monotonic Stack Problems
Advantages of Monotonic Stack:
- We can use the extra space of a monotonic stack to reduce the time complexity.
- We can get the nearest smaller or greater element depending on the monotonic stack type, by just retrieving the stack’s top element, which is just an O(1) operation.
- The monotonic stack helps us maintain maximum and minimum elements in the range and keeps the order of elements in the range. Therefore, we don’t need to compare elements one by one again to get minima and maxima in the range. Meanwhile, because it keeps the element’s order, we only need to update the stack based on the newest added element.
Disadvantages of Monotonic Stack:
- It increases the space complexity of the algorithm by a factor of O(N), i.e. by a linear complexity.
- It is often more complex to handle as now with the existing problem, we also need to handle the stack carefully. As once the elements are popped from the stack, we cannot get them back.
Related Articles:
Last Updated :
24 Jan, 2024
Like Article
Save Article
Share your thoughts in the comments
Please Login to comment...