1856. Maximum Subarray Min-Product

Problem Description

Given an array of integers nums, you need to find the maximum min-product among all possible non-empty subarrays.

The min-product of an array is defined as the minimum value in the array multiplied by the sum of all elements in the array.

For example, if we have the array [3,2,5]:

  • The minimum value is 2
  • The sum of all elements is 3 + 2 + 5 = 10
  • The min-product is 2 * 10 = 20

Your task is to:

  1. Consider all possible contiguous subarrays of the given array
  2. Calculate the min-product for each subarray
  3. Return the maximum min-product found

Since the answer can be very large, return the result modulo 10^9 + 7. Note that you should find the maximum min-product first, then apply the modulo operation.

A subarray is a contiguous sequence of elements from the array. For instance, if nums = [1,2,3,4], then [2,3] is a subarray, but [1,3] is not (elements are not contiguous).

The key insight is that for each element in the array, we can consider it as the minimum element of some subarray. We need to find the optimal subarray boundaries where this element remains the minimum, which would maximize the sum and thus the min-product for that particular minimum value.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key observation is that we want to maximize the product of (minimum value) × (sum of subarray). Since we're dealing with a product, we need to think about how to balance these two factors.

For each element nums[i] in the array, let's consider what happens if we fix it as the minimum value of a subarray. Once we fix the minimum, we want to maximize the sum of the subarray while ensuring nums[i] remains the minimum. This means we should extend the subarray as far as possible to the left and right, including all elements that are greater than or equal to nums[i].

Think of it this way: if nums[i] is going to be our minimum value, we should be greedy and include as many elements as possible in our subarray (to maximize the sum), but we must stop when we encounter an element smaller than nums[i] (otherwise nums[i] wouldn't be the minimum anymore).

For example, if we have [3, 1, 5, 6, 4, 2] and we're considering 4 as the minimum:

  • We can extend left to include 6 and 5 (both ≥ 4)
  • We stop at 1 on the left (1 < 4)
  • We can't extend right because 2 < 4
  • So the subarray would be [5, 6, 4] with min-product = 4 × (5+6+4) = 60

This leads us to the problem: for each element, we need to find:

  1. Left boundary: the rightmost position to the left where an element is strictly less than nums[i]
  2. Right boundary: the leftmost position to the right where an element is less than or equal to nums[i]

The reason for the asymmetry (strictly less on the left, less than or equal on the right) is to avoid counting the same subarray twice when we have duplicate values.

Finding these boundaries efficiently for all elements suggests using a monotonic stack - a classic technique for finding the next/previous smaller element. We can use the stack to maintain elements in increasing order, which helps us quickly identify boundaries.

To calculate the sum efficiently, we use a prefix sum array so that the sum of any subarray can be computed in O(1) time as s[right] - s[left].

Learn more about Stack, Prefix Sum and Monotonic Stack patterns.

Solution Implementation

1class Solution: 2 def maxSumMinProduct(self, nums: List[int]) -> int: 3 # Get the length of the input array 4 n = len(nums) 5 6 # Arrays to store the left and right boundaries for each element 7 # left_boundary[i]: rightmost index j where j < i and nums[j] < nums[i] 8 # right_boundary[i]: leftmost index j where j > i and nums[j] < nums[i] 9 left_boundary = [-1] * n 10 right_boundary = [n] * n 11 12 # Find left boundaries using monotonic increasing stack 13 # For each element, find the nearest smaller element to its left 14 stack = [] 15 for i, value in enumerate(nums): 16 # Pop elements from stack that are >= current element 17 while stack and nums[stack[-1]] >= value: 18 stack.pop() 19 # If stack is not empty, top element is the left boundary 20 if stack: 21 left_boundary[i] = stack[-1] 22 # Add current index to stack 23 stack.append(i) 24 25 # Find right boundaries using monotonic increasing stack 26 # For each element, find the nearest smaller element to its right 27 stack = [] 28 for i in range(n - 1, -1, -1): 29 # Pop elements from stack that are > current element 30 while stack and nums[stack[-1]] > nums[i]: 31 stack.pop() 32 # If stack is not empty, top element is the right boundary 33 if stack: 34 right_boundary[i] = stack[-1] 35 # Add current index to stack 36 stack.append(i) 37 38 # Build prefix sum array for efficient range sum calculation 39 # prefix_sum[i] = sum of nums[0] to nums[i-1] 40 prefix_sum = list(accumulate(nums, initial=0)) 41 42 # Define modulo constant for the result 43 MOD = 10**9 + 7 44 45 # Calculate the maximum sum-min product 46 # For each element as minimum, calculate sum of subarray * minimum value 47 # Subarray range: (left_boundary[i] + 1) to (right_boundary[i] - 1) 48 max_product = max( 49 (prefix_sum[right_boundary[i]] - prefix_sum[left_boundary[i] + 1]) * value 50 for i, value in enumerate(nums) 51 ) 52 53 # Return the result modulo MOD 54 return max_product % MOD 55
1class Solution { 2 public int maxSumMinProduct(int[] nums) { 3 int n = nums.length; 4 5 // Arrays to store the nearest smaller element indices 6 // leftBoundary[i] = index of nearest smaller element to the left of i (-1 if none) 7 int[] leftBoundary = new int[n]; 8 // rightBoundary[i] = index of nearest smaller element to the right of i (n if none) 9 int[] rightBoundary = new int[n]; 10 11 // Initialize boundaries 12 Arrays.fill(leftBoundary, -1); 13 Arrays.fill(rightBoundary, n); 14 15 // Monotonic stack to find nearest smaller elements 16 Deque<Integer> stack = new ArrayDeque<>(); 17 18 // Find nearest smaller element to the left for each position 19 for (int i = 0; i < n; i++) { 20 // Pop elements from stack that are greater than or equal to current element 21 while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) { 22 stack.pop(); 23 } 24 // If stack has elements, top is the nearest smaller element to the left 25 if (!stack.isEmpty()) { 26 leftBoundary[i] = stack.peek(); 27 } 28 // Add current index to stack 29 stack.push(i); 30 } 31 32 // Clear stack for reuse 33 stack.clear(); 34 35 // Find nearest smaller element to the right for each position 36 for (int i = n - 1; i >= 0; i--) { 37 // Pop elements from stack that are greater than current element 38 // Note: Using > instead of >= to handle duplicates correctly 39 while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) { 40 stack.pop(); 41 } 42 // If stack has elements, top is the nearest smaller element to the right 43 if (!stack.isEmpty()) { 44 rightBoundary[i] = stack.peek(); 45 } 46 // Add current index to stack 47 stack.push(i); 48 } 49 50 // Build prefix sum array for quick range sum calculation 51 // prefixSum[i] = sum of elements from index 0 to i-1 52 long[] prefixSum = new long[n + 1]; 53 for (int i = 0; i < n; i++) { 54 prefixSum[i + 1] = prefixSum[i] + nums[i]; 55 } 56 57 // Calculate maximum sum-min product 58 long maxProduct = 0; 59 for (int i = 0; i < n; i++) { 60 // For each element as minimum, calculate the subarray sum where it's the minimum 61 // The subarray spans from (leftBoundary[i] + 1) to (rightBoundary[i] - 1) 62 long subarraySum = prefixSum[rightBoundary[i]] - prefixSum[leftBoundary[i] + 1]; 63 long product = (long)nums[i] * subarraySum; 64 maxProduct = Math.max(maxProduct, product); 65 } 66 67 // Return result modulo 10^9 + 7 68 final int MOD = (int)1e9 + 7; 69 return (int)(maxProduct % MOD); 70 } 71} 72
1class Solution { 2public: 3 int maxSumMinProduct(vector<int>& nums) { 4 int n = nums.size(); 5 6 // left[i] stores the index of the nearest element to the left of i  7 // that is strictly smaller than nums[i] 8 vector<int> left(n, -1); 9 10 // right[i] stores the index of the nearest element to the right of i  11 // that is smaller than or equal to nums[i] 12 vector<int> right(n, n); 13 14 // Monotonic stack to find previous smaller element 15 stack<int> stk; 16 17 // Find the previous smaller element for each index 18 for (int i = 0; i < n; ++i) { 19 // Pop elements that are greater than or equal to current element 20 while (!stk.empty() && nums[stk.top()] >= nums[i]) { 21 stk.pop(); 22 } 23 // If stack is not empty, top element is the previous smaller element 24 if (!stk.empty()) { 25 left[i] = stk.top(); 26 } 27 stk.push(i); 28 } 29 30 // Clear the stack for reuse 31 stk = stack<int>(); 32 33 // Find the next smaller or equal element for each index 34 for (int i = n - 1; i >= 0; --i) { 35 // Pop elements that are strictly greater than current element 36 while (!stk.empty() && nums[stk.top()] > nums[i]) { 37 stk.pop(); 38 } 39 // If stack is not empty, top element is the next smaller or equal element 40 if (!stk.empty()) { 41 right[i] = stk.top(); 42 } 43 stk.push(i); 44 } 45 46 // Build prefix sum array for range sum queries 47 vector<long long> prefixSum(n + 1, 0); 48 for (int i = 0; i < n; ++i) { 49 prefixSum[i + 1] = prefixSum[i] + nums[i]; 50 } 51 52 // Calculate the maximum min-product 53 long long maxProduct = 0; 54 for (int i = 0; i < n; ++i) { 55 // For each element as minimum, calculate the sum of subarray  56 // where nums[i] is the minimum element 57 // The subarray ranges from (left[i] + 1) to (right[i] - 1) 58 long long subarraySum = prefixSum[right[i]] - prefixSum[left[i] + 1]; 59 long long minProduct = static_cast<long long>(nums[i]) * subarraySum; 60 maxProduct = max(maxProduct, minProduct); 61 } 62 63 // Apply modulo as per problem requirement 64 const int MOD = 1e9 + 7; 65 return maxProduct % MOD; 66 } 67}; 68
1/** 2 * Finds the maximum score of any subarray where score = sum(subarray) * min(subarray) 3 * Uses monotonic stack to find boundaries for each element as minimum 4 * @param nums - Input array of positive integers 5 * @returns Maximum score modulo 10^9 + 7 6 */ 7function maxSumMinProduct(nums: number[]): number { 8 const n = nums.length; 9 10 // leftBoundary[i] stores the index of the nearest element to the left that is smaller than nums[i] 11 // -1 means no such element exists (nums[i] is the minimum from start to i) 12 const leftBoundary: number[] = Array(n).fill(-1); 13 14 // rightBoundary[i] stores the index of the nearest element to the right that is smaller or equal to nums[i] 15 // n means no such element exists (nums[i] is the minimum from i to end) 16 const rightBoundary: number[] = Array(n).fill(n); 17 18 // Monotonic increasing stack to find left boundaries 19 const stack: number[] = []; 20 21 // Find left boundary for each element 22 // Stack maintains indices in increasing order of their values 23 for (let i = 0; i < n; ++i) { 24 // Pop elements from stack that are greater than or equal to current element 25 while (stack.length && nums[stack.at(-1)!] >= nums[i]) { 26 stack.pop(); 27 } 28 // If stack is not empty, top element is the left boundary 29 if (stack.length) { 30 leftBoundary[i] = stack.at(-1)!; 31 } 32 stack.push(i); 33 } 34 35 // Clear stack for reuse 36 stack.length = 0; 37 38 // Find right boundary for each element (traverse from right to left) 39 for (let i = n - 1; i >= 0; --i) { 40 // Pop elements from stack that are greater than current element 41 while (stack.length && nums[stack.at(-1)!] > nums[i]) { 42 stack.pop(); 43 } 44 // If stack is not empty, top element is the right boundary 45 if (stack.length) { 46 rightBoundary[i] = stack.at(-1)!; 47 } 48 stack.push(i); 49 } 50 51 // Build prefix sum array for quick range sum calculation 52 // prefixSum[i] = sum of nums[0] to nums[i-1] 53 const prefixSum: number[] = Array(n + 1).fill(0); 54 for (let i = 0; i < n; ++i) { 55 prefixSum[i + 1] = prefixSum[i] + nums[i]; 56 } 57 58 // Calculate maximum product 59 let maxProduct: bigint = 0n; 60 const MOD = 10 ** 9 + 7; 61 62 // For each element as the minimum value in a subarray 63 for (let i = 0; i < n; ++i) { 64 // Calculate sum of subarray where nums[i] is the minimum 65 // Subarray spans from (leftBoundary[i] + 1) to (rightBoundary[i] - 1) 66 const subarraySum = prefixSum[rightBoundary[i]] - prefixSum[leftBoundary[i] + 1]; 67 68 // Calculate product: minimum value * sum of subarray 69 const currentProduct = BigInt(nums[i]) * BigInt(subarraySum); 70 71 // Update maximum product 72 if (maxProduct < currentProduct) { 73 maxProduct = currentProduct; 74 } 75 } 76 77 // Return result modulo MOD 78 return Number(maxProduct % BigInt(MOD)); 79} 80

Solution Approach

The solution uses monotonic stacks to find boundaries and prefix sums for efficient range sum calculation.

Step 1: Find Left Boundaries

We traverse the array from left to right with a monotonic stack to find left[i] - the rightmost position to the left of i where nums[left[i]] < nums[i].

left = [-1] * n # Initialize with -1 (no element to the left) stk = [] for i, x in enumerate(nums):  while stk and nums[stk[-1]] >= x:  stk.pop() # Remove elements >= current element  if stk:  left[i] = stk[-1] # Top of [stack](/problems/stack_intro) is the rightmost smaller element  stk.append(i)

The stack maintains indices in increasing order of their values. When we encounter nums[i], we pop all elements from the stack that are greater than or equal to nums[i]. The remaining top element (if any) is the rightmost element smaller than nums[i].

Step 2: Find Right Boundaries

Similarly, we traverse from right to left to find right[i] - the leftmost position to the right of i where nums[right[i]] <= nums[i].

right = [n] * n # Initialize with n (no element to the right) stk = [] for i in range(n - 1, -1, -1):  while stk and nums[stk[-1]] > nums[i]:  stk.pop() # Remove elements > current element  if stk:  right[i] = stk[-1] # Top of [stack](/problems/stack_intro) is the leftmost smaller or equal element  stk.append(i)

Note the subtle difference: we use > instead of >= to handle duplicates correctly and avoid counting the same subarray multiple times.

Step 3: Calculate Prefix Sums

We build a prefix sum array where s[i] represents the sum of the first i elements:

s = list(accumulate(nums, initial=0))

This allows us to calculate the sum of subarray [left[i]+1, right[i]-1] as s[right[i]] - s[left[i] + 1] in O(1) time.

Step 4: Find Maximum Min-Product

For each element nums[i] as the minimum value, the subarray extends from left[i] + 1 to right[i] - 1. The min-product is:

min_product = nums[i] * (s[right[i]] - s[left[i] + 1])

We calculate this for all positions and return the maximum value modulo 10^9 + 7:

return max((s[right[i]] - s[left[i] + 1]) * x for i, x in enumerate(nums)) % mod

Time Complexity: O(n) - each element is pushed and popped from the stack at most once.
Space Complexity: O(n) - for the left, right, s arrays and the stack.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with the array nums = [2, 3, 3, 1, 2].

Step 1: Find Left Boundaries

We traverse left to right with a monotonic stack to find the rightmost smaller element to the left of each position.

  • i=0, x=2: stack=[], no smaller element to left, left[0]=-1, stack=[0]
  • i=1, x=3: stack=[0], nums[0]=2 < 3, left[1]=0, stack=[0,1]
  • i=2, x=3: stack=[0,1], nums[1]=3 >= 3 so pop 1, nums[0]=2 < 3, left[2]=0, stack=[0,2]
  • i=3, x=1: stack=[0,2], nums[2]=3 >= 1 so pop 2, nums[0]=2 >= 1 so pop 0, stack=[], left[3]=-1, stack=[3]
  • i=4, x=2: stack=[3], nums[3]=1 < 2, left[4]=3, stack=[3,4]

Result: left = [-1, 0, 0, -1, 3]

Step 2: Find Right Boundaries

We traverse right to left to find the leftmost smaller or equal element to the right.

  • i=4, x=2: stack=[], no element to right, right[4]=5, stack=[4]
  • i=3, x=1: stack=[4], nums[4]=2 > 1, pop 4, stack=[], right[3]=5, stack=[3]
  • i=2, x=3: stack=[3], nums[3]=1 <= 3, right[2]=3, stack=[3,2]
  • i=1, x=3: stack=[3,2], nums[2]=3 > 3 is false, right[1]=2, stack=[3,2,1]
  • i=0, x=2: stack=[3,2,1], nums[1]=3 > 2, pop 1, nums[2]=3 > 2, pop 2, nums[3]=1 <= 2, right[0]=3, stack=[3,0]

Result: right = [3, 2, 3, 5, 5]

Step 3: Calculate Prefix Sums

s = [0, 2, 5, 8, 9, 11] where s[i] = sum of first i elements

Step 4: Calculate Min-Products

For each position i, the subarray extends from left[i]+1 to right[i]-1:

  • i=0: subarray [0,2], sum = s[3] - s[0] = 8 - 0 = 8, min-product = 2 × 8 = 16
  • i=1: subarray [1,1], sum = s[2] - s[1] = 5 - 2 = 3, min-product = 3 × 3 = 9
  • i=2: subarray [1,2], sum = s[3] - s[1] = 8 - 2 = 6, min-product = 3 × 6 = 18
  • i=3: subarray [0,4], sum = s[5] - s[0] = 11 - 0 = 11, min-product = 1 × 11 = 11
  • i=4: subarray [4,4], sum = s[5] - s[4] = 11 - 9 = 2, min-product = 2 × 2 = 4

Maximum min-product = 18 (from position i=2)

The key insight is that for element 3 at position 2, we can extend the subarray to include position 1 (also value 3) but must stop at position 3 (value 1 < 3). This gives us the subarray [3,3] with minimum 3 and sum 6, yielding the maximum min-product of 18.

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of several linear passes through the array:

  • First monotonic stack traversal to find left boundaries: Each element is pushed and popped from the stack at most once, resulting in O(n) operations
  • Second monotonic stack traversal to find right boundaries: Similarly, each element is pushed and popped at most once, giving O(n) operations
  • Building the prefix sum array using accumulate: O(n) time
  • Final iteration to calculate the maximum sum-min product: O(n) time to iterate through all elements

Since all operations are linear and performed sequentially, the overall time complexity is O(n) + O(n) + O(n) + O(n) = O(n).

Space Complexity: O(n)

The algorithm uses the following auxiliary data structures:

  • left array of size n: O(n) space
  • right array of size n: O(n) space
  • stk for monotonic stack operations: At most n elements, so O(n) space
  • s prefix sum array of size n+1: O(n) space

The total space complexity is O(n) + O(n) + O(n) + O(n) = O(n), where n is the length of the input array nums.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Handling of Duplicate Elements

One of the most common mistakes is using the same comparison operator for both left and right boundary calculations. This can lead to counting the same subarray multiple times when duplicate elements exist.

Incorrect approach:

# Both using >= operator while stack and nums[stack[-1]] >= value: # Left boundary while stack and nums[stack[-1]] >= nums[i]: # Right boundary

Why it's wrong: If we have duplicates like [2, 2, 2], using >= for both boundaries means each 2 could claim the entire array as its domain, causing the same subarray to be counted three times.

Correct approach:

# Left boundary: use >= while stack and nums[stack[-1]] >= value:  stack.pop()  # Right boundary: use > (strictly greater) while stack and nums[stack[-1]] > nums[i]:  stack.pop()

This ensures that when duplicates exist, only the leftmost occurrence can extend to include all duplicates, preventing double counting.

2. Applying Modulo Too Early

Incorrect approach:

# Applying modulo during calculation max_product = 0 for i, value in enumerate(nums):  product = ((prefix_sum[right_boundary[i]] - prefix_sum[left_boundary[i] + 1]) * value) % MOD  max_product = max(max_product, product) return max_product

Why it's wrong: The problem asks to find the maximum first, then apply modulo. Applying modulo during calculation can change the comparison results since (a % MOD) > (b % MOD) doesn't necessarily mean a > b.

Correct approach:

# Find maximum first, then apply modulo max_product = max(  (prefix_sum[right_boundary[i]] - prefix_sum[left_boundary[i] + 1]) * value  for i, value in enumerate(nums) ) return max_product % MOD

3. Off-by-One Errors in Boundary Indices

Common mistake: Confusion about whether boundaries are inclusive or exclusive.

Key points to remember:

  • left_boundary[i] is the index of an element strictly smaller than nums[i]
  • right_boundary[i] is the index of an element smaller than or equal to nums[i]
  • The valid subarray where nums[i] is minimum spans from left_boundary[i] + 1 to right_boundary[i] - 1 (inclusive)
  • For prefix sum calculation: sum[l, r] = prefix_sum[r + 1] - prefix_sum[l]

4. Integer Overflow in Other Languages

While Python handles large integers automatically, in languages like Java or C++, the multiplication can overflow:

Solution for other languages:

// Java example - use long type long product = ((long)rangeSum * nums[i]) % MOD;

5. Incorrect Initialization of Boundaries

Incorrect:

left_boundary = [0] * n # Wrong! This suggests element 0 is always a left boundary right_boundary = [n-1] * n # Wrong! This suggests element n-1 is always a right boundary

Correct:

left_boundary = [-1] * n # -1 indicates no smaller element to the left right_boundary = [n] * n # n indicates no smaller element to the right

These sentinel values correctly handle edge cases where an element is the smallest in its extending range.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More