Using Monotonic Stack
1. Understanding Monotonic Stack Technique
A monotonic stack is a stack where the elements are either entirely non-increasing or non-decreasing. This property allows for efficient solutions to problems involving comparisons between elements, particularly in scenarios where we need to find the next greater or smaller element.
| Property | Description | Usage Example |
|---|---|---|
| Monotonic Property | Elements in the stack follow a consistent order (increasing or decreasing) | Stack maintains elements in increasing order from bottom to top |
| Time Complexity | O(n) amortized for processing n elements | Each element is pushed and popped at most once |
| Space Complexity | O(n) in worst case (all elements in stack) | Stack stores up to n elements |
Example: Finding Next Greater Element
function nextGreaterElement(arr) {
const result = new Array(arr.length).fill(-1);
const stack = [];
for (let i = arr.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] <= arr[i]) {
stack.pop();
}
if (stack.length > 0) {
result[i] = stack[stack.length - 1];
}
stack.push(arr[i]);
}
return result;
}
// Example usage:
const nums = [4, 5, 2, 10];
console.log(nextGreaterElement(nums)); // [5, 10, 10, -1]
2. Finding Next Greater Element I
| Property | Description | Example |
|---|---|---|
| Input | Array of integers | [1, 2, 1] |
| Output | Array where each element is replaced by the next greater element to its right | [2, -1, -1] |
| Approach | Monotonic stack to efficiently find next greater elements |
Example: Finding Next Greater Element
function nextGreaterElement(nums) {
const result = new Array(nums.length).fill(-1);
const stack = [];
for (let i = nums.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] <= nums[i]) {
stack.pop();
}
if (stack.length && stack[stack.length - 1] > nums[i]) {
result[i] = stack[stack.length - 1];
}
stack.push(nums[i]);
}
return result;
}
// Example usage:
const input = [1, 2, 1];
console.log(nextGreaterElement(input)); // Output: [2, -1, -1]
3. Finding Next Greater Element II
Given a circular array, find the next greater element for each element in the array. The next greater element of an element is the first greater element to its right, wrapping around to the beginning if necessary.
| Property | Description | Example |
|---|---|---|
| Input | Circular array of integers | [1, 2, 1] |
| Output | Array of next greater elements | [2, -1, 2] |
Example: Finding Next Greater Element II
function nextGreaterElements(nums) {
const n = nums.length;
const result = new Array(n).fill(-1);
const stack = [];
// Traverse the array twice to simulate circular behavior
for (let i = 2 * n - 1; i >= 0; i--) {
const num = nums[i % n];
while (stack.length && stack[stack.length - 1] <= num) {
stack.pop();
}
if (stack.length && i < n) {
result[i] = stack[stack.length - 1];
}
stack.push(num);
}
return result;
}
// Example usage
const nums = [1, 2, 1];
console.log(nextGreaterElements(nums)); // Output: [2, -1, 2]
4. Finding Next Smaller Element
Given an array of integers, the "Next Smaller Element" problem asks us to find the first smaller element to the right of each element in the array. If no such element exists, we return -1 for that position.
| Element | Next Smaller Element | Explanation |
|---|---|---|
| 4 | 2 |
First element smaller than 4 to its right is 2 |
| 2 | 1 |
First element smaller than 2 to its right is 1 |
| 1 | -1 |
No element to the right is smaller than 1 |
| 5 | 3 |
First element smaller than 5 to its right is 3 |
| 3 | -1 |
No element to the right is smaller than 3 |
Example: Algorithm Implementation
function nextSmallerElement(arr) {
const result = [];
const stack = [];
for (let i = arr.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] >= arr[i]) {
stack.pop();
}
if (stack.length === 0) {
result.push(-1);
} else {
result.push(stack[stack.length - 1]);
}
stack.push(arr[i]);
}
return result.reverse();
}
// Example usage:
const input = [4, 2, 1, 5, 3];
console.log(nextSmallerElement(input)); // Output: [2, 1, -1, 3, -1]
5. Finding Previous Greater Element
| Element | Previous Greater Element | Index | Notes |
|---|---|---|---|
| arr | [4, 10, 5, 18, 3] |
[1, 2, 3, 4, 5] |
Input array of integers |
Example: Finding Previous Greater Element
const arr = [4, 10, 5, 18, 3];
const result = [];
const stack = [];
for (let i = 0; i < arr.length; i++) {
while (stack.length > 0 && arr[stack[stack.length - 1]] <= arr[i]) {
stack.pop();
}
if (stack.length === 0) {
result.push(-1); // No previous greater element
} else {
result.push(stack[stack.length - 1]); // Index of previous greater element
}
stack.push(i);
}
console.log(result); // [-1, 0, 1, 2, -1]
6. Finding Stock Span
The stock span problem is a classic application of monotonic stacks. For each day, we want to find how many consecutive previous days (including the current day) have stock prices less than or equal to the current price.
| Input | Description | Example |
|---|---|---|
| prices | Array of stock prices for consecutive days | [100, 80, 60, 70, 60, 75, 85] |
| Output | Description | Example |
|---|---|---|
| span | Array where each element represents the span for that day | [1, 1, 1, 2, 1, 4, 6] |
Example: Calculating Stock Span
function calculateSpan(prices) {
const span = new Array(prices.length);
const stack = [];
for (let i = 0; i < prices.length; i++) {
while (stack.length > 0 && prices[stack[stack.length - 1]] <= prices[i]) {
stack.pop();
}
span[i] = stack.length === 0 ? i + 1 : i - stack[stack.length - 1];
stack.push(i);
}
return span;
}
// Example usage:
const prices = [100, 80, 60, 70, 60, 75, 85];
console.log(calculateSpan(prices)); // [1, 1, 1, 2, 1, 4, 6]
7. Finding Largest Rectangle Area
Given an array of heights representing bars in a histogram, find the largest rectangular area that can be formed.
| Property | Description | Notes |
|---|---|---|
| Input | Array of integers representing bar heights | Each height is a non-negative integer |
| Output | Maximum area of rectangle in histogram | Area = height × width |
Example: Histogram with Heights [2, 1, 5, 6, 2, 3]
function largestRectangleArea(heights) {
const stack = [];
let maxArea = 0;
for (let i = 0; i <= heights.length; i++) {
const currentHeight = i === heights.length ? 0 : heights[i];
while (stack.length > 0 && heights[stack[stack.length - 1]] > currentHeight) {
const height = heights[stack.pop()];
const width = i - (stack.length === 0 ? -1 : stack[stack.length - 1]) - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
// Example usage:
const heights = [2, 1, 5, 6, 2, 3];
console.log(largestRectangleArea(heights)); // Output: 10
8. Finding Maximal Rectangle
Given a binary matrix filled with 0's and 1's, find the largest rectangle containing only 1's and return its area.
| Property | Description | Example |
|---|---|---|
| Input | Binary matrix (2D array of 0s and 1s) | |
| Output | Maximum area of rectangle with all 1s | 6 |
Example: Maximal Rectangle
function maximalRectangle(matrix) {
if (!matrix || matrix.length === 0 || matrix[0].length === 0) return 0;
const rows = matrix.length;
const cols = matrix[0].length;
const heights = new Array(cols).fill(0);
let maxArea = 0;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
heights[j] = matrix[i][j] === '1' ? heights[j] + 1 : 0;
}
const area = largestRectangleInHistogram(heights);
maxArea = Math.max(maxArea, area);
}
return maxArea;
}
function largestRectangleInHistogram(heights) {
const stack = [];
let maxArea = 0;
for (let i = 0; i <= heights.length; i++) {
while (stack.length && (i === heights.length || heights[stack[stack.length - 1]] > heights[i])) {
const h = heights[stack.pop()];
const w = i - (stack.length ? stack[stack.length - 1] + 1 : 0);
maxArea = Math.max(maxArea, h * w);
}
stack.push(i);
}
return maxArea;
}
// Example usage:
const matrix = [
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
];
console.log(maximalRectangle(matrix)); // Output: 6
9. Removing Duplicate Letters
Use a monotonic stack to remove duplicate letters from a string while maintaining the lexicographically smallest result.
| Step | Action | Description |
|---|---|---|
| 1 | stack = [] |
Initialize an empty stack to maintain the result. |
| 2 | last_occurrence = map of char to last index |
Record the last occurrence of each character in the string. |
| 3 | used = set of chars in stack |
Track characters already in the stack to avoid duplicates. |
| 4 | Iterate through each character in the string | For each character, check if it can be added to the stack. |
| 5 | while stack and stack[-1] > char and last_occurrence[stack[-1]] > i |
Pop from stack if the top element is larger than current char and will appear later. |
| 6 | stack.append(char) |
Add current character to stack if not already used. |
Example: Removing Duplicates
function removeDuplicateLetters(s) {
const lastOccurrence = {};
for (let i = 0; i < s.length; i++) {
lastOccurrence[s[i]] = i;
}
const stack = [];
const used = new Set();
for (let i = 0; i < s.length; i++) {
const char = s[i];
if (used.has(char)) continue;
while (stack.length > 0 &&
stack[stack.length - 1] > char &&
lastOccurrence[stack[stack.length - 1]] > i) {
const removed = stack.pop();
used.delete(removed);
}
stack.push(char);
used.add(char);
}
return stack.join('');
}
// Example usage:
console.log(removeDuplicateLetters("bcabc")); // "abc"
console.log(removeDuplicateLetters("cbacdcbc")); // "acdb"
10. Finding Sum of Subarray Minimums
This problem involves finding the sum of minimum elements across all contiguous subarrays of an array. A monotonic stack approach efficiently computes this by determining for each element how many subarrays it is the minimum of.
| Property | Description | Notes |
|---|---|---|
| Input | Array of integers | Can contain duplicates |
| Output | Sum of minimums across all subarrays | Modulo 10^9 + 7 |
Example: Calculate sum of subarray minimums
function sumSubarrayMins(arr) {
const MOD = 1000000007;
const n = arr.length;
const left = new Array(n);
const right = new Array(n);
const stack = [];
// Find left boundaries (first smaller element to the left)
for (let i = 0; i < n; i++) {
while (stack.length && arr[stack[stack.length - 1]] > arr[i]) {
stack.pop();
}
left[i] = stack.length === 0 ? -1 : stack[stack.length - 1];
stack.push(i);
}
stack.length = 0;
// Find right boundaries (first smaller element to the right)
for (let i = n - 1; i >= 0; i--) {
while (stack.length && arr[stack[stack.length - 1]] >= arr[i]) {
stack.pop();
}
right[i] = stack.length === 0 ? n : stack[stack.length - 1];
stack.push(i);
}
let result = 0;
for (let i = 0; i < n; i++) {
const count = (i - left[i]) * (right[i] - i);
result = (result + arr[i] * count) % MOD;
}
return result;
}
// Example usage:
console.log(sumSubarrayMins([3, 1, 2, 4])); // Output: 17
11. Trapping Rain Water
Given an array of non-negative integers representing heights of bars, compute how much water can be trapped after raining.
| Property | Description | Example |
|---|---|---|
| height | Array of non-negative integers representing bar heights | [0,1,0,2,1,0,1,3,2,1,2,1] |
| waterTrapped | Total units of water that can be trapped | 6 |
Example: Trapping Rain Water
function trap(height) {
let left = 0, right = height.length - 1;
let leftMax = 0, rightMax = 0;
let water = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
water += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
water += rightMax - height[right];
}
right--;
}
}
return water;
}
// Example usage:
const height = [0,1,0,2,1,0,1,3,2,1,2,1];
console.log(trap(height)); // Output: 6
12. Finding Maximum Width Ramp
A ramp in an array is defined as a pair of indices (i, j) where i <= j and A[i] <= A[j]. The width of the ramp is j - i. The task is to find the maximum width ramp in the array.
| Property | Description | Example |
|---|---|---|
| Array | Input array of integers | [6, 0, 8, 2, 1, 5] |
| Ramp | Pair (i, j) where i <= j and A[i] <= A[j] | (1, 5) with values (0, 5) |
| Width | Distance between indices j - i | 5 - 1 = 4 |
Example: Finding Maximum Width Ramp
function maxWidthRamp(nums) {
let stack = [];
let n = nums.length;
// Build a decreasing stack of indices
for (let i = 0; i < n; i++) {
if (stack.length === 0 || nums[stack[stack.length - 1]] > nums[i]) {
stack.push(i);
}
}
let maxWidth = 0;
// Traverse from right to left to find max width
for (let j = n - 1; j >= 0; j--) {
while (stack.length > 0 && nums[stack[stack.length - 1]] <= nums[j]) {
let i = stack.pop();
maxWidth = Math.max(maxWidth, j - i);
}
}
return maxWidth;
}
// Example usage
console.log(maxWidthRamp([6, 0, 8, 2, 1, 5])); // Output: 4