2702. Minimum Operations to Make Numbers Non-positive
Problem Description
You have an array of integers nums (0-indexed) and two integer values x and y. You can perform operations on this array to reduce all numbers to zero or below.
In each operation, you:
- Choose any index
ifrom the array (where0 <= i < nums.length) - Decrease the value at
nums[i]byx - Decrease the values at all other indices (every position except
i) byy
Your goal is to find the minimum number of operations needed to make every integer in nums less than or equal to zero.
For example, if you have nums = [10, 5, 8], x = 4, and y = 1, and you perform one operation choosing index 0:
nums[0]decreases by 4:10 - 4 = 6nums[1]decreases by 1:5 - 1 = 4nums[2]decreases by 1:8 - 1 = 7- Result after operation:
[6, 4, 7]
You would continue performing operations until all values become <= 0. The problem asks for the minimum number of such operations required.
Intuition
The key observation is that if we can make all numbers <= 0 in t operations, then we can definitely do it in t+1 operations or any number greater than t. This monotonic property suggests we can use binary search to find the minimum number of operations.
Let's think about what happens after t operations:
- Every number gets decreased by
yin each operation (since it's not chosen), so each number decreases by at leastt * y - Some numbers might need additional reduction beyond
t * yto become<= 0 - We can provide this extra reduction by choosing those specific indices during our
toperations
For a number nums[i] to become <= 0 after t operations:
- If
nums[i] <= t * y, it's already handled (since every position gets reduced byyin each operation) - If
nums[i] > t * y, we need extra reduction ofnums[i] - t * y - Each time we choose index
i, we get an extra reduction ofx - y(since we reduce byxinstead ofy) - So we need to choose index
iat leastceil((nums[i] - t * y) / (x - y))times
The crucial insight: if the total number of times we need to choose specific indices (sum of all required selections) is <= t, then t operations are sufficient. Otherwise, we need more operations.
This gives us a way to check if a given number of operations t is feasible, which we can use with binary search to find the minimum valid t.
Learn more about Binary Search patterns.
Solution Implementation
1class Solution:
2 def minOperations(self, nums: List[int], x: int, y: int) -> int:
3 """
4 Find minimum operations needed to reduce all elements to 0 or less.
5 Uses binary search template to find first feasible number of operations.
6 """
7
8 def feasible(time: int) -> bool:
9 """
10 Check if all elements can be reduced to 0 or less within given time.
11 Returns True if 'time' operations are sufficient, False otherwise.
12 """
13 targeted_operations_needed = 0
14
15 for value in nums:
16 # After 'time' operations, value becomes (value - time * y)
17 remaining_value = value - time * y
18
19 if remaining_value > 0:
20 # Each targeted operation reduces by (x - y) more than global
21 # Ceiling division: (a + b - 1) // b
22 operations_for_this_element = (remaining_value + (x - y) - 1) // (x - y)
23 targeted_operations_needed += operations_for_this_element
24
25 return targeted_operations_needed <= time
26
27 # Binary search template
28 left, right = 0, max(nums)
29 first_true_index = -1
30
31 while left <= right:
32 mid = (left + right) // 2
33
34 if feasible(mid):
35 first_true_index = mid
36 right = mid - 1 # Try to find smaller valid value
37 else:
38 left = mid + 1
39
40 return first_true_index
411class Solution {
2 private int[] nums;
3 private int x;
4 private int y;
5
6 public int minOperations(int[] nums, int x, int y) {
7 this.nums = nums;
8 this.x = x;
9 this.y = y;
10
11 // Find the maximum value for upper bound
12 int left = 0;
13 int right = 0;
14 for (int value : nums) {
15 right = Math.max(right, value);
16 }
17
18 // Binary search template
19 int firstTrueIndex = -1;
20
21 while (left <= right) {
22 int mid = left + (right - left) / 2;
23
24 if (feasible(mid)) {
25 firstTrueIndex = mid;
26 right = mid - 1; // Try to find smaller valid value
27 } else {
28 left = mid + 1;
29 }
30 }
31
32 return firstTrueIndex;
33 }
34
35 /**
36 * Check if operationCount operations are sufficient to reduce all elements.
37 */
38 private boolean feasible(int operationCount) {
39 long requiredExtraOperations = 0;
40
41 for (int value : nums) {
42 long remainingValue = value - (long) operationCount * y;
43
44 if (remainingValue > 0) {
45 // Ceiling division: (a + b - 1) / b
46 requiredExtraOperations += (remainingValue + x - y - 1) / (x - y);
47 }
48 }
49
50 return requiredExtraOperations <= operationCount;
51 }
52}
531class Solution {
2public:
3 int minOperations(vector<int>& nums, int x, int y) {
4 int left = 0;
5 int right = *max_element(nums.begin(), nums.end());
6 int firstTrueIndex = -1;
7
8 // Feasible function to check if 'time' operations are sufficient
9 auto feasible = [&](int time) {
10 long long operationsNeeded = 0;
11
12 for (int num : nums) {
13 if (num > 1LL * time * y) {
14 long long remaining = num - 1LL * time * y;
15 // Ceiling division: (a + b - 1) / b
16 operationsNeeded += (remaining + x - y - 1) / (x - y);
17 }
18 }
19
20 return operationsNeeded <= time;
21 };
22
23 // Binary search template
24 while (left <= right) {
25 int mid = left + (right - left) / 2;
26
27 if (feasible(mid)) {
28 firstTrueIndex = mid;
29 right = mid - 1; // Try to find smaller valid value
30 } else {
31 left = mid + 1;
32 }
33 }
34
35 return firstTrueIndex;
36 }
37};
381function minOperations(nums: number[], x: number, y: number): number {
2 let left = 0;
3 let right = Math.max(...nums);
4 let firstTrueIndex = -1;
5
6 // Feasible function to check if 'operations' are sufficient
7 const feasible = (operations: number): boolean => {
8 let selectionsNeeded = 0;
9
10 for (const value of nums) {
11 const remainingValue = value - operations * y;
12
13 if (remainingValue > 0) {
14 // Ceiling division
15 selectionsNeeded += Math.ceil(remainingValue / (x - y));
16 }
17 }
18
19 return selectionsNeeded <= operations;
20 };
21
22 // Binary search template
23 while (left <= right) {
24 const mid = Math.floor((left + right) / 2);
25
26 if (feasible(mid)) {
27 firstTrueIndex = mid;
28 right = mid - 1; // Try to find smaller valid value
29 } else {
30 left = mid + 1;
31 }
32 }
33
34 return firstTrueIndex;
35}
36Solution Approach
We implement a binary search solution to find the minimum number of operations needed. The solution follows the standard binary search template for finding the first value where a condition becomes true.
Feasible Function:
The feasible(t) function determines if t operations are sufficient to make all numbers <= 0:
-
For each number
vinnums:- After
toperations, every number is reduced by at leastt * y - If
v > t * y, the remaining amountv - t * ymust be eliminated by choosing this index - When we choose an index, we get extra reduction of
x - yper operation - So we need
ceil((v - t * y) / (x - y))operations where this index is chosen
- After
-
Count the total number of times we need to choose specific indices:
cnt = 0 for v in nums: if v > t * y: cnt += ceil((v - t * y) / (x - y)) -
If
cnt <= t, thentoperations are sufficient (returnTrue), otherwise we need more operations (returnFalse)
Binary Search Template:
left, right = 0, max(nums)
first_true_index = -1
while left <= right:
mid = (left + right) // 2
if feasible(mid):
first_true_index = mid
right = mid - 1 # Try to find smaller valid value
else:
left = mid + 1
return first_true_index
The template tracks first_true_index to store the answer when the feasible condition is met. Since we're looking for the minimum operations where feasible(t) becomes true, we update first_true_index = mid when feasible and continue searching left for a potentially smaller value. The algorithm efficiently narrows down the search space by halving it in each iteration, resulting in O(n * log(max(nums))) time complexity.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Example: nums = [5, 3], x = 3, y = 1
Step 1: Set up binary search with template
left = 0(minimum possible operations)right = max(nums) = 5(maximum operations we might need)first_true_index = -1(tracks our answer)
Step 2: Binary search iterations using while left <= right
Iteration 1: left = 0, right = 5
mid = (0 + 5) // 2 = 2- Check if
feasible(2):- For
nums[0] = 5: remaining =5 - 2*1 = 3, needceil(3/2) = 2selections - For
nums[1] = 3: remaining =3 - 2*1 = 1, needceil(1/2) = 1selection - Total selections needed:
2 + 1 = 3 > 2→ Not feasible
- For
feasible(2) = False, soleft = mid + 1 = 3
Iteration 2: left = 3, right = 5
mid = (3 + 5) // 2 = 4- Check if
feasible(4):- For
nums[0] = 5: remaining =5 - 4*1 = 1, needceil(1/2) = 1selection - For
nums[1] = 3: remaining =3 - 4*1 = -1 <= 0, need 0 selections - Total selections needed:
1 <= 4→ Feasible!
- For
feasible(4) = True, sofirst_true_index = 4,right = mid - 1 = 3
Iteration 3: left = 3, right = 3
mid = (3 + 3) // 2 = 3- Check if
feasible(3):- For
nums[0] = 5: remaining =5 - 3*1 = 2, needceil(2/2) = 1selection - For
nums[1] = 3: remaining =3 - 3*1 = 0 <= 0, need 0 selections - Total selections needed:
1 <= 3→ Feasible!
- For
feasible(3) = True, sofirst_true_index = 3,right = mid - 1 = 2
Iteration 4: left = 3, right = 2
left > right, loop terminates
Step 3: Return result
first_true_index = 3, so the minimum number of operations is 3
Verification:
- Operation 1: Choose index 0 →
[5-3, 3-1] = [2, 2] - Operation 2: Choose index 0 →
[2-3, 2-1] = [-1, 1] - Operation 3: Choose index 1 →
[-1-1, 1-3] = [-2, -2]
All values are now <= 0, confirming that 3 operations are sufficient and minimal.
Time and Space Complexity
Time Complexity: O(n * log(max(nums)))
The algorithm uses binary search on the range [0, max(nums)], which requires O(log(max(nums))) iterations. In each iteration, the check function is called, which iterates through all n elements in the nums array to calculate the required operations. Each element requires O(1) time for the arithmetic operations and ceiling division. Therefore, the overall time complexity is O(n * log(max(nums))).
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. The variables l, r, mid, t, cnt, and v are all scalar values that don't depend on the input size. The check function doesn't use any additional data structures that scale with input size. Therefore, the space complexity is O(1).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
A common mistake is using the while left < right with right = mid pattern instead of the standard template.
Incorrect Implementation:
left, right = 0, max(nums)
while left < right:
mid = (left + right) // 2
if feasible(mid):
right = mid
else:
left = mid + 1
return left
Correct Implementation (Template-compliant):
left, right = 0, max(nums)
first_true_index = -1
while left <= right:
mid = (left + right) // 2
if feasible(mid):
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
Why the template matters: The standard template explicitly tracks the answer with first_true_index, uses while left <= right, and right = mid - 1 when feasible. This provides clearer semantics and handles edge cases more predictably.
2. Integer Division vs Ceiling Division Error
Another common mistake is incorrectly calculating the ceiling division when determining how many targeted operations are needed for each element.
Incorrect Implementation:
operations_for_this_element = remaining_value // (x - y) # Wrong! This is floor division
Why it's wrong: When we have a remaining value that isn't perfectly divisible by (x - y), floor division will underestimate the operations needed. For example, if remaining_value = 5 and (x - y) = 2, we need 3 operations (not 2).
Correct Implementation:
# Method 1: Using math.ceil import math operations_for_this_element = math.ceil(remaining_value / (x - y)) # Method 2: Manual ceiling division (avoiding floating point) operations_for_this_element = (remaining_value + (x - y) - 1) // (x - y)
3. Incorrect Binary Search Boundary
Setting the wrong upper bound for binary search can lead to incorrect results or unnecessary iterations.
Incorrect Implementation:
right = sum(nums) # Wrong! This could be unnecessarily large
Why it's wrong: The maximum operations needed is when we only use global operations (reducing by y), which would be max(nums) // y + 1. Using sum(nums) as the upper bound works but is inefficient.
Correct Implementation:
right = max(nums) # Sufficient upper bound
# Or more precisely:
right = (max(nums) + y - 1) // y # Exact maximum operations needed
4. Edge Case: When x equals y
If x == y, the problem becomes trivial but the division by (x - y) would cause a division by zero error.
Solution:
def minOperations(self, nums: List[int], x: int, y: int) -> int:
if x == y:
# All operations have the same effect
return (max(nums) + x - 1) // x
# Continue with binary search approach...
5. Overflow in Ceiling Division Formula
When using the manual ceiling division formula (a + b - 1) // b, the addition might cause integer overflow in languages with fixed integer sizes.
Solution for Python: Python handles arbitrary precision integers, so this isn't an issue. However, if implementing in other languages:
# Alternative ceiling division without overflow risk operations_for_this_element = remaining_value // (x - y) if remaining_value % (x - y) != 0: operations_for_this_element += 1
How does quick sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!