Maximum of Minimum Values in All Subarrays
Problem​
You are given an integer array nums of size n. You are asked to solve n queries for each integer i
in the range 0 <= i < n
.
To solve the ith query:
1- Find the minimum value in each possible subarray of size i + 1
of
the array nums.
2- Find the maximum of those minimum values. This maximum is the
answer to the query.
Return a 0-indexed
integer array ans of size n such that ans[i]
is the answer to the ith query
.
A subarray is a contiguous sequence of elements in an array.
Examples​
Example 1:
`Input: nums = [0,1,2,4] Output: [4,2,1,0] Explanation: i=0:
- The subarrays of size 1 are [0], [1], [2], [4]. The minimum values are 0, 1, 2, 4.
- The maximum of the minimum values is 4. i=1:
- The subarrays of size 2 are [0,1], [1,2], [2,4]. The minimum values are 0, 1, 2.
- The maximum of the minimum values is 2. i=2:
- The subarrays of size 3 are [0,1,2], [1,2,4]. The minimum values are 0, 1.
- The maximum of the minimum values is 1. i=3:
- There is one subarray of size 4, which is [0,1,2,4]. The minimum value is 0.
- There is only one value, so the maximum is 0.`
Example 2:
`Input: nums = [10,20,50,10] Output: [50,20,10,10] Explanation: i=0:
- The subarrays of size 1 are [10], [20], [50], [10]. The minimum values are 10, 20, 50, 10.
- The maximum of the minimum values is 50. i=1:
- The subarrays of size 2 are [10,20], [20,50], [50,10]. The minimum values are 10, 20, 10.
- The maximum of the minimum values is 20. i=2:
- The subarrays of size 3 are [10,20,50], [20,50,10]. The minimum values are 10, 10.
- The maximum of the minimum values is 10. i=3:
- There is one subarray of size 4, which is [10,20,50,10]. The minimum value is 10.
- There is only one value, so the maximum is 10.`
Constraints​
n == nums.length
1 <= n <= 10^5
0 <= nums[i] <= 109
.
Approach​
The implementation of the solution involves a single pass algorithm using the concept of Monotonic Stacks for finding the next smaller elements on both left and right sides for each element in the input array nums. This is essential in determining the maximum minimum for different subarray sizes.
Here's a step-by-step walkthrough of the solution:
Initialize two arrays left and right with lengths equal to that of nums. Set all values in left to -1 and in right to n, where n is the size of nums. Also, create an empty list stk to be used as our Monotonic Stack.
Iterate through nums from left to right (using index i): Pop elements from the stack stk while the top of the stack is greater than or equal to the current element nums[i]. If stk is not empty after popping elements, it means stk[-1] is the previous smaller element's index, which we assign to left[i]. Push the current index i onto stk, indicating that we are considering nums[i] for the next elements' previous smaller check.
Reset the stack stk and perform a similar process from right to left (using a decreasing index i): Pop elements while the top of the stack is greater than or equal to nums[i]. If stk is not empty after the popping process, the top element of stk is the next smaller element's index, which we assign to right[i]. Push the current index i onto stk.
After constructing left and right arrays, initialize the answer array ans with zeros, with the same size as nums.
For every index i in nums, calculate the width m as the number of subarrays where nums[i] is the minimum by right[i] - left[i] - 1, and update ans[m - 1] with the maximum of its current value and nums[i].
Iterate through ans in reverse (starting from second to last element), making sure each ans[i] is at least as large as ans[i + 1]. This step ensures the answers are correct and handle the case where a smaller subarray may have the same maximum minimum as a larger subarray.
Solution for Maximum of Minimum Values in All Subarrays​
Java Solution​
public class Solution {
public int[] findMaximums(int[] nums) {
int n = nums.length;
// Initialize arrays to keep track of the left and right boundaries
int[] leftBoundaries = new int[n];
int[] rightBoundaries = new int[n];
// Fill the arrays with initial values indicating no boundary found yet
Arrays.fill(leftBoundaries, -1);
Arrays.fill(rightBoundaries, n);
// Stack to keep track of indices for which we haven't found a smaller number yet
Deque<Integer> stack = new ArrayDeque<>();
// Iterating from left to right to compute left boundaries
for (int i = 0; i < n; ++i) {
// Pop elements from the stack that are greater or equal to the current element
while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
stack.pop();
}
// If stack is not empty, the current top is the closest left smaller element
if (!stack.isEmpty()) {
leftBoundaries[i] = stack.peek();
}
// Push the current index onto the stack
stack.push(i);
}
// Clear the stack for the next iteration
stack.clear();
// Iterating from right to left to compute right boundaries
for (int i = n - 1; i >= 0; --i) {
// Pop elements from the stack that are greater or equal to the current element
while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
stack.pop();
}
// If stack is not empty, the current top is the closest right smaller element
if (!stack.isEmpty()) {
rightBoundaries[i] = stack.peek();
}
// Push the current index onto the stack
stack.push(i);
}
// Initialize the result array
int[] result = new int[n];
// Find the maximum value for every possible window size
for (int i = 0; i < n; ++i) {
int windowSize = rightBoundaries[i] - leftBoundaries[i] - 1;
result[windowSize - 1] = Math.max(result[windowSize - 1], nums[i]);
}
// Iterate to ensure each element represents the maximum for window size >= i+1
for (int i = n - 2; i >= 0; --i) {
result[i] = Math.max(result[i], result[i + 1]);
}
// Return the filled in result array
return result;
}
}
C++ solution​
#include <vector>
#include <stack>
using std::vector;
using std::stack;
using std::max;
class Solution {
public:
// Function to find the maximums of subarrays of all possible sizes
vector<int> findMaximums(vector<int>& nums) {
int n = nums.size();
// Vectors to store indices of previous and next smaller elements
vector<int> left(n, -1);
vector<int> right(n, n);
// Stack to keep track of indices as we search for smaller elements
stack<int> stk;
// Calculate the previous smaller element for each element in nums
for (int i = 0; i < n; ++i) {
// Pop elements from the stack until the current element is greater
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
// If the stack isn't empty, set the left bound
if (!stk.empty()) {
left[i] = stk.top();
}
// Push the current index onto the stack
stk.push(i);
}
// Clear the stack to reuse it for the next smaller elements on the right
stk = stack<int>();
// Calculate the next smaller element for each element in nums
for (int i = n - 1; i >= 0; --i) {
// Pop elements from the stack until the current element is greater
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
// If the stack isn't empty, set the right bound
if (!stk.empty()) {
right[i] = stk.top();
}
// Push the current index onto the stack
stk.push(i);
}
// Vector to store the maximums of subarrays of all possible sizes
vector<int> maxSubarraySizes(n);
// Find the maximum element seen for each subarray size
for (int i = 0; i < n; ++i) {
// Calculate the size of the largest window that the current element can be the minimum
int windowSize = right[i] - left[i] - 1;
// Update the maximum value for this window size if the current element is larger
maxSubarraySizes[windowSize - 1] = max(maxSubarraySizes[windowSize - 1], nums[i]);
}
// Ensure the maximum value for each size is at least as large as for the size above
for (int i = n - 2; i >= 0; --i) {
maxSubarraySizes[i] = max(maxSubarraySizes[i], maxSubarraySizes[i + 1]);
}
// Return the vector of maximums
return maxSubarraySizes;
}
};
Python Solution​
from typing import List
class Solution:
def findMaximums(self, nums: List[int]) -> List[int]:
n = len(nums)
# Initialize the left boundary for each element's minimum window
left_boundaries = [-1] * n
# Initialize the right boundary for each element's minimum window
right_boundaries = [n] * n
stack = []
# Determine the previous less element for each number in nums
for index, value in enumerate(nums):
# Pop elements from the stack if the current value is less or equal
while stack and nums[stack[-1]] >= value:
stack.pop()
# Set the left boundary if there is a previous less element
if stack:
left_boundaries[index] = stack[-1]
stack.append(index)
# Reset the stack for determining next less element
stack = []
# Determine the next less element for each number in nums
for index in range(n - 1, -1, -1):
# Pop elements from the stack if the current number is less or equal
while stack and nums[stack[-1]] >= nums[index]:
stack.pop()
# Set the right boundary if there is a next less element
if stack:
right_boundaries[index] = stack[-1]
stack.append(index)
# Initialize the result array to store the maximums for each window size
results = [0] * n
# Update results array with the maximum values for each window size
for index in range(n):
window_size = right_boundaries[index] - left_boundaries[index] - 1
results[window_size - 1] = max(results[window_size - 1], nums[index])
# Propagate the maximums for larger window sizes
for index in range(n - 2, -1, -1):
results[index] = max(results[index], results[index + 1])
return results
Complexity Analysis​
Time Complexity: O(n)
Space Complexity: O(n)
References​
LeetCode Problem: Maximum of Minimum Values in All Subarrays