Before attempting this problem, you should be comfortable with:
The min-product of a subarray is defined as the minimum element multiplied by the sum of all elements. For each possible subarray, we need to track both the running minimum and the running sum. By trying all subarrays starting from each index, we can compute every possible min-product and find the maximum.
i from 0 to n-1:total_sum = 0 and mini = infinity.i to n-1.mini with each new element.total_sum.mini * total_sum and update the result.10^9 + 7.class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
res, MOD = 0, 1000000007
for i in range(len(nums)):
total_sum = 0
mini = float("inf")
for j in range(i, len(nums)):
mini = min(mini, nums[j])
total_sum += nums[j]
cur = (mini * total_sum) % MOD
res = max(res, cur)
return resFor any subarray, the minimum element defines the "bottleneck." If we know the minimum element's position, the optimal subarray with that element as minimum spans as far as possible in both directions. This leads to a divide and conquer approach: find the minimum in the current range, calculate the score for the entire range using that minimum, then recursively solve for the left and right portions (excluding the minimum).
[l, r]:l > r, return 0.[l, r] and compute the total sum.sum * min_element.[l, min_idx - 1] and [min_idx + 1, r].[0, n-1] and return the result modulo 10^9 + 7.class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
MOD = 10**9 + 7
def rec(l, r):
if l > r:
return 0
min_idx = l
total_sum = 0
for i in range(l, r + 1):
total_sum += nums[i]
if nums[i] < nums[min_idx]:
min_idx = i
cur = total_sum * nums[min_idx]
left = rec(l, min_idx - 1)
right = rec(min_idx + 1, r)
return max(cur, left, right)
return rec(0, len(nums) - 1) % MODThe brute force divide and conquer spends O(n) time finding the minimum in each range, leading to O(n^2) worst case. We can optimize this using a segment tree that answers range minimum queries in O(log n). Combined with a prefix sum array for O(1) range sum queries, this reduces the overall complexity significantly.
[l, r]:l > r, return 0.[l, r].10^9 + 7.class SegmentTree:
def __init__(self, N, A):
self.n = N
while (self.n & (self.n - 1)) != 0:
self.n += 1
self.build(N, A)
def build(self, N, A):
self.tree = [(-1, float('inf'))] * (2 * self.n)
for i in range(N):
self.tree[self.n + i] = (i, A[i])
for i in range(self.n - 1, 0, -1):
self.tree[i] = min(self.tree[i << 1], self.tree[i << 1 | 1], key=lambda x: x[1])
def query(self, l, r):
res = (-1, float('inf'))
l += self.n
r += self.n + 1
while l < r:
if l & 1:
res = min(res, self.tree[l], key=lambda x: x[1])
l += 1
if r & 1:
r -= 1
res = min(res, self.tree[r], key=lambda x: x[1])
l >>= 1
r >>= 1
return res[0]
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
MOD = 10**9 + 7
segTree = SegmentTree(len(nums), nums)
prefix_sum = [0] * (len(nums) + 1)
for i in range(0, len(nums)):
prefix_sum[i + 1] += prefix_sum[i] + nums[i]
def rec(l, r):
if l > r:
return 0
min_idx = segTree.query(l, r)
total_sum = prefix_sum[r + 1] - prefix_sum[l]
cur = total_sum * nums[min_idx]
left = rec(l, min_idx - 1)
right = rec(min_idx + 1, r)
return max(cur, left, right)
return rec(0, len(nums) - 1) % MODFor each element, we want to find the maximum subarray where that element is the minimum. This means finding the nearest smaller elements on both sides. A monotonic stack efficiently computes these boundaries in linear time. With prefix sums for fast range sums, we can evaluate each element's contribution and find the global maximum.
prev_min[i]: index of the nearest smaller element to the left (or -1).nxt_min[i]: index of the nearest smaller element to the right (or n).i:[prev_min[i] + 1, nxt_min[i] - 1].nums[i] * range_sum and update the result.10^9 + 7.class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
n = len(nums)
prefix_sum = [0] * (n + 1)
for i in range(n):
prefix_sum[i + 1] = prefix_sum[i] + nums[i]
prev_min, nxt_min = [-1] * n, [n] * n
stack = []
for i in range(n):
while stack and nums[stack[-1]] >= nums[i]:
stack.pop()
if stack:
prev_min[i] = stack[-1]
stack.append(i)
stack.clear()
for i in range(n - 1, -1, -1):
while stack and nums[stack[-1]] >= nums[i]:
stack.pop()
if stack:
nxt_min[i] = stack[-1]
stack.append(i)
res = 0
for i in range(n):
l, r = prev_min[i] + 1, nxt_min[i] - 1
total_sum = prefix_sum[r + 1] - prefix_sum[l]
res = max(res, nums[i] * total_sum)
return res % (10 ** 9 + 7)We can avoid storing separate boundary arrays by processing elements as they are popped from the stack. When an element is popped (because a smaller element is found), we immediately know its right boundary. The left boundary is the element currently at the top of the stack. We also track the starting index of each element's potential range as we push to the stack.
10^9 + 7.class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i + 1] = prefix[i] + nums[i]
res = 0
stack = []
for i, num in enumerate(nums):
new_start = i
while stack and stack[-1][1] > num:
start, val = stack.pop()
total = prefix[i] - prefix[start]
res = max(res, val * total)
new_start = start
stack.append((new_start, num))
while stack:
start, val = stack.pop()
total = prefix[n] - prefix[start]
res = max(res, val * total)
return res % (10**9 + 7)This variation uses an index-only stack and processes all elements in a single pass by treating the position after the array as a sentinel. When we pop an element, the current position is its right boundary, and the new stack top (or -1) is its left boundary. This eliminates the need to store value pairs in the stack.
0 to n (inclusive, where n acts as sentinel):j from the stack.0 if empty).nums[j] * range_sum and update the result.10^9 + 7.class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
n = len(nums)
prefix_sum = [0] * (n + 1)
for i in range(n):
prefix_sum[i + 1] = prefix_sum[i] + nums[i]
res, stack = 0, []
for i in range(n + 1):
while stack and (i == n or nums[i] < nums[stack[-1]]):
j = stack.pop()
start = 0 if not stack else stack[-1] + 1
res = max(res, nums[j] * (prefix_sum[i] - prefix_sum[start]))
stack.append(i)
return res % (10 ** 9 + 7)The min-product can be extremely large (minimum value up to 10^7 times sum up to 10^14), exceeding 32-bit integer limits. You must use 64-bit integers for intermediate calculations and only apply modulo at the very end when returning the result. Applying modulo during comparisons breaks the max logic since you're comparing reduced values.
The min-product is min(subarray) * sum(subarray), which combines both the minimum element and the subarray sum. This is different from problems involving just the minimum times length, or just the sum. Make sure to compute prefix sums for efficient range sum queries and track minimums correctly.
When using prefix sums, the sum of elements from index l to r (inclusive) is prefix[r+1] - prefix[l], not prefix[r] - prefix[l]. Getting this boundary wrong by one index leads to incorrect subarray sums and wrong answers.