Maximum and Minimum Sums of at Most Size K Subarrays
HardPrompt
You are given an integer array nums and a positive integer k. Return the sum of the maximum and minimum elements of all subarrays with at most k elements.
Example 1:
Input: nums = [1,2,3], k = 2
Output: 20
Explanation:
The subarrays of nums with at most 2 elements are:
| Subarray | Minimum | Maximum | Sum |
|---|---|---|---|
[1] |
1 | 1 | 2 |
[2] |
2 | 2 | 4 |
[3] |
3 | 3 | 6 |
[1, 2] |
1 | 2 | 3 |
[2, 3] |
2 | 3 | 5 |
| Final Total | 20 |
The output would be 20.
Example 2:
Input: nums = [1,-3,1], k = 2
Output: -6
Explanation:
The subarrays of nums with at most 2 elements are:
| Subarray | Minimum | Maximum | Sum |
|---|---|---|---|
[1] |
1 | 1 | 2 |
[-3] |
-3 | -3 | -6 |
[1] |
1 | 1 | 2 |
[1, -3] |
-3 | 1 | -2 |
[-3, 1] |
-3 | 1 | -2 |
| Final Total | -6 |
The output would be -6.
Constraints:
1 <= nums.length <= 800001 <= k <= nums.length-106 <= nums[i] <= 106
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through all possible subarrays that satisfy the length constraint (at most k). For each starting position i, we extend the subarray to the right, one element at a time, up to a length of k. While extending, we keep track of the minimum and maximum values seen so far in the current subarray and add them to a running total.
Algorithm
- Initialize a variable
totalSumto 0. - Use a nested loop to generate all subarrays. The outer loop iterates from
i = 0ton-1to select the starting index of the subarray. - The inner loop iterates from
j = iton-1to select the ending index. - Inside the inner loop, check if the length of the current subarray
(j - i + 1)is greater thank. If it is, break the inner loop as subsequent subarrays starting atiwill also be too long. - For each valid subarray
nums[i..j], we need to find its minimum and maximum elements. Instead of re-calculating this from scratch for each subarray, we can maintain the minimum and maximum of the subarraynums[i..j]as we extend it fromjtoj+1. - For a fixed
i, initializecurrentMin = nums[i]andcurrentMax = nums[i]. - As
jincreases, updatecurrentMin = min(currentMin, nums[j])andcurrentMax = max(currentMax, nums[j]). - Add the
currentMinandcurrentMaxtototalSum. - After iterating through all possible subarrays, return
totalSum.
Walkthrough
The brute-force method is the most straightforward way to solve the problem. We generate every single subarray, check if its length is at most k, and if so, find its minimum and maximum elements to add to our total sum.
A slight optimization on the naive brute-force method is to avoid re-calculating the minimum and maximum for each subarray. When we consider subarrays starting at index i, we can iterate with a second pointer j from i outwards. As we expand the subarray nums[i..j] by incrementing j, we can update the minimum and maximum in O(1) time.
class Solution { public long findMaximumsAndMinimums(int[] nums, int k) { long totalSum = 0; int n = nums.length; for (int i = 0; i < n; i++) { int currentMin = Integer.MAX_VALUE; int currentMax = Integer.MIN_VALUE; // Iterate through all subarrays starting at i with length at most k for (int j = i; j < n && (j - i + 1) <= k; j++) { // Update min and max for the subarray nums[i..j] currentMin = Math.min(currentMin, nums[j]); currentMax = Math.max(currentMax, nums[j]); // Add the min and max of the current subarray to the total sum totalSum += (long)currentMin + currentMax; } } return totalSum; }}Complexity
Time
O(n * k), where `n` is the number of elements in `nums`. The outer loop runs `n` times, and the inner loop runs at most `k` times. Inside the inner loop, operations are O(1).
Space
O(1) extra space, as we only use a few variables to store the running sum and current min/max.
Trade-offs
Pros
Simple to understand and implement.
Uses constant extra space.
Cons
The time complexity of O(n*k) is too slow for the given constraints, where n and k can be up to 80,000. This will result in a Time Limit Exceeded (TLE) error on most platforms.
A much more efficient approach is to change perspective. Instead of iterating through subarrays, we can iterate through each element nums[i] and calculate its total contribution to the final sum. The problem can be split into two parts: finding the sum of all maximums and the sum of all minimums. The logic is symmetrical, so we'll focus on the maximums.
The contribution of nums[i] to the sum of maximums is nums[i] multiplied by the number of subarrays (of length at most k) in which nums[i] is the maximum element. We can use a monotonic stack to efficiently find the nearest elements to the left and right of i that are larger than nums[i]. This defines a range where nums[i] is the maximum. Within this range, we can then use a mathematical formula to count how many subarrays satisfy the length constraint k in O(1) time. This leads to an overall linear time solution.
Algorithm
- Decompose the problem into two independent subproblems: calculating the sum of maximums of all valid subarrays, and the sum of minimums of all valid subarrays. The final answer is the sum of these two results.
- Let's focus on the sum of maximums. Instead of iterating over subarrays, we iterate through each element
nums[i]and calculate its total contribution. The contribution ofnums[i]isnums[i]multiplied by the number of valid subarrays for whichnums[i]is the maximum. - To count these subarrays, we first need to find the range
(L, R)in whichnums[i]is the maximum.Lis the index of the first element to the left ofithat is greater thannums[i], andRis the index of the first element to the right ofithat is greater than or equal tonums[i]. This specific tie-breaking (using>on one side and>=on the other) ensures that for any subarray, the maximum element is counted exactly once. TheseLandRboundaries can be found for alliin O(n) time using a monotonic stack. - For a fixed
i,nums[i]is the maximum for any subarraynums[l..r]whereL < l <= iandi <= r < R. We also have the constraint that the subarray lengthr - l + 1 <= k. - Let
x = i - Landy = R - i. These are the number of possible start positions (relative toi) and end positions, respectively. We need to count pairs of(l, r)satisfying the conditions. This can be formulated as a counting problem that can be solved with a derived mathematical formula in O(1) time for eachi. - The number of valid subarrays for
nums[i]as max iscount = sum_{p=0 to x-1} (min(y, k-p)). This sum can be calculated in O(1) by splitting it into two parts: one wherek-p >= yand one wherek-p < y. - Sum up the contributions
nums[i] * countfor allito get the total sum of maximums. - Repeat a symmetrical process for the sum of minimums (e.g., finding next smaller elements) and add the results.
Walkthrough
This approach hinges on calculating the contribution of each element nums[i] separately for the sum of maximums and the sum of minimums.
Let's define a helper function calculateContribution(nums, k, isMax).
1. Finding Boundaries with a Monotonic Stack:
To find the number of subarrays where nums[i] is the maximum, we need to find its effective range. We find left[i], the index of the first element to the left of i that is strictly greater than nums[i], and right[i], the index of the first element to the right that is greater than or equal to nums[i]. This can be done for all i in O(n) using a monotonic decreasing stack. A similar process with a monotonic increasing stack is used for minimums.
2. Calculating Contribution:
Once we have left[i] and right[i], we know that nums[i] is the maximum for any subarray nums[l..r] where left[i] < l <= i and i <= r < right[i]. Let x = i - left[i] be the number of possible start positions to the left (including i) and y = right[i] - i be the number of possible end positions to the right (including i).
We now need to count the pairs (l, r) that also satisfy r - l + 1 <= k. This can be done with an O(1) formula derived from summing up the possibilities. For each possible number of elements to the left of i, p (from 0 to x-1), we count the valid number of elements to the right, q (from 0 to y-1), such that p + q + 1 <= k. The total count for nums[i] is sum_{p=0 to x-1} min(y, k-p). This sum can be computed in O(1) by splitting it into an arithmetic series and a constant part.
3. Final Sum:
The final answer is calculateContribution(nums, k, true) + calculateContribution(nums, k, false).
import java.util.ArrayDeque;import java.util.Deque; class Solution { public long findMaximumsAndMinimums(int[] nums, int k) { long sumMax = calculateContribution(nums, k, true); long sumMin = calculateContribution(nums, k, false); return sumMax + sumMin; } private long calculateContribution(int[] nums, int k, boolean isMax) { int n = nums.length; int[] left = new int[n]; Deque<Integer> stack = new ArrayDeque<>(); // To ensure each subarray's max/min is counted once, we use a strict inequality // on one side and a non-strict one on the other for tie-breaking. // For max: left is next >, right is next >=. // For min: left is next <, right is next <=. // Left boundary (strict) for (int i = 0; i < n; i++) { while (!stack.isEmpty() && (isMax ? nums[stack.peek()] <= nums[i] : nums[stack.peek()] >= nums[i])) { stack.pop(); } left[i] = stack.isEmpty() ? -1 : stack.peek(); stack.push(i); } int[] right = new int[n]; stack.clear(); // Right boundary (non-strict) for (int i = n - 1; i >= 0; i--) { while (!stack.isEmpty() && (isMax ? nums[stack.peek()] < nums[i] : nums[stack.peek()] > nums[i])) { stack.pop(); } right[i] = stack.isEmpty() ? n : stack.peek(); stack.push(i); } long totalContribution = 0; for (int i = 0; i < n; i++) { long x = i - left[i]; long y = right[i] - i; // Count valid subarrays where nums[i] is the extremum. // This is equivalent to counting pairs (p, q) where 0<=p<x, 0<=q<y and p+q+1 <= k. // The formula is: sum_{p=0 to x-1} min(y, k-p) long count = 0; long p_split = k - y; if (p_split >= x) { // k >= x+y, all x*y subarrays are valid count = x * y; } else { // Part 1: p from 0 to p_split-1. Here, k-p > y, so min(y, k-p) = y. if (p_split > 0) { count += p_split * y; } // Part 2: p from max(0, p_split) to x-1. Here, k-p <= y, so min(y, k-p) = k-p. // This is an arithmetic series sum. long start_p = Math.max(0, p_split); long num_terms = x - start_p; if (num_terms > 0) { long first_term = k - start_p; long last_term = k - (x - 1); count += num_terms * (first_term + last_term) / 2; } } totalContribution += (long)nums[i] * count; } return totalContribution; }}Complexity
Time
O(n). Finding the left and right boundaries for all elements using a monotonic stack takes O(n). The final loop to calculate contributions also runs `n` times, with O(1) work inside. Thus, the total time complexity is linear.
Space
O(n) to store the `left` and `right` boundary arrays and for the stack used in their computation.
Trade-offs
Pros
Highly efficient with a linear time complexity, making it suitable for large inputs.
The core logic of contribution and monotonic stacks is a powerful pattern applicable to many other problems.
Cons
The logic is significantly more complex than the brute-force approach.
The implementation requires careful handling of edge cases and the logic for counting subarrays, which can be error-prone.
Requires O(n) extra space for stacks and boundary arrays.
Solutions
Solution
/** * @param {number[]} nums * @param {number} k * @return {number} */ var minMaxSubarraySum = function ( nums , k ) { const computeSum = ( nums , k , isMin ) => { const n = nums . length ; const prev = Array ( n ). fill ( - 1 ); const next = Array ( n ). fill ( n ); let stk = []; if ( isMin ) { for ( let i = 0 ; i < n ; i ++ ) { while ( stk . length > 0 && nums [ stk [ stk . length - 1 ]] >= nums [ i ]) { stk . pop (); } prev [ i ] = stk . length > 0 ? stk [ stk . length - 1 ] : - 1 ; stk . push ( i ); } stk = []; for ( let i = n - 1 ; i >= 0 ; i -- ) { while ( stk . length > 0 && nums [ stk [ stk . length - 1 ]] > nums [ i ]) { stk . pop (); } next [ i ] = stk . length > 0 ? stk [ stk . length - 1 ] : n ; stk . push ( i ); } } else { for ( let i = 0 ; i < n ; i ++ ) { while ( stk . length > 0 && nums [ stk [ stk . length - 1 ]] <= nums [ i ]) { stk . pop (); } prev [ i ] = stk . length > 0 ? stk [ stk . length - 1 ] : - 1 ; stk . push ( i ); } stk = []; for ( let i = n - 1 ; i >= 0 ; i -- ) { while ( stk . length > 0 && nums [ stk [ stk . length - 1 ]] < nums [ i ]) { stk . pop (); } next [ i ] = stk . length > 0 ? stk [ stk . length - 1 ] : n ; stk . push ( i ); } } let totalSum = 0 ; for ( let i = 0 ; i < n ; i ++ ) { const left = prev [ i ]; const right = next [ i ]; const a = left + 1 ; const b = i ; const c = i ; const d = right - 1 ; let start1 = Math . max ( a , i - k + 1 ); let endCandidate1 = d - k + 1 ; let upper1 = Math . min ( b , endCandidate1 ); let sum1 = 0 ; if ( upper1 >= start1 ) { const termCount = upper1 - start1 + 1 ; const first = start1 ; const last = upper1 ; const indexSum = ( last * ( last + 1 )) / 2 - (( first - 1 ) * first ) / 2 ; const constantSum = ( k - i ) * termCount ; sum1 = indexSum + constantSum ; } let start2 = upper1 + 1 ; let end2 = b ; start2 = Math . max ( start2 , a ); end2 = Math . min ( end2 , b ); let sum2 = 0 ; if ( start2 <= end2 ) { const count = end2 - start2 + 1 ; const term = d - i + 1 ; sum2 = term * count ; } totalSum += nums [ i ] * ( sum1 + sum2 ); } return totalSum ; }; const minSum = computeSum ( nums , k , true ); const maxSum = computeSum ( nums , k , false ); return minSum + maxSum ; };Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.