From a87ecf50f204e1cd0c2c1e6cf1426c3218a5dd5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Jul 2026 04:01:03 +0000 Subject: [PATCH] feat: add solution for 3501. Maximize Active Section with Trade II --- .../analysis.md | 131 ++++++++++++++++ .../problem.md | 138 +++++++++++++++++ .../solution_daily_20260722.go | 141 ++++++++++++++++++ .../solution_daily_20260722_test.go | 80 ++++++++++ 4 files changed, 490 insertions(+) create mode 100644 problems/3501-maximize-active-section-with-trade-ii/analysis.md create mode 100644 problems/3501-maximize-active-section-with-trade-ii/problem.md create mode 100644 problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722.go create mode 100644 problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722_test.go diff --git a/problems/3501-maximize-active-section-with-trade-ii/analysis.md b/problems/3501-maximize-active-section-with-trade-ii/analysis.md new file mode 100644 index 0000000..3589579 --- /dev/null +++ b/problems/3501-maximize-active-section-with-trade-ii/analysis.md @@ -0,0 +1,131 @@ +# 3501. Maximize Active Section with Trade II + +[LeetCode Link](https://leetcode.com/problems/maximize-active-section-with-trade-ii/) + +Difficulty: Hard +Topics: Array, String, Binary Search, Segment Tree +Acceptance Rate: 47.5% + +This is a genuinely hard problem — it combines a non-obvious modeling step +(figuring out what a "trade" really buys you) with a data-structure step +(answering many range queries fast). Take the modeling insight first; the +efficiency machinery only makes sense once you know exactly what quantity you +need to query. Don't be discouraged if the trade definition feels slippery at +first — everyone has to reduce it to something simpler before it clicks. + +## Hints + +### Hint 1 + +Before thinking about queries at all, solve the single-string version (this is +the easier companion problem, "Maximize Active Section with Trade I"). Describe +in one sentence what a full trade accomplishes: you blank out a block of `1`s +that sits between two `0` blocks, then flood a block of `0`s that sits between +two `1` blocks. What is the *net* change to the number of `1`s? Try it on +`"0 1 0"`-style fragments and look for a pattern in terms of the surrounding +runs of zeros. + +### Hint 2 + +A trade only ever merges **two consecutive runs of zeros** that are separated by +exactly one run of ones. If those zero runs have sizes `z1` and `z2`, the net +gain in active sections is exactly `z1 + z2` (you remove the `1`s between them, +then fill everything back with `1`s). So the whole problem reduces to: *find the +best adjacent pair of zero runs.* Also notice the parts of `s` outside the query +range never change, which means + +``` +answer = totalOnes(s) + bestGain(range) +``` + +Now the only question is how to compute `bestGain` for each of up to 10^5 +ranges quickly. + +### Hint 3 + +Only the **two extreme** zero runs of a query get clipped by the `[l, r]` +boundary — the augmenting `'1'` glued to each end keeps them "surrounded," so a +clipped zero run is still a legal trade partner. Every *interior* consecutive +pair of zero runs uses its full, precomputable size. So precompute +`pairSum[i] = size(zeroRun[i]) + size(zeroRun[i+1])` and answer range-maximum +queries over it with a sparse table (or segment tree). For each query, use +binary search to find the first and last zero runs touching `[l, r]`, take the +interior range-max, and separately compute the two boundary pairs with clipped +sizes. + +## Approach + +**Step 1 — Reduce the trade to a run-merge.** +A valid trade blanks a `1`-block surrounded by `0`s and then fills a `0`-block +surrounded by `1`s. The optimal move is always: pick a `1`-run that sits between +two `0`-runs, delete it (turning `z1 0…0 z2` into one big `0`-block), then fill +that entire merged `0`-block with `1`s. If the two zero runs have sizes `z1` and +`z2`, you converted `z1 + z2` former zeros into ones — a net gain of `z1 + z2`. +No trade at all gives gain `0`, and a trade is only possible when at least two +zero runs are separated by a single one run. + +**Step 2 — Reframe the answer.** +The trade happens strictly inside `s[l..r]`; the rest of `s` is untouched. So the +active-section count of the whole final string is + +``` +answer = totalOnes(s) + bestGain(l, r) +``` + +where `bestGain(l, r)` is the largest `z1 + z2` over adjacent zero runs that lie +inside the augmented substring `t = '1' + s[l..r] + '1'`. + +**Step 3 — Precompute.** +Decompose `s` into maximal zero runs `zStart[i]..zEnd[i]`. In a run +decomposition, consecutive zero runs are *always* separated by exactly one +`1`-run, so every adjacent zero-run pair is a valid trade candidate. Build +`pairSum[i] = size(i) + size(i+1)` and a **sparse table** over it for O(1) +range-maximum queries. + +**Step 4 — Answer a query.** +Use binary search on the (sorted) run boundaries: + +- `a` = first zero run with `zEnd[a] >= l`, +- `b` = last zero run with `zStart[b] <= r`. + +If fewer than two zero runs overlap `[l, r]` (`b <= a`), gain is `0`. Otherwise +the zero runs `a..b` are exactly those inside the range; only `a` and `b` may be +clipped by the boundary. The best gain is the maximum of: + +- the **left boundary pair**: `clip(a) + size(a+1)`, +- the **right boundary pair**: `size(b-1) + clip(b)`, +- the **interior pairs**: `rangeMaxPair(a+1, b-2)` from the sparse table, + +where `clip(i)` is the length of zero run `i` intersected with `[l, r]`. (When +there are exactly two zero runs, both are boundary/clipped and the gain is simply +`clip(a) + clip(b)`.) + +**Worked example.** `s = "1000100"`, query `[1, 5]`. Zero runs are `[1..3]` +(size 3) and `[5..6]` (size 2), and `totalOnes = 2`. The query touches both runs; +run `[1..3]` clips to length 3 and run `[5..6]` clips to length 1 (only index 5 +is in range). Gain `= 3 + 1 = 4`, so `answer = 2 + 4 = 6`, matching the expected +output. + +## Complexity Analysis + +Time Complexity: O((n + q) log n) — O(n log n) to build the sparse table plus +O(log n) per query for the two binary searches and an O(1) range-max lookup, +across `q` queries. +Space Complexity: O(n log n) for the sparse table (O(n) for the run arrays). + +## Edge Cases + +- **No zero runs (all `1`s):** no trade is possible, so every answer is + `totalOnes` with gain `0`. +- **A single zero run in range (including all `0`s):** a trade needs a `1`-block + between two `0`-blocks, which doesn't exist, so gain is `0`. +- **Boundary zero run extends outside `[l, r]`:** it must be clipped to its + intersection with the range; the augmenting `'1'` keeps it a valid trade + partner even though it's truncated (see example 3, query `[1, 5]`). +- **Interior pair beats both boundary pairs:** a clipped boundary run can be + small, so the best pair may be entirely in the middle — the range-max query + over full-size interior pairs catches this. +- **Exactly two overlapping zero runs:** both are clipped; handle this directly + instead of assuming one side is a full interior run. +- **Single-character string / smallest inputs:** the run decomposition and + binary searches must still behave (gain stays `0`). diff --git a/problems/3501-maximize-active-section-with-trade-ii/problem.md b/problems/3501-maximize-active-section-with-trade-ii/problem.md new file mode 100644 index 0000000..1538d0b --- /dev/null +++ b/problems/3501-maximize-active-section-with-trade-ii/problem.md @@ -0,0 +1,138 @@ +--- +number: "3501" +frontend_id: "3501" +title: "Maximize Active Section with Trade II" +slug: "maximize-active-section-with-trade-ii" +difficulty: "Hard" +topics: + - "Array" + - "String" + - "Binary Search" + - "Segment Tree" +acceptance_rate: 4749.9 +is_premium: false +created_at: "2026-07-22T03:55:59.309602+00:00" +fetched_at: "2026-07-22T03:55:59.309602+00:00" +link: "https://leetcode.com/problems/maximize-active-section-with-trade-ii/" +date: "2026-07-22" +--- + +# 3501. Maximize Active Section with Trade II + +You are given a binary string `s` of length `n`, where: + + * `'1'` represents an **active** section. + * `'0'` represents an **inactive** section. + + + +You can perform **at most one trade** to maximize the number of active sections in `s`. In a trade, you: + + * Convert a contiguous block of `'1'`s that is surrounded by `'0'`s to all `'0'`s. + * Afterward, convert a contiguous block of `'0'`s that is surrounded by `'1'`s to all `'1'`s. + + + +Additionally, you are given a **2D array** `queries`, where `queries[i] = [li, ri]` represents a substring `s[li...ri]`. + +For each query, determine the **maximum** possible number of active sections in `s` after making the optimal trade on the substring `s[li...ri]`. + +Return an array `answer`, where `answer[i]` is the result for `queries[i]`. + +**Note** + + * For each query, treat `s[li...ri]` as if it is **augmented** with a `'1'` at both ends, forming `t = '1' + s[li...ri] + '1'`. The augmented `'1'`s **do not** contribute to the final count. + * The queries are independent of each other. + + + + + +**Example 1:** + +**Input:** s = "01", queries = [[0,1]] + +**Output:** [1] + +**Explanation:** + +Because there is no block of `'1'`s surrounded by `'0'`s, no valid trade is possible. The maximum number of active sections is 1. + +**Example 2:** + +**Input:** s = "0100", queries = [[0,3],[0,2],[1,3],[2,3]] + +**Output:** [4,3,1,1] + +**Explanation:** + + * Query `[0, 3]` -> Substring `"0100"` -> Augmented to `"101001"` +Choose `"0100"`, convert `"0100"` -> `"0000"` -> `"1111"`. +The final string without augmentation is `"1111"`. The maximum number of active sections is 4. + + * Query `[0, 2]` -> Substring `"010"` -> Augmented to `"10101"` +Choose `"010"`, convert `"010"` -> `"000"` -> `"111"`. +The final string without augmentation is `"1110"`. The maximum number of active sections is 3. + + * Query `[1, 3]` -> Substring `"100"` -> Augmented to `"11001"` +Because there is no block of `'1'`s surrounded by `'0'`s, no valid trade is possible. The maximum number of active sections is 1. + + * Query `[2, 3]` -> Substring `"00"` -> Augmented to `"1001"` +Because there is no block of `'1'`s surrounded by `'0'`s, no valid trade is possible. The maximum number of active sections is 1. + + + + +**Example 3:** + +**Input:** s = "1000100", queries = [[1,5],[0,6],[0,4]] + +**Output:** [6,7,2] + +**Explanation:** + + * Query `[1, 5]` -> Substring `"00010"` -> Augmented to `"1000101"` +Choose `"00010"`, convert `"00010"` -> `"00000"` -> `"11111"`. +The final string without augmentation is `"1111110"`. The maximum number of active sections is 6. + + * Query `[0, 6]` -> Substring `"1000100"` -> Augmented to `"110001001"` +Choose `"000100"`, convert `"000100"` -> `"000000"` -> `"111111"`. +The final string without augmentation is `"1111111"`. The maximum number of active sections is 7. + + * Query `[0, 4]` -> Substring `"10001"` -> Augmented to `"1100011"` +Because there is no block of `'1'`s surrounded by `'0'`s, no valid trade is possible. The maximum number of active sections is 2. + + + + +**Example 4:** + +**Input:** s = "01010", queries = [[0,3],[1,4],[1,3]] + +**Output:** [4,4,2] + +**Explanation:** + + * Query `[0, 3]` -> Substring `"0101"` -> Augmented to `"101011"` +Choose `"010"`, convert `"010"` -> `"000"` -> `"111"`. +The final string without augmentation is `"11110"`. The maximum number of active sections is 4. + + * Query `[1, 4]` -> Substring `"1010"` -> Augmented to `"110101"` +Choose `"010"`, convert `"010"` -> `"000"` -> `"111"`. +The final string without augmentation is `"01111"`. The maximum number of active sections is 4. + + * Query `[1, 3]` -> Substring `"101"` -> Augmented to `"11011"` +Because there is no block of `'1'`s surrounded by `'0'`s, no valid trade is possible. The maximum number of active sections is 2. + + + + + + +**Constraints:** + + * `1 <= n == s.length <= 105` + * `1 <= queries.length <= 105` + * `s[i]` is either `'0'` or `'1'`. + * `queries[i] = [li, ri]` + * `0 <= li <= ri < n` diff --git a/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722.go b/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722.go new file mode 100644 index 0000000..2665ac5 --- /dev/null +++ b/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722.go @@ -0,0 +1,141 @@ +package main + +import "sort" + +// Approach: +// +// A "trade" removes a block of 1's that is surrounded by 0's, then fills a block +// of 0's surrounded by 1's. The net effect is to merge two consecutive zero-runs +// (separated by exactly one one-run) into all 1's, gaining (size of the left +// zero-run) + (size of the right zero-run) active sections. The one-run between +// them is what we remove and then re-fill. +// +// Because the parts of s outside a query range are untouched, the answer for a +// query is simply: +// +// answer = totalOnes(s) + bestTradeGain(range) +// +// where bestTradeGain is the maximum sum of two consecutive (clipped) zero-runs +// that lie inside the augmented substring t = '1' + s[l..r] + '1'. The augmenting +// 1's only matter for the two extreme zero-runs of the range: they may be clipped +// by the boundary yet are still "surrounded" thanks to the augmentation. Every +// interior consecutive-pair uses full run sizes, so we precompute those pair sums +// and answer range-maximum queries in O(1) with a sparse table. The two boundary +// pairs are handled directly with clipped sizes. +// +// Complexity: O((n + q) log n) time, O(n) space. +func maxActiveSectionsAfterTrade(s string, queries [][]int) []int { + n := len(s) + + totalOnes := 0 + for i := 0; i < n; i++ { + if s[i] == '1' { + totalOnes++ + } + } + + // Zero-run decomposition: zStart[i]..zEnd[i] (inclusive) are all '0'. + var zStart, zEnd []int + for i := 0; i < n; { + if s[i] == '0' { + j := i + for j < n && s[j] == '0' { + j++ + } + zStart = append(zStart, i) + zEnd = append(zEnd, j-1) + i = j + } else { + i++ + } + } + m := len(zStart) + + size := func(i int) int { return zEnd[i] - zStart[i] + 1 } + + // Sparse table over pairSum[i] = size(i) + size(i+1) for i in [0, m-2]. + var sparse [][]int + var logTable []int + if m >= 2 { + cnt := m - 1 + logTable = make([]int, cnt+1) + for i := 2; i <= cnt; i++ { + logTable[i] = logTable[i/2] + 1 + } + K := logTable[cnt] + 1 + sparse = make([][]int, K) + sparse[0] = make([]int, cnt) + for i := 0; i < cnt; i++ { + sparse[0][i] = size(i) + size(i+1) + } + for k := 1; k < K; k++ { + width := 1 << k + sparse[k] = make([]int, cnt-width+1) + for i := 0; i+width <= cnt; i++ { + a := sparse[k-1][i] + b := sparse[k-1][i+(1<<(k-1))] + if a > b { + sparse[k][i] = a + } else { + sparse[k][i] = b + } + } + } + } + + // rangeMaxPair returns the max pairSum over indices [lo, hi] (inclusive), + // or 0 when the range is empty. + rangeMaxPair := func(lo, hi int) int { + if lo > hi { + return 0 + } + k := logTable[hi-lo+1] + a := sparse[k][lo] + b := sparse[k][hi-(1< b { + return a + } + return b + } + + // Length of zero-run i clipped to [l, r]. + clip := func(i, l, r int) int { + e := zEnd[i] + if e > r { + e = r + } + st := zStart[i] + if st < l { + st = l + } + return e - st + 1 + } + + ans := make([]int, len(queries)) + for qi, q := range queries { + l, r := q[0], q[1] + gain := 0 + if m >= 2 { + // First zero-run whose end >= l, last zero-run whose start <= r. + a := sort.Search(m, func(i int) bool { return zEnd[i] >= l }) + b := sort.Search(m, func(i int) bool { return zStart[i] > r }) - 1 + if a <= b && a >= 0 && b > a { + if b == a+1 { + // Exactly two zero-runs: both are boundary/clipped. + gain = clip(a, l, r) + clip(b, l, r) + } else { + // Left boundary pair, right boundary pair, and interior pairs. + gain = clip(a, l, r) + size(a+1) + if g := size(b-1) + clip(b, l, r); g > gain { + gain = g + } + if g := rangeMaxPair(a+1, b-2); g > gain { + gain = g + } + } + } + } + ans[qi] = totalOnes + gain + } + return ans +} diff --git a/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722_test.go b/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722_test.go new file mode 100644 index 0000000..88de299 --- /dev/null +++ b/problems/3501-maximize-active-section-with-trade-ii/solution_daily_20260722_test.go @@ -0,0 +1,80 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestSolution(t *testing.T) { + tests := []struct { + name string + s string + queries [][]int + expected []int + }{ + { + name: "example 1: no valid trade possible", + s: "01", + queries: [][]int{{0, 1}}, + expected: []int{1}, + }, + { + name: "example 2: single zero-run vs. two zero-runs", + s: "0100", + queries: [][]int{{0, 3}, {0, 2}, {1, 3}, {2, 3}}, + expected: []int{4, 3, 1, 1}, + }, + { + name: "example 3: clipped boundary zero-run", + s: "1000100", + queries: [][]int{{1, 5}, {0, 6}, {0, 4}}, + expected: []int{6, 7, 2}, + }, + { + name: "example 4: alternating pattern", + s: "01010", + queries: [][]int{{0, 3}, {1, 4}, {1, 3}}, + expected: []int{4, 4, 2}, + }, + { + name: "edge case: all ones (no zero-run, gain always 0)", + s: "1111", + queries: [][]int{{0, 3}, {1, 2}, {0, 0}}, + expected: []int{4, 4, 4}, + }, + { + name: "edge case: all zeros (single zero-run, no trade)", + s: "0000", + queries: [][]int{{0, 3}, {1, 2}}, + expected: []int{0, 0}, + }, + { + name: "edge case: single character", + s: "0", + queries: [][]int{{0, 0}}, + expected: []int{0}, + }, + { + name: "edge case: interior pair beats boundary pairs", + s: "010010010", + queries: [][]int{{0, 8}, {3, 6}}, + expected: []int{7, 6}, + }, + { + name: "edge case: three zero-runs, best is the wider adjacent pair", + s: "0100100", + queries: [][]int{{0, 6}}, + expected: []int{6}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := maxActiveSectionsAfterTrade(tt.s, tt.queries) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("maxActiveSectionsAfterTrade(%q, %v) = %v, want %v", + tt.s, tt.queries, result, tt.expected) + } + }) + } +}