LeetCode #2104 Medium

Sum of Subarray Ranges

The range of a subarray is its maximum minus its minimum. Return the sum of ranges over all subarrays.

Constraints
  • 1 <= nums.length <= 1000
  • -10⁹ <= nums[i] <= 10⁹
monotonic-stackarraycontribution
Open on LeetCode ↗
02

Intuition

Sum of subarray ranges sums the difference between the maximum and minimum of every subarray. The brute force over all O(n²) subarrays is acceptable for small inputs, but the linear solution is the point. The key observation splits the problem in two: - The sum of all ranges equals the sum of all subarray maximums minus the sum of all subarray minimums, because each range is a difference and summation distributes over subtraction. So this becomes two independent instances of Sum of Subarray Minimums — one computing minimums, one computing maximums with the comparison reversed. Each half uses the same technique: for every element, count how many subarrays it is the extreme of, then weight by its value. The boundaries are the nearest smaller elements on each side for minimums, and the nearest larger elements for maximums. With left and right as the distances to those boundaries, the element contributes to left × right subarrays. The duplicate-handling rule carries over unchanged. Use a strict comparison on one side and non-strict on the other, in both passes, so subarrays with tied extremes are attributed to exactly one element. A monotonic increasing stack serves the minimums and a decreasing stack the maximums — the same code with the comparison flipped. Unlike Sum of Subarray Minimums, no modulus is required here, since the constraints keep the result within range. Applying one anyway produces wrong answers, which is an easy habit to carry over incorrectly. Four stack passes, or two if each computes both boundaries, give O(n) time and O(n) space.

How to spot this pattern

Range is max minus min, and summation is linear — so the total equals (sum of all subarray maxima) minus (sum of all subarray minima). Each half is the contribution-counting technique from Sum of Subarray Minimums, run with the comparisons flipped.

03

Approach

Try it first

Before reading on: price up what the direct approach costs here, then ask what ordering you can maintain so the answer is always at one end. Aim for O(n) time and O(n) space.

1

Split into two sums

The total equals the sum of all subarray maximums minus the sum of all minimums, since summation distributes over the subtraction in each range.

2

Reuse the minimums technique

Each half is Sum of Subarray Minimums — count how many subarrays each element is the extreme of, then weight by its value.

3

Flip the comparison for maximums

Minimums use nearest smaller elements as boundaries; maximums use nearest larger ones. The same code with the comparison reversed.

4

Multiply the distances

An element is the extreme of left × right subarrays, where those are the distances to its two boundaries.

5

Break ties asymmetrically in both passes

Strict on one side, non-strict on the other, in both the minimum and maximum passes — otherwise tied extremes are counted twice.

6

Apply no modulus

The constraints keep the result in range here, unlike Sum of Subarray Minimums. Applying a modulus out of habit produces wrong answers.

7

Cost of the approach

Each element is pushed and popped a constant number of times, giving O(n) time and O(n) space.

04

Solution & live demo

▶1class Solution:
▶2 def subArrayRanges(self, nums):
▶3 n = len(nums)
▶4 
▶5 def total(is_min):
▶6 prev, nxt = [-1] * n, [n] * n
▶7 st = []
▶8 for i in range(n):
▶9 while st and ((nums[st[-1]] >= nums[i]) if is_min else (nums[st[-1]] <= nums[i])):
▶10 st.pop()
▶11 prev[i] = st[-1] if st else -1
▶12 st.append(i)
▶13 st = []
▶14 for i in range(n - 1, -1, -1):
▶15 while st and ((nums[st[-1]] > nums[i]) if is_min else (nums[st[-1]] < nums[i])):
▶16 st.pop()
▶17 nxt[i] = st[-1] if st else n
▶18 st.append(i)
▶19 return sum(nums[i] * (i - prev[i]) * (nxt[i] - i) for i in range(n))
▶20 
▶21 return total(False) - total(True)
05

Common pitfalls

Trying to compute ranges directly

✗ Wrong
for each subarray: total += max(sub) - min(sub)
✓ Right
return total(False) - total(True)

There's no monotonic stack for "range" itself. Splitting by linearity gives two problems that each have a known linear solution, then subtracting recombines them exactly.

Reusing the same comparison operators for max

✗ Wrong
while st and nums[st[-1]] >= nums[i]:  # for both
✓ Right
(nums[st[-1]] >= nums[i]) if is_min else (nums[st[-1]] <= nums[i])

The maxima pass needs previous-greater and next-greater spans, which means inverted comparisons. Sharing the minima operators computes the minima sum twice and returns zero.

Getting the subtraction order backwards

✗ Wrong
return total(True) - total(False)
✓ Right
return total(False) - total(True)

Range is max − min, so the maxima sum leads. Reversing gives a negative of the correct answer — obviously wrong on inspection, but easy to write when the flag's meaning isn't fresh.

06

Edge cases

All elements equal

Max equals min in every subarray, so the answer is 0 — the sharpest test of correct tie handling.

Single element

Its range is 0, so the answer is 0.

Sorted array

Maxima and minima are at opposite ends of every subarray; the formula still applies unchanged.

Negative values

No special handling needed — no modulus is applied in this problem, unlike Sum of Subarray Minimums.

07

Complexity

Time
O(n)
Space
O(n)
Four monotonic-stack passes. The O(n^2) double loop also passes but teaches less.