LeetCode 2702 - Minimum Operations to Make Numbers Non-positive

In this problem, we are given an array nums and two integers x and y, where x y. During each operation, we choose one index i and apply two different decrements: - nums[i] decreases by x - every other element decreases by y The goal is to determine the minimum number of…

LeetCode Problem 2702

Difficulty: 🔴 Hard
Topics: Array, Binary Search

Solution

Problem Understanding

In this problem, we are given an array nums and two integers x and y, where x > y. During each operation, we choose one index i and apply two different decrements:

  • nums[i] decreases by x
  • every other element decreases by y

The goal is to determine the minimum number of operations needed so that every value in the array becomes less than or equal to zero.

At first glance, the operation looks asymmetric because one chosen element receives a larger reduction while the rest receive a smaller reduction. However, there is a very important observation hidden inside the operation.

Every operation decreases all elements by at least y. The chosen element simply receives an additional reduction of x - y.

This reformulation is the key to solving the problem efficiently.

Suppose we perform exactly k operations total. Then every element automatically loses k * y, regardless of how many times it was selected. On top of that, whenever an element is chosen during an operation, it gains an extra reduction of x - y.

So after k operations:

  • every number loses k * y
  • some elements may need additional targeted reductions

The challenge becomes determining whether a given number of operations k is sufficient.

The constraints are extremely large:

  • nums.length can be up to 10^5
  • nums[i] can be up to 10^9

This immediately rules out simulation-based approaches. We cannot try every sequence of operations. We need a more mathematical strategy.

The problem guarantees that:

  • 1 <= y < x
  • therefore x - y is always positive

This matters because targeted operations genuinely provide extra progress.

Several edge cases are important:

  • Arrays with one element
  • Very large values near 10^9
  • Cases where global reduction alone is enough
  • Cases where one element dominates and must be selected many times
  • Situations where the answer is very large, requiring careful binary search bounds

Approaches

Brute Force Approach

A brute-force solution would attempt to simulate operations directly.

One possible strategy is repeatedly selecting the current maximum element, because reducing the largest number seems intuitively optimal. After each operation:

  • the chosen element decreases by x
  • all others decrease by y

We would continue until all values become non-positive.

This approach is correct in spirit because eventually all numbers will reach zero or below. However, it is computationally infeasible.

The answer itself can be extremely large. For example, if values are near 10^9 and reductions are small, we may need hundreds of millions of operations. Simulating each operation individually would be far too slow.

Even using a priority queue would not help enough because the number of operations dominates the complexity.

Key Insight

The critical observation is that every operation contributes a guaranteed reduction of y to every element.

Instead of thinking operationally, we think mathematically.

Assume we perform exactly k operations.

Then every element automatically loses:

k * y

If an element still remains positive afterward, we must target it additional times. Each targeted selection contributes an extra reduction of:

x - y

For an element num:

remaining = num - k * y

If remaining > 0, we need:

ceil(remaining / (x - y))

extra selections of that element.

If the total required selections across all elements is at most k, then k operations are feasible.

This transforms the problem into a decision problem:

Can we finish everything in k operations?

Once we can answer that efficiently, we use binary search on the answer.

The feasibility condition is monotonic:

  • if k operations work, then k + 1 operations also work

That monotonic property makes binary search valid.

Approach Comparison

Approach Time Complexity Space Complexity Notes
Brute Force O(answer × n) or worse O(n) Simulates operations directly, far too slow
Optimal O(n log M) O(1) Binary search on answer with mathematical feasibility check

Here, M represents the search range for the answer.

Algorithm Walkthrough

  1. Observe that every operation decreases all elements by y.

Instead of handling the chosen element separately, reinterpret the operation as:

  • all elements lose y
  • the chosen element gains an additional reduction of x - y
  1. Binary search the minimum number of operations.

We search for the smallest integer k such that all numbers can become non-positive after exactly k operations. 3. Define a feasibility function.

For each element num:

  • after k operations, it automatically loses k * y
  • compute:
remaining = num - k * y
  1. If remaining <= 0, this element already becomes non-positive.

No targeted operations are needed. 5. Otherwise, calculate how many times this element must be selected.

Each targeted selection gives an extra reduction of:

x - y

So the required selections are:

ceil(remaining / (x - y))

We compute this efficiently using integer arithmetic:

(remaining + extra - 1) // extra

where:

extra = x - y
  1. Sum the required selections for all elements.

If the total required selections is less than or equal to k, then k operations are feasible. 7. Use binary search.

  • if k is feasible, try smaller values
  • otherwise, try larger values
  1. Return the smallest feasible value.

Why it works

The correctness comes from separating every operation into two components:

  • a universal reduction of y
  • an additional targeted reduction of x - y

After fixing the total number of operations k, every element receives the global reduction automatically. The only remaining question is whether we have enough targeted selections available to eliminate the leftover positive values.

If the total required targeted selections does not exceed k, then we can distribute the selections across the operations and achieve the goal. Because feasibility is monotonic, binary search correctly finds the minimum valid number of operations.

Python Solution

from typing import List

class Solution:
    def minOperations(self, nums: List[int], x: int, y: int) -> int:
        extra = x - y

        def can_finish(operations: int) -> bool:
            required = 0

            for num in nums:
                remaining = num - operations * y

                if remaining > 0:
                    needed = (remaining + extra - 1) // extra
                    required += needed

                    if required > operations:
                        return False

            return required <= operations

        left = 0
        right = max(nums)

        while left < right:
            mid = (left + right) // 2

            if can_finish(mid):
                right = mid
            else:
                left = mid + 1

        return left

The implementation begins by computing extra = x - y, which represents the additional reduction gained when an index is selected.

The helper function can_finish determines whether a given number of operations is sufficient. For every element, it computes the remaining value after the global reduction of operations * y.

If the remaining value is still positive, the algorithm calculates how many extra targeted selections are required. These required selections are accumulated.

An important optimization appears inside the loop. As soon as the total required selections exceeds the number of available operations, the function immediately returns False. This avoids unnecessary work.

The main binary search maintains a search range from 0 to max(nums). The upper bound works because each operation reduces at least one element by x, so eventually even the largest number can be eliminated.

Whenever a midpoint is feasible, the algorithm searches for a smaller answer. Otherwise, it searches for a larger one.

Go Solution

package main

func minOperations(nums []int, x int, y int) int {
	extra := x - y

	canFinish := func(operations int) bool {
		required := 0

		for _, num := range nums {
			remaining := num - operations*y

			if remaining > 0 {
				needed := (remaining + extra - 1) / extra
				required += needed

				if required > operations {
					return false
				}
			}
		}

		return required <= operations
	}

	left := 0
	right := 0

	for _, num := range nums {
		if num > right {
			right = num
		}
	}

	for left < right {
		mid := left + (right-left)/2

		if canFinish(mid) {
			right = mid
		} else {
			left = mid + 1
		}
	}

	return left
}

The Go implementation follows the exact same logic as the Python version.

One important detail is avoiding overflow patterns commonly associated with binary search. Instead of using:

mid := (left + right) / 2

the implementation uses:

mid := left + (right-left)/2

This is standard practice in Go and many systems languages.

Slices are used naturally for iterating through the input array, and integer arithmetic handles the ceiling division formula efficiently.

Worked Examples

Example 1

nums = [3,4,1,7,6]
x = 4
y = 2

We compute:

extra = x - y = 2

We binary search the answer.

Check k = 3

Every element automatically loses:

3 * 2 = 6
Element After Global Reduction Remaining Positive? Extra Selections Needed
3 -3 No 0
4 -2 No 0
1 -5 No 0
7 1 Yes 1
6 0 No 0

Total required targeted selections:

1

Since:

1 <= 3

three operations are feasible.

Check k = 2

Every element loses:

2 * 2 = 4
Element After Global Reduction Remaining Positive? Extra Selections Needed
3 -1 No 0
4 0 No 0
1 -3 No 0
7 3 Yes 2
6 2 Yes 1

Total required selections:

2 + 1 = 3

Since:

3 > 2

two operations are not feasible.

Therefore, the minimum answer is:

3

Example 2

nums = [1,2,1]
x = 2
y = 1

Compute:

extra = 1

Check k = 1

Every element loses:

1 * 1 = 1
Element After Global Reduction Remaining Positive? Extra Selections Needed
1 0 No 0
2 1 Yes 1
1 0 No 0

Total required selections:

1

Since:

1 <= 1

one operation is feasible.

Thus the answer is:

1

Complexity Analysis

Measure Complexity Explanation
Time O(n log M) Binary search performs O(log M) checks, each scanning the array once
Space O(1) Only constant extra variables are used

The feasibility check processes each element exactly once, giving O(n) work per check.

The binary search range is bounded by the maximum element value, so the number of iterations is O(log M), where M = max(nums).

Combining both gives:

O(n log M)

which easily fits the constraints.

Test Cases

from typing import List

class Solution:
    def minOperations(self, nums: List[int], x: int, y: int) -> int:
        extra = x - y

        def can_finish(operations: int) -> bool:
            required = 0

            for num in nums:
                remaining = num - operations * y

                if remaining > 0:
                    required += (remaining + extra - 1) // extra

                    if required > operations:
                        return False

            return True

        left = 0
        right = max(nums)

        while left < right:
            mid = (left + right) // 2

            if can_finish(mid):
                right = mid
            else:
                left = mid + 1

        return left

sol = Solution()

assert sol.minOperations([3, 4, 1, 7, 6], 4, 2) == 3  # provided example 1
assert sol.minOperations([1, 2, 1], 2, 1) == 1  # provided example 2

assert sol.minOperations([1], 2, 1) == 1  # single element
assert sol.minOperations([10], 10, 1) == 1  # exact reduction in one hit
assert sol.minOperations([1000000000], 1000000000, 1) == 1  # very large values

assert sol.minOperations([5, 5, 5], 6, 5) == 3  # global reduction almost enough
assert sol.minOperations([9, 9, 9], 10, 1) == 3  # balanced large reductions
assert sol.minOperations([100, 1, 1], 10, 1) == 10  # one dominant element

assert sol.minOperations([2, 2, 2], 3, 1) == 2  # all elements need multiple rounds
assert sol.minOperations([8, 4], 5, 2) == 3  # mixed requirements

assert sol.minOperations([1, 1, 1, 1], 2, 1) == 1  # all become zero together

Test Summary

Test Why
[3,4,1,7,6], x=4, y=2 Validates the primary sample
[1,2,1], x=2, y=1 Validates the second sample
[1] Tests single-element arrays
[10], x=10 Exact one-operation elimination
[1000000000] Large-value boundary case
[5,5,5], x=6, y=5 Tests strong global reduction
[9,9,9], x=10, y=1 Symmetric multi-element case
[100,1,1] One dominant element
[2,2,2], x=3, y=1 Requires repeated operations
[8,4], x=5, y=2 Mixed reduction behavior
[1,1,1,1] All elements become zero simultaneously

Edge Cases

Single Element Arrays

When the array contains only one element, the operation behaves differently because there are no "other" elements to reduce by y. A buggy implementation might incorrectly assume every operation always applies both reductions.

The mathematical formulation still works correctly because the chosen element effectively receives the full x reduction, which matches the problem definition.

For example:

nums = [10], x = 10, y = 1

One operation immediately reduces the value to zero.

Very Large Numbers

The constraints allow values up to 10^9. Any implementation that simulates operations directly will time out.

The binary search approach avoids this entirely because it only performs logarithmically many checks. Even for huge values, the algorithm remains efficient.

The implementation also uses integer arithmetic carefully to avoid floating-point precision issues.

Cases Where Global Reduction Is Enough

Sometimes repeated global reductions already eliminate all elements, meaning no extra targeted selections are necessary.

For example:

nums = [5,5,5]
x = 6
y = 5

After one operation:

[ -1, 0, 0 ]

depending on the chosen index.

A buggy solution might still try to assign targeted selections unnecessarily. The implementation correctly skips elements whose remaining value is already non-positive.

One Extremely Large Element

Arrays where one value is dramatically larger than the others are especially important.

For example:

nums = [100,1,1]

Most operations must target the large element. Incorrect greedy reasoning can fail here if it distributes operations poorly.

The feasibility formula handles this naturally by calculating exactly how many extra selections each element requires independently.