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
82 changes: 82 additions & 0 deletions problems/1658-minimum-operations-to-reduce-x-to-zero/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# 1658. Minimum Operations to Reduce X to Zero

[LeetCode Link](https://leetcode.com/problems/minimum-operations-to-reduce-x-to-zero/)

Difficulty: Medium
Topics: Array, Hash Table, Binary Search, Sliding Window, Prefix Sum
Acceptance Rate: 41.7%

## Hints

### Hint 1

The operations only ever peel elements off the two ends, so whatever you remove is always a **prefix plus a suffix** of the original array — never anything from the middle. That means the order of your removals doesn't matter, only *how many* you take from the left and *how many* from the right. Try to restate the problem in terms of those two counts instead of a sequence of moves.

Also notice what's missing from the problem: nothing asks you to actually mutate the array. Simulating removals (or exploring both choices recursively) is the trap that makes this look harder than it is.

### Hint 2

Searching directly over "prefix of length `i` + suffix of length `j` with sum exactly `x`" is doable with prefix sums and a two-pointer scan, but there's a slicker reformulation. The elements you **keep** are exactly the ones you didn't remove — and since the removals are a prefix and a suffix, the kept elements form a **contiguous subarray** in the middle.

So: if `total` is the sum of the whole array, and the removed part sums to `x`, what must the middle subarray sum to? And if you want to *minimize* the number removed, what do you want to do to the middle?

### Hint 3

Minimize removals ⟺ **maximize the length of a contiguous subarray whose sum equals `total - x`**. Call that target `target = total - x`. If you find the longest such subarray with length `L`, the answer is `n - L`.

The last piece is why a sliding window is legal here: the constraint `1 <= nums[i]` means every element is strictly positive, so the running window sum is **monotonically increasing** as you extend the right end and **decreasing** as you advance the left end. That monotonicity is exactly what lets you move `left` forward without ever needing to back up — giving an O(n) scan instead of a hash-map-of-prefix-sums lookup. (The hash map version works too and is the approach you'd need if negatives were allowed.)

Watch the two degenerate targets: `target < 0` and `target == 0`.

## Approach

**Step 1 — Reframe.** Removing a prefix summing to `p` and a suffix summing to `s` with `p + s = x` leaves behind a contiguous middle subarray summing to `total - x`. Minimizing `(prefix length + suffix length)` is the same as maximizing the middle subarray's length. So the whole problem collapses to:

> Find the **longest** contiguous subarray of `nums` with sum exactly `target = total - x`.

If such a subarray of length `L` exists, the answer is `n - L`. If none exists, the answer is `-1`.

**Step 2 — Handle the degenerate targets.**

- `target < 0`: the entire array sums to less than `x`, so even removing everything can't reach `x`. Return `-1` immediately. (This also guards the sliding window, which assumes a non-negative target.)
- `target == 0`: you must remove the entire array. The "longest subarray summing to 0" is the empty one, length `0`, so the answer is `n`. Returning `n` up front keeps the main loop's `sum == target` check dealing only with non-empty windows.

**Step 3 — Slide the window.** Maintain `left`, `right`, and the running `sum` of `nums[left..right]`.

- Extend: add `nums[right]` to `sum`.
- Shrink: while `sum > target`, subtract `nums[left]` and advance `left`. Since every element is `>= 1`, each subtraction strictly decreases `sum`, so this terminates, and `sum` can never skip past `target` from below — we check it right after shrinking.
- Record: if `sum == target`, update the best length with `right - left + 1`.

Each index enters and leaves the window at most once, so the pointers advance a total of at most `2n` times.

**Worked example** — `nums = [3,2,20,1,1,3]`, `x = 10`:

`total = 30`, so `target = 30 - 10 = 20`.

| right | value | sum before shrink | shrink action | window | sum | best |
|---|---|---|---|---|---|---|
| 0 | 3 | 3 | — | `[3]` | 3 | — |
| 1 | 2 | 5 | — | `[3,2]` | 5 | — |
| 2 | 20 | 25 | drop 3, drop 2 | `[20]` | 20 | **1** |
| 3 | 1 | 21 | drop 20 | `[1]` | 1 | 1 |
| 4 | 1 | 2 | — | `[1,1]` | 2 | 1 |
| 5 | 3 | 5 | — | `[1,1,3]` | 5 | 1 |

Longest window is `[20]` with `L = 1`, so the answer is `6 - 1 = 5` — matching the expected output (remove `3,2` from the left and `1,1,3` from the right).

**Why not simulate?** Greedily taking the smaller end fails (Example 3 forces you to remove a `3` early even though `1`s sit on the right), and branching on both ends is `O(2^n)`. Memoizing on `(left, right)` is `O(n^2)` states — too slow for `n = 10^5`. The reframing is what buys the linear scan.

## Complexity Analysis

Time Complexity: O(n) — one pass to compute `total`, then one pass where `right` advances `n` times and `left` advances at most `n` times total.
Space Complexity: O(1) — only a handful of integer accumulators; the input is never copied or mutated.

## Edge Cases

- **`x > total` (`target < 0`)**: impossible — removing every element still leaves `x` positive. Must return `-1` *before* entering the window loop, otherwise `sum > target` would be true even for an empty window and `left` would run past `right` forever chasing an unreachable target.
- **`x == total` (`target == 0`)**: the answer is `n`, not `-1`. Easy to lose if you only look for non-empty windows, since the matching "middle" subarray here is the empty one.
- **No subarray sums to `target` even though `target > 0`**: e.g. `nums = [2,2,2], x = 3` → `target = 3`, but every subarray sum is even. The window never hits equality, so `best` stays unset and the answer is `-1`. Don't confuse "no window found" with "window of length 0".
- **Single-element array**: `[5], x = 5` → `1`; `[5], x = 3` → `-1`. Confirms the loop handles `n = 1` without special casing.
- **Answer uses only one end**: `nums = [1,1,4,2,3], x = 5` takes purely a suffix. The reformulation covers this automatically because the middle subarray is simply flush against one edge — no separate "prefix-only / suffix-only" branch needed.
- **Integer sizes**: `n <= 10^5` and `nums[i] <= 10^4` put `total` at up to `10^9`, and `x <= 10^9`, so `total - x` fits comfortably in Go's `int` (64-bit on all mainstream platforms). No overflow concern here, but it's worth a moment's thought since the sums are near the 32-bit boundary.
- **All elements positive is load-bearing**: the sliding window is only correct because of it. If negatives were allowed you'd need the prefix-sum + hash-map variant (store the *earliest* index for each prefix sum, then maximize `i - firstIndex[prefix-target]`), which is the Hash Table topic tag hinting at the general version.
58 changes: 58 additions & 0 deletions problems/1658-minimum-operations-to-reduce-x-to-zero/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
number: "1658"
frontend_id: "1658"
title: "Minimum Operations to Reduce X to Zero"
slug: "minimum-operations-to-reduce-x-to-zero"
difficulty: "Medium"
topics:
- "Array"
- "Hash Table"
- "Binary Search"
- "Sliding Window"
- "Prefix Sum"
acceptance_rate: 4170.9
is_premium: false
created_at: "2026-09-23T04:58:04.824531+00:00"
fetched_at: "2026-09-23T04:58:04.824531+00:00"
link: "https://leetcode.com/problems/minimum-operations-to-reduce-x-to-zero/"
date: "2026-09-23"
---

# 1658. Minimum Operations to Reduce X to Zero

You are given an integer array `nums` and an integer `x`. In one operation, you can either remove the leftmost or the rightmost element from the array `nums` and subtract its value from `x`. Note that this **modifies** the array for future operations.

Return _the**minimum number** of operations to reduce _`x` _to**exactly**_ `0` _if it is possible_ _, otherwise, return_`-1`.



**Example 1:**


**Input:** nums = [1,1,4,2,3], x = 5
**Output:** 2
**Explanation:** The optimal solution is to remove the last two elements to reduce x to zero.


**Example 2:**


**Input:** nums = [5,6,7,8,9], x = 4
**Output:** -1


**Example 3:**


**Input:** nums = [3,2,20,1,1,3], x = 10
**Output:** 5
**Explanation:** The optimal solution is to remove the last three elements and the first two elements (5 operations in total) to reduce x to zero.




**Constraints:**

* `1 <= nums.length <= 105`
* `1 <= nums[i] <= 104`
* `1 <= x <= 109`
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

// 1658. Minimum Operations to Reduce X to Zero
//
// Removals only ever come off the two ends, so the removed elements form a
// prefix plus a suffix and the kept elements form a contiguous middle
// subarray. Removing a total of x therefore means the middle sums to
// target = total - x, and minimizing the number of removals is the same as
// maximizing that middle subarray's length: the answer is n - longestLen.
//
// Every nums[i] is >= 1, so a window's sum grows when the right end extends
// and shrinks when the left end advances. That monotonicity lets a single
// sliding window find the longest subarray summing to target in O(n) time
// and O(1) space.
func minOperations(nums []int, x int) int {
total := 0
for _, v := range nums {
total += v
}

target := total - x
if target < 0 {
// Even removing every element leaves x positive.
return -1
}
if target == 0 {
// The whole array must go; the matching middle subarray is empty.
return len(nums)
}

longest := -1
sum, left := 0, 0
for right, v := range nums {
sum += v
// target > 0 here, so this never shrinks past an empty window.
for sum > target {
sum -= nums[left]
left++
}
if sum == target && right-left+1 > longest {
longest = right - left + 1
}
}

if longest < 0 {
return -1
}
return len(nums) - longest
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import "testing"

func TestSolution(t *testing.T) {
tests := []struct {
name string
nums []int
x int
expected int
}{
{"example 1: suffix only, remove last two", []int{1, 1, 4, 2, 3}, 5, 2},
{"example 2: x exceeds total sum", []int{5, 6, 7, 8, 9}, 4, -1},
{"example 3: two from the left and three from the right", []int{3, 2, 20, 1, 1, 3}, 10, 5},
{"edge case: x equals total sum, remove everything", []int{1, 1, 4, 2, 3}, 11, 5},
{"edge case: target reachable in value but not as a subarray sum", []int{2, 2, 2}, 3, -1},
{"edge case: single element matches x exactly", []int{5}, 5, 1},
{"edge case: single element smaller than x", []int{5}, 3, -1},
{"edge case: one operation off the left end", []int{5, 2, 3, 1, 1}, 5, 1},
{"edge case: prefer the longer of two matching windows", []int{1, 1, 1, 1, 1}, 2, 2},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nums := make([]int, len(tt.nums))
copy(nums, tt.nums)

result := minOperations(nums, tt.x)
if result != tt.expected {
t.Errorf("minOperations(%v, %d) = %v, want %v", tt.nums, tt.x, result, tt.expected)
}
for i := range nums {
if nums[i] != tt.nums[i] {
t.Errorf("minOperations mutated input: got %v, want %v", nums, tt.nums)
break
}
}
})
}
}