Given an array of integers arr
of length n
, you are to determine, for every possible subarray length k
(where 1 ≤ k ≤ n
), the maximum value among the minimums of all subarrays of length k
. In other words, for each possible window size k
, find the largest number that is the smallest element in some subarray of length k
.
arr
.k
from 1
to n
, report a value res[k-1]
which is the maximum of all minimums of all subarrays of length k
.res
of length n
such that res[i]
is the answer for window size i+1
.Constraints:
1 ≤ n ≤ 10^5
1 ≤ arr[i] ≤ 10^6
The naive way to solve this problem is to consider every subarray of every length, compute its minimum, and then find the maximum among those minimums for each length. But this brute-force approach is too slow for large arrays, as it would require checking O(n^2)
subarrays.
To optimize, we need to quickly find, for each possible window size, the largest value that can be the minimum in any subarray of that size. This suggests we should, for each element, determine the largest window in which it is the minimum, and then use this information to update the result for that window size.
The key insight is to use monotonic stacks to efficiently find the previous and next smaller elements for each position, which in turn tells us the window size where each element is the minimum.
We solve the problem in the following steps:
arr
, use a stack to find the index of the previous smaller element to its left.-1
(for left) or n
(for right) as a sentinel value.arr[i]
is the minimum is from prev_smaller[i] + 1
to next_smaller[i] - 1
.len = next_smaller[i] - prev_smaller[i] - 1
.arr[i]
, update res[len - 1]
to be the maximum of its current value and arr[i]
.k
is contained within all larger window sizes, propagate the maximums backwards so that res[k-1] ≥ res[k]
for all k
.This approach ensures we only need to traverse the array a few times, leading to an efficient solution.
Let's consider arr = [1, 2, 3, 2, 1]
.
arr[0] = 1
: prev = -1, next = 4arr[1] = 2
: prev = 0, next = 4arr[2] = 3
: prev = 1, next = 3arr[3] = 2
: prev = 0, next = 4arr[4] = 1
: prev = -1, next = 5[3, 2, 2, 1, 1]
O(n^2)
(checking all subarrays for all lengths)O(n)
(to store results)O(n)
(each element is pushed and popped at most once from the stack)O(n)
(for stacks and result array)The optimized solution is efficient and suitable for large arrays.
To solve the Maximum of Minimum Values in All Subarrays problem, we use monotonic stacks to efficiently find the largest window where each element is the minimum. By mapping each element to the window size it can dominate, and then propagating maximums, we produce the correct result for all window sizes in linear time. This technique is a powerful example of how stack-based approaches can optimize sliding window and range query problems.
def maxOfMin(arr):
n = len(arr)
res = [float('-inf')] * n
# Previous and next smaller elements
prev = [-1] * n
next_ = [n] * n
stack = []
# Previous smaller
for i in range(n):
while stack and arr[stack[-1]] >= arr[i]:
stack.pop()
prev[i] = stack[-1] if stack else -1
stack.append(i)
stack = []
# Next smaller
for i in range(n-1, -1, -1):
while stack and arr[stack[-1]] >= arr[i]:
stack.pop()
next_[i] = stack[-1] if stack else n
stack.append(i)
# Fill result for lengths
for i in range(n):
length = next_[i] - prev[i] - 1
res[length-1] = max(res[length-1], arr[i])
# Propagate max backwards
for i in range(n-2, -1, -1):
res[i] = max(res[i], res[i+1])
return res
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
vector<int> maxOfMin(vector<int>& arr) {
int n = arr.size();
vector<int> res(n, INT_MIN);
vector<int> prev(n, -1), next(n, n);
stack<int> st;
// Previous smaller
for (int i = 0; i < n; ++i) {
while (!st.empty() && arr[st.top()] >= arr[i])
st.pop();
prev[i] = st.empty() ? -1 : st.top();
st.push(i);
}
while (!st.empty()) st.pop();
// Next smaller
for (int i = n-1; i >= 0; --i) {
while (!st.empty() && arr[st.top()] >= arr[i])
st.pop();
next[i] = st.empty() ? n : st.top();
st.push(i);
}
// Fill result for lengths
for (int i = 0; i < n; ++i) {
int len = next[i] - prev[i] - 1;
res[len-1] = max(res[len-1], arr[i]);
}
// Propagate max backwards
for (int i = n-2; i >= 0; --i)
res[i] = max(res[i], res[i+1]);
return res;
}
import java.util.*;
public class Solution {
public int[] maxOfMin(int[] arr) {
int n = arr.length;
int[] res = new int[n];
Arrays.fill(res, Integer.MIN_VALUE);
int[] prev = new int[n];
int[] next = new int[n];
Stack<Integer> stack = new Stack<>();
// Previous smaller
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && arr[stack.peek()] >= arr[i])
stack.pop();
prev[i] = stack.isEmpty() ? -1 : stack.peek();
stack.push(i);
}
stack.clear();
// Next smaller
for (int i = n - 1; i >= 0; i--) {
while (!stack.isEmpty() && arr[stack.peek()] >= arr[i])
stack.pop();
next[i] = stack.isEmpty() ? n : stack.peek();
stack.push(i);
}
// Fill result for lengths
for (int i = 0; i < n; i++) {
int len = next[i] - prev[i] - 1;
res[len - 1] = Math.max(res[len - 1], arr[i]);
}
// Propagate max backwards
for (int i = n - 2; i >= 0; i--)
res[i] = Math.max(res[i], res[i + 1]);
return res;
}
}
function maxOfMin(arr) {
const n = arr.length;
const res = Array(n).fill(-Infinity);
const prev = Array(n).fill(-1);
const next = Array(n).fill(n);
const stack = [];
// Previous smaller
for (let i = 0; i < n; i++) {
while (stack.length && arr[stack[stack.length-1]] >= arr[i]) {
stack.pop();
}
prev[i] = stack.length ? stack[stack.length-1] : -1;
stack.push(i);
}
stack.length = 0;
// Next smaller
for (let i = n-1; i >= 0; i--) {
while (stack.length && arr[stack[stack.length-1]] >= arr[i]) {
stack.pop();
}
next[i] = stack.length ? stack[stack.length-1] : n;
stack.push(i);
}
// Fill result for lengths
for (let i = 0; i < n; i++) {
let len = next[i] - prev[i] - 1;
res[len-1] = Math.max(res[len-1], arr[i]);
}
// Propagate max backwards
for (let i = n-2; i >= 0; i--) {
res[i] = Math.max(res[i], res[i+1]);
}
return res;
}