Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions problems/3691-maximum-total-subarray-value-ii/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 3691. Maximum Total Subarray Value II

[LeetCode Link](https://leetcode.com/problems/maximum-total-subarray-value-ii/)

Difficulty: Hard
Topics: Array, Greedy, Segment Tree, Heap (Priority Queue)
Acceptance Rate: 28.9%

This is a genuinely hard problem — the greedy insight is short, but turning it into something
that runs within the `n <= 5·10^4` limit takes a careful counting argument. Don't be discouraged
if the path to an efficient solution isn't obvious; work through the hints in order.

## Hints

### Hint 1

Since every subarray's value `max - min` is non-negative and we want the **maximum** total over
exactly `k` *distinct* subarrays, distinctness never forces us to take a smaller value when a
larger one is available. So the task reduces to a clean one: **sum the `k` largest values** among
all `O(n^2)` subarrays. There are far too many subarrays to list them, so think about how you'd
find the top `k` of a huge implicit multiset *without materializing it*.

### Hint 2

A classic trick for "sum of the top `k` elements of an implicit set" is to **binary search on a
threshold value `T`**. If you can quickly answer "how many subarrays have value `>= T`?", then you
can find the cutoff value where the count first drops below `k`. The monotonicity you need is real:
for a fixed left endpoint, growing the subarray to the right only makes `max - min` larger, so the
count of subarrays with value `>= T` is non-increasing in `T`.

### Hint 3

Counting (and summing) subarrays by their range is easiest via the **complement**: count subarrays
whose range `max - min <= X`. That predicate is *monotone in a sliding window* — if `[l..r]` has
range `<= X`, so does any `[l'..r]` with `l' >= l`. So a two-pointer window where `left` only ever
moves right works, with two **monotonic deques** tracking the window max and min. The extra spark:
maintain not just the max/min, but the **windowed sum of subarray-maxes and subarray-mins ending at
`r`**. Each deque element owns a contiguous block of left endpoints; store that block's length so
you can update the running sums in O(1) as the window grows on the right and shrinks on the left.
That gives you both `count(range <= X)` and `sum(range <= X)` in one O(n) pass.

## Approach

**Reduction.** Because values are non-negative and subarrays are distinct, the answer is simply the
sum of the `k` largest values of `range(l, r) = max(nums[l..r]) - min(nums[l..r])` over all
subarrays.

**One O(n) primitive.** Define `f(X) -> (cnt, sum)` where `cnt` is the number of subarrays with
`range <= X` and `sum` is the total of their ranges. Compute it with a sliding window `[left, r]`:

- Two monotonic deques hold the window's maximum and minimum. Each deque element is a *segment*: an
index `i` together with `len`, the number of left endpoints `l` for which this element is the max
(resp. min) of `nums[l..r]`. The front segment of the max deque is the maximum of the whole
window; the front of the min deque is the minimum.
- Maintain `sumMax = Σ_{l=left..r} max(nums[l..r])` and `sumMin` analogously. When `r` enters,
pop the back segments it dominates (`<=` for max, `>=` for min), accumulate their lengths, and push
`r` as a new segment of length `poppedLen + 1`, adjusting `sumMax` / `sumMin` by the segment value
times length. When `range > X`, advance `left`: drop one unit from the front segment of each deque
(subtract its value once, decrement its length, pop it if it hits 0).
- After settling the window for this `r`, add `r - left + 1` to `cnt` and `sumMax - sumMin` to `sum`.

A single call with a huge `X` (so the window never shrinks) yields `total = n(n+1)/2` and
`totalSum = Σ range` over *all* subarrays.

**From the primitive to the answer.** Let `geCount(T) = total - f(T-1).cnt` be the number of
subarrays with `range >= T`; it is non-increasing in `T`. Binary search for the largest `T*` with
`geCount(T*) >= k` (search range `[0, max(nums) - min(nums)]`). Then:

- `c1 = geCount(T*+1)` subarrays have `range > T*`; all of them are in the top `k`. Their total is
`sumGE(T*+1) = totalSum - f(T*).sum`.
- Fill the remaining `k - c1` picks with subarrays of range exactly `T*`, each contributing `T*`.

So `answer = sumGE(T*+1) + T* · (k - c1)`.

**Why it works.** `geCount(T*) >= k > geCount(T*+1)` guarantees enough range-`T*` subarrays exist to
finish the selection, and every subarray strictly above the cutoff is included exactly once.

**Worked example.** `nums = [4,2,5,1], k = 3`. Subarray ranges are
`{0,0,0,0,2,3,4,3,4,4}`; sorted descending the top three are `4,4,4`. Here `total = 10`,
`geCount(4) = 3 >= 3` and `geCount(5) = 0`, so `T* = 4`, `c1 = 0`, `sumGE(5) = 0`, and the answer is
`0 + 4·(3 - 0) = 12`.

## Complexity Analysis

Time Complexity: O(n · log V), where `V = max(nums) - min(nums)`. Each `f(X)` runs in O(n) (every
index is pushed/popped from each deque at most once), and binary search does O(log V) calls plus a
couple of extra evaluations.
Space Complexity: O(n) for the two monotonic deques.

## Edge Cases

- **Single element (`n = 1`).** The only subarray has range `0`; `k` must be `1` and the answer is
`0`. The window logic and the `T* = 0` branch both produce `0`.
- **All elements equal.** Every range is `0`, so the answer is `0` regardless of `k`. `T* = 0`,
`c1 = 0`, and `T*·(k - c1) = 0`.
- **`T* = 0` in general.** When fewer than `k` subarrays have a positive range, we take all positive
ones (`sumGE(1) = totalSum`) and pad with zero-range subarrays; the formula collapses to
`totalSum`.
- **Large counts overflow `int32`.** The number of subarrays can reach ~`1.25·10^9` and the running
sums reach ~`10^18`; use 64-bit integers throughout for counts, sums, and the answer.
- **`k` equal to the total number of subarrays.** Then `T* = 0` and the answer is the sum of *all*
subarray ranges, i.e. `totalSum`.
75 changes: 75 additions & 0 deletions problems/3691-maximum-total-subarray-value-ii/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
number: "3691"
frontend_id: "3691"
title: "Maximum Total Subarray Value II"
slug: "maximum-total-subarray-value-ii"
difficulty: "Hard"
topics:
- "Array"
- "Greedy"
- "Segment Tree"
- "Heap (Priority Queue)"
acceptance_rate: 2885.8
is_premium: false
created_at: "2026-06-10T04:55:42.758621+00:00"
fetched_at: "2026-06-10T04:55:42.758621+00:00"
link: "https://leetcode.com/problems/maximum-total-subarray-value-ii/"
date: "2026-06-10"
---

# 3691. Maximum Total Subarray Value II

You are given an integer array `nums` of length `n` and an integer `k`.

You must select **exactly** `k` **distinct** non-empty subarrays `nums[l..r]` of `nums`. Subarrays may overlap, but the exact same subarray (same `l` and `r`) **cannot** be chosen more than once.

The **value** of a subarray `nums[l..r]` is defined as: `max(nums[l..r]) - min(nums[l..r])`.

The **total value** is the sum of the **values** of all chosen subarrays.

Return the **maximum** possible total value you can achieve.



**Example 1:**

**Input:** nums = [1,3,2], k = 2

**Output:** 4

**Explanation:**

One optimal approach is:

* Choose `nums[0..1] = [1, 3]`. The maximum is 3 and the minimum is 1, giving a value of `3 - 1 = 2`.
* Choose `nums[0..2] = [1, 3, 2]`. The maximum is still 3 and the minimum is still 1, so the value is also `3 - 1 = 2`.



Adding these gives `2 + 2 = 4`.

**Example 2:**

**Input:** nums = [4,2,5,1], k = 3

**Output:** 12

**Explanation:**

One optimal approach is:

* Choose `nums[0..3] = [4, 2, 5, 1]`. The maximum is 5 and the minimum is 1, giving a value of `5 - 1 = 4`.
* Choose `nums[1..3] = [2, 5, 1]`. The maximum is 5 and the minimum is 1, so the value is also `4`.
* Choose `nums[2..3] = [5, 1]`. The maximum is 5 and the minimum is 1, so the value is again `4`.



Adding these gives `4 + 4 + 4 = 12`.



**Constraints:**

* `1 <= n == nums.length <= 5 * 10​​​​​​​4`
* `0 <= nums[i] <= 109`
* `1 <= k <= min(105, n * (n + 1) / 2)`
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package main

// 3691. Maximum Total Subarray Value II
//
// Approach: The answer is the sum of the k largest subarray "values", where the
// value of nums[l..r] is max - min (all values are non-negative and subarrays
// must be distinct, so distinctness never forces a smaller pick).
//
// We binary search a cutoff range value T. For a threshold X we compute, in a
// single O(n) sliding-window pass, both the number of subarrays whose range
// (max-min) is <= X and the sum of those ranges. Two monotonic deques track the
// window max and min; each deque element owns a contiguous block of left
// endpoints (its segment length), which lets us maintain the windowed sum of
// subarray-maxes and subarray-mins in O(1) per step. From count(<=X)/sum(<=X)
// we derive count(>=T)/sum(>=T) and assemble the top-k total.
//
// Time: O(n log V), Space: O(n).
func maxTotalValue(nums []int, k int) int64 {
n := len(nums)
total := int64(n) * int64(n+1) / 2

type seg struct {
idx int
ln int64
}

// rangeCountSumLE returns (cnt, sum): the number of subarrays whose
// (max-min) <= X, and the total of their (max-min) values.
rangeCountSumLE := func(X int64) (int64, int64) {
var maxDq, minDq []seg
var sumMax, sumMin int64
var cnt, sum int64
left := 0
for r := 0; r < n; r++ {
v := nums[r]

// Extend the max deque: r absorbs every back segment it dominates.
var pLen int64 = 0
for len(maxDq) > 0 && nums[maxDq[len(maxDq)-1].idx] <= v {
e := maxDq[len(maxDq)-1]
maxDq = maxDq[:len(maxDq)-1]
sumMax -= int64(nums[e.idx]) * e.ln
pLen += e.ln
}
maxDq = append(maxDq, seg{r, pLen + 1})
sumMax += int64(v) * (pLen + 1)

// Extend the min deque symmetrically.
pLen = 0
for len(minDq) > 0 && nums[minDq[len(minDq)-1].idx] >= v {
e := minDq[len(minDq)-1]
minDq = minDq[:len(minDq)-1]
sumMin -= int64(nums[e.idx]) * e.ln
pLen += e.ln
}
minDq = append(minDq, seg{r, pLen + 1})
sumMin += int64(v) * (pLen + 1)

// Shrink from the left while the window range exceeds X.
for int64(nums[maxDq[0].idx]-nums[minDq[0].idx]) > X {
sumMax -= int64(nums[maxDq[0].idx])
maxDq[0].ln--
if maxDq[0].ln == 0 {
maxDq = maxDq[1:]
}
sumMin -= int64(nums[minDq[0].idx])
minDq[0].ln--
if minDq[0].ln == 0 {
minDq = minDq[1:]
}
left++
}

cnt += int64(r - left + 1)
sum += sumMax - sumMin
}
return cnt, sum
}

// totalSum: a window that never shrinks (X larger than any possible range).
_, totalSum := rangeCountSumLE(int64(1) << 62)

// geCount(T): number of subarrays with range >= T.
geCount := func(T int64) int64 {
if T <= 0 {
return total
}
c, _ := rangeCountSumLE(T - 1)
return total - c
}

mx, mn := nums[0], nums[0]
for _, x := range nums {
if x > mx {
mx = x
}
if x < mn {
mn = x
}
}

// Largest T with geCount(T) >= k.
lo, hi := int64(0), int64(mx-mn)
for lo < hi {
mid := (lo + hi + 1) / 2
if geCount(mid) >= int64(k) {
lo = mid
} else {
hi = mid - 1
}
}
tStar := lo

cLE, sLE := rangeCountSumLE(tStar) // subarrays with range <= tStar
c1 := total - cLE // subarrays with range >= tStar+1
sumGE := totalSum - sLE // sum of ranges >= tStar+1

return sumGE + tStar*(int64(k)-c1)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import "testing"

func TestSolution(t *testing.T) {
tests := []struct {
name string
nums []int
k int
expected int64
}{
{"example 1: pick two overlapping subarrays", []int{1, 3, 2}, 2, 4},
{"example 2: three subarrays each value 4", []int{4, 2, 5, 1}, 3, 12},
{"edge case: single element forces value 0", []int{7}, 1, 0},
{"edge case: all equal elements yield 0", []int{5, 5, 5, 5}, 4, 0},
{"edge case: k equals total subarrays sums all ranges", []int{1, 3, 2}, 6, 5},
{"edge case: two elements single pick", []int{0, 1000000000}, 1, 1000000000},
{"edge case: pick only the single largest range", []int{4, 2, 5, 1}, 1, 4},
{"edge case: strictly increasing array", []int{1, 2, 3, 4}, 3, 7},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := maxTotalValue(tt.nums, tt.k)
if got != tt.expected {
t.Errorf("maxTotalValue(%v, %d) = %d, want %d", tt.nums, tt.k, got, tt.expected)
}
})
}
}