LeetCode 1856 - Maximum Subarray Min-Product
The problem asks us to find the maximum min-product of any contiguous subarray of a given integer array nums. The min-product of a subarray is defined as the minimum value in that subarray multiplied by the sum of all elements in that subarray.
Difficulty: π‘ Medium
Topics: Array, Stack, Monotonic Stack, Prefix Sum
Solution
Problem Understanding
The problem asks us to find the maximum min-product of any contiguous subarray of a given integer array nums. The min-product of a subarray is defined as the minimum value in that subarray multiplied by the sum of all elements in that subarray. We are asked to return this maximum value modulo 10^9 + 7.
In simpler terms, given nums, we need to consider every possible contiguous subarray, calculate its sum, determine its minimum value, multiply these two numbers, and keep track of the largest result. The modulo operation only applies to the final maximum value.
The input nums can have up to 10^5 elements, each as large as 10^7. This means any brute-force approach that examines all subarrays will be far too slow. Since the constraints guarantee the result fits in a 64-bit integer, we donβt need to worry about intermediate overflow in Python or Go if using 64-bit integers.
Important edge cases include:
- Arrays of length 1, where the subarray is the array itself.
- Arrays with all elements equal, where every subarray has the same minimum.
- Arrays with strictly increasing or decreasing sequences, which affect how far a minimum can extend in a contiguous subarray.
Approaches
Brute Force
The brute-force approach is straightforward: iterate over all possible subarrays, compute the sum and minimum for each subarray, calculate the min-product, and update the maximum. This is correct but inefficient. Calculating all subarrays leads to O(n^2) subarrays, and computing the minimum and sum for each takes O(n) in a naive way. This results in O(n^3) time complexity in the worst case, which is not feasible for n = 10^5.
Optimal Approach
The key insight is to recognize that the problem is similar to the Largest Rectangle in Histogram problem. Instead of maximizing area, we maximize min * sum. We can use a monotonic increasing stack to determine, for each element, the range of subarrays where this element is the minimum. Once the left and right boundaries for each element are found, we can compute the subarray sum using prefix sums efficiently.
This reduces the problem to:
- Compute prefix sums of
nums. - Use a monotonic stack to find the first smaller element to the left and first smaller element to the right for each index.
- For each index
i, calculate the sum of the subarray[left[i]+1, right[i]-1]using prefix sums, multiply bynums[i], and update the maximum.
This approach works in O(n) time and O(n) space.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^3) | O(1) | Checks every subarray explicitly |
| Optimal | O(n) | O(n) | Monotonic stack + prefix sum |
Algorithm Walkthrough
- Compute prefix sums of
numsto allow O(1) subarray sum queries. This is done asprefix[i] = prefix[i-1] + nums[i]. - Initialize a monotonic increasing stack. The stack stores indices of elements in increasing order of their value.
- Iterate through
nums:
- While the current element is smaller than the element at the top of the stack, pop the stack.
- The popped index corresponds to a minimum element of a subarray.
- The left boundary is either the new top of the stack or
-1if the stack is empty. - The right boundary is the current index.
- Compute the sum of the subarray between these boundaries using prefix sums.
- Update the maximum min-product if
nums[popped] * subarray_sumis larger.
- Handle remaining elements in the stack after iteration. For these, the right boundary is the end of the array.
- Return maximum min-product modulo 10^9 + 7.
Why it works: Each element is considered as the minimum in a subarray extending as far left and right as possible without encountering a smaller element. This guarantees we explore the largest subarray for which each number is the minimum exactly once, producing the correct maximum min-product.
Python Solution
from typing import List
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
MOD = 10**9 + 7
n = len(nums)
# Compute prefix sums
prefix = [0] * (n + 1)
for i in range(n):
prefix[i+1] = prefix[i] + nums[i]
stack = []
max_product = 0
for i in range(n):
while stack and nums[stack[-1]] > nums[i]:
idx = stack.pop()
left = stack[-1] if stack else -1
sub_sum = prefix[i] - prefix[left+1]
max_product = max(max_product, nums[idx] * sub_sum)
stack.append(i)
while stack:
idx = stack.pop()
left = stack[-1] if stack else -1
sub_sum = prefix[n] - prefix[left+1]
max_product = max(max_product, nums[idx] * sub_sum)
return max_product % MOD
Explanation: The prefix sum array allows O(1) subarray sum queries. The stack ensures we efficiently find left and right boundaries for each minimum. We compute the min-product whenever an element is popped, guaranteeing each element is treated as the minimum of the maximal subarray possible.
Go Solution
func maxSumMinProduct(nums []int) int {
const MOD int = 1e9 + 7
n := len(nums)
prefix := make([]int64, n+1)
for i := 0; i < n; i++ {
prefix[i+1] = prefix[i] + int64(nums[i])
}
stack := []int{}
var maxProduct int64 = 0
for i := 0; i < n; i++ {
for len(stack) > 0 && nums[stack[len(stack)-1]] > nums[i] {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
left := -1
if len(stack) > 0 {
left = stack[len(stack)-1]
}
subSum := prefix[i] - prefix[left+1]
product := int64(nums[idx]) * subSum
if product > maxProduct {
maxProduct = product
}
}
stack = append(stack, i)
}
for len(stack) > 0 {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
left := -1
if len(stack) > 0 {
left = stack[len(stack)-1]
}
subSum := prefix[n] - prefix[left+1]
product := int64(nums[idx]) * subSum
if product > maxProduct {
maxProduct = product
}
}
return int(maxProduct % int64(MOD))
}
Explanation: The Go version mirrors the Python logic but uses int64 for intermediate calculations to prevent overflow. Slices and their lengths replace stack operations.
Worked Examples
Example: nums = [1,2,3,2]
Prefix sums: [0,1,3,6,8]
Stack iteration:
- i=0: stack=[0]
- i=1: stack=[0,1]
- i=2: stack=[0,1,2]
- i=3: nums[2]=3 > 2 β pop 2: left=1, sum=prefix[3]-prefix[2]=6-3=3 β product=3_3=9
nums[1]=2 β€ 2 β pop 1: left=0, sum=prefix[3]-prefix[1]=6-1=5 β product=2_5=10
stack=[0,3]
- End: pop 3: left=0, sum=prefix[4]-prefix[1]=8-1=7 β product=2*7=14 (maximum)
- pop 0: left=-1, sum=prefix[4]-prefix[0]=8-0=8 β product=1*8=8
Maximum min-product = 14
Similar step-through applies to other examples.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is pushed and popped from the stack at most once. Prefix sum computation is O(n). |
| Space | O(n) | Stack can hold up to n elements. Prefix sum array of size n+1. |
The linear time arises because the monotonic stack ensures each element is considered only when popped. Prefix sums allow O(1) sum queries for subarrays.
Test Cases
# Basic examples
assert Solution().maxSumMinProduct([1,2,3,2]) == 14 # Example 1
assert Solution().maxSumMinProduct([2,3,3,1,2]) == 18 # Example 2
assert Solution().maxSumMinProduct([3,1,5,6,4,2]) == 60