Facebook Pixel

3495. Minimum Operations to Make Array Elements Zero

HardBit ManipulationArrayMath
LeetCode ↗

Problem Description

You are given a 2D array queries, where queries[i] is of the form [l, r]. Each queries[i] defines an array of integers nums consisting of elements ranging from l to r, both inclusive.

In one operation, you can:

  • Select two integers a and b from the array.
  • Replace them with floor(a / 4) and floor(b / 4).

Your task is to determine the minimum number of operations required to reduce all elements of the array to zero for each query. Return the sum of the results for all queries.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Math / Bit Manipulation?

This problem maps to Math / Bit Manipulation through a short path in the full flowchart.

Math orbittricks?yesNumbertheory(gcd,noMath / BitManipulation

The problem involves counting operations where each division by 4 reduces a number, with the total work bounded by ceil(sum/2) and the max single-element cost.

Open in Flowchart

Intuition

Let's start by understanding how many operations a single element needs. Each operation divides a number by 4 (taking the floor). So for an element x, the number of times we must divide it by 4 before it reaches 0 is the smallest integer p such that 4^p > x. For example, numbers in [1, 3] need 1 operation, numbers in [4, 15] need 2 operations, numbers in [16, 63] need 3 operations, and so on. In general, numbers in the range [4^(p-1), 4^p - 1] each require p operations.

Now here's the key twist: each operation processes two elements at once (we pick a and b and divide both). This means the total "work" to be done, measured as the sum of individual operations needed across all elements, can be shared two-at-a-time.

Let s be the total number of single-element divisions needed across all elements in [l, r]. Since each operation handles two divisions simultaneously, in the best case we could finish in about s / 2 operations, that is ceil(s / 2) operations. However, there's a constraint: a single element still needs its full count of operations performed on it one at a time. So if one element requires mx operations, we cannot finish in fewer than mx operations no matter how we pair things. Therefore the answer for a range is max(ceil(s / 2), mx).

Within the range [l, r], the largest element is r, and since the required operation count grows with the value, r needs the most operations. So mx equals the number of operations needed just for element r.

To compute s efficiently for many queries, we define f(x) as the sum of operation counts for all elements in [1, x]. We can build this by walking through each "bucket" [4^(p-1), 4^p - 1], counting how many elements fall in that bucket and multiplying by the per-element cost p. Then for any query, s = f(r) - f(l - 1), and the per-element count for r is mx = f(r) - f(r - 1). Summing max(ceil(s / 2), mx) over all queries gives the final answer.

Pattern Learn more about Math patterns.

Solution Approach

We use a prefix sum idea built on top of a closed-form helper function.

Step 1: Define the helper function f(x).

f(x) returns the sum of the minimum operations needed for every element in the range [1, x]. We compute it by iterating over the "buckets" of values that share the same operation count:

  • We maintain p, the starting value of the current bucket, beginning at p = 1 (which is 4^0).
  • We maintain i, the per-element operation count for the current bucket, starting at i = 1.
  • The current bucket covers values [p, 4*p - 1], but we clamp the upper end to x so we don't count beyond x. The number of elements in this bucket is cnt = min(p * 4 - 1, x) - p + 1.
  • We add cnt * i to the running result, since each of those cnt elements costs i operations.
  • We then advance to the next bucket by multiplying p *= 4 and incrementing i += 1.
  • The loop continues while p <= x.
def f(x: int) -> int:
    res = 0
    p = i = 1
    while p <= x:
        cnt = min(p * 4 - 1, x) - p + 1
        res += cnt * i
        i += 1
        p *= 4
    return res

Step 2: Process each query.

For each query [l, r]:

  • Compute s = f(r) - f(l - 1). This gives the total number of single-element divisions required across all elements in [l, r].
  • Compute mx = f(r) - f(r - 1). This isolates the operation count of just element r, which is the largest element and therefore needs the most operations.
  • The answer for this query is max((s + 1) // 2, mx), where (s + 1) // 2 is the integer form of ceil(s / 2) (since two divisions can be handled per operation), and mx is the lower bound forced by the single hardest element.

We accumulate these values across all queries into ans and return it.

ans = 0
for l, r in queries:
    s = f(r) - f(l - 1)
    mx = f(r) - f(r - 1)
    ans += max((s + 1) // 2, mx)
return ans

Complexity Analysis:

  • Time complexity: Each call to f(x) runs in O(log x) time because the bucket value p quadruples each iteration. With q queries and a maximum value r, the overall time is O(q * log r).
  • Space complexity: O(1), as only a constant number of variables are used.

Example Walkthrough

Let's trace through a small example with queries = [[1, 5], [2, 4]].

Setup: Understanding f(x)

Recall f(x) sums the per-element operation counts for all values in [1, x]. The buckets are:

  • Bucket [1, 3] → each element costs 1 operation
  • Bucket [4, 15] → each element costs 2 operations
  • Bucket [16, 63] → each element costs 3 operations

Let's precompute the values we'll need by walking the buckets.

Computing f(5):

IterationpiBucket [p, 4p-1] clamped to 5cnt = min(4p-1, 5) - p + 1Contribution cnt * i
111[1, 3]3 - 1 + 1 = 33 * 1 = 3
242[4, 5] (clamped from [4,15])5 - 4 + 1 = 22 * 2 = 4

Now p = 16 > 5, so we stop. f(5) = 3 + 4 = 7.

This makes sense: values 1, 2, 3 cost 1 each (3 total), and 4, 5 cost 2 each (4 total).

Computing the other needed values (using the same bucket logic):

  • f(0) = 0 (empty range)
  • f(1) = 1 (just value 1, costs 1)
  • f(3) = 3 (values 1,2,3, each costs 1)
  • f(4) = 3 + 2 = 5 (values 1,2,3 cost 1 each, value 4 costs 2)

Query 1: [1, 5]

The array is nums = [1, 2, 3, 4, 5]. Per-element costs are [1, 1, 1, 2, 2].

  • s = f(5) - f(0) = 7 - 0 = 7 → total single-element divisions needed
  • mx = f(5) - f(4) = 7 - 5 = 2 → cost of the largest element 5
  • answer = max(ceil(7 / 2), 2) = max(4, 2) = 4

Why 4? We have 7 divisions to perform, and each operation handles 2 of them. That requires at least ceil(7/2) = 4 operations. The hardest single element (5) only needs 2, which isn't the binding constraint here. So 4 operations.


Query 2: [2, 4]

The array is nums = [2, 3, 4]. Per-element costs are [1, 1, 2].

  • s = f(4) - f(1) = 5 - 1 = 4 → total single-element divisions needed
  • mx = f(4) - f(3) = 5 - 3 = 2 → cost of the largest element 4
  • answer = max(ceil(4 / 2), 2) = max(2, 2) = 2

Why 2? We have 4 divisions, handled 2 at a time → 2 operations. We can verify by hand: pair (4, 2)(1, 0), then pair (3, 1)(0, 0), and the 0s stay 0. That's exactly 2 operations, and all elements reach zero.


Final Result

Sum the per-query answers:

ans = 4 + 2 = 6

The walkthrough shows the two competing bounds in action: in Query 1 the shared-work bound ceil(s/2) dominates, while in Query 2 both bounds happen to tie. Taking max of the two guarantees we never undercount.

Solution Implementation

1from typing import List
2
3
4class Solution:
5    def minOperations(self, queries: List[List[int]]) -> int:
6        def total_operations(x: int) -> int:
7            """
8            Compute the sum of operations needed to reduce every integer
9            in the range [1, x] down to 0, where each integer n requires
10            floor(log4(n)) + 1 single-divisions.
11
12            Numbers in the range [4^(level-1), 4^level - 1] each need
13            'level' divisions to reach 0.
14            """
15            result = 0
16            power = level = 1  # power = 4^(level-1), level = number of ops for this band
17            while power <= x:
18                # Count how many integers in [power, 4*power - 1] are <= x
19                count = min(power * 4 - 1, x) - power + 1
20                result += count * level
21                level += 1
22                power *= 4
23            return result
24
25        answer = 0
26        for left, right in queries:
27            # Total single-division cost for all numbers in [left, right]
28            single_cost = total_operations(right) - total_operations(left - 1)
29            # Maximum operations any single number needs (the largest value, 'right')
30            max_cost = total_operations(right) - total_operations(right - 1)
31            # Each operation processes two numbers, so we need ceil(single_cost / 2),
32            # but never fewer than the worst single number's requirement.
33            answer += max((single_cost + 1) // 2, max_cost)
34        return answer
35
1class Solution {
2    /**
3     * For each query [l, r], we consider all integers in the range [l, r].
4     * Each integer x requires a certain number of operations to reduce it to 0,
5     * where each operation divides the number by 4 (the operation count equals
6     * floor(log4(x)) + 1, i.e., the "level" of x in powers of 4).
7     *
8     * Since one operation can act on two numbers at once, the minimum number of
9     * operations for a single query is max(ceil(totalSteps / 2), maxSteps),
10     * where totalSteps is the sum of steps over all numbers in [l, r], and
11     * maxSteps is the largest step count among numbers in [l, r].
12     */
13    public long minOperations(int[][] queries) {
14        long answer = 0;
15        for (int[] query : queries) {
16            int left = query[0], right = query[1];
17
18            // Total number of single-element reduction steps for the range [left, right].
19            long totalSteps = countSteps(right) - countSteps(left - 1);
20
21            // Maximum step count in the range, which equals the step count of the
22            // single largest number 'right' (since steps are non-decreasing with value).
23            long maxSteps = countSteps(right) - countSteps(right - 1);
24
25            // Each operation handles two numbers, so we need at least ceil(totalSteps / 2)
26            // operations; but we also need at least maxSteps operations to fully reduce
27            // the largest number.
28            answer += Math.max((totalSteps + 1) / 2, maxSteps);
29        }
30        return answer;
31    }
32
33    /**
34     * Computes the prefix sum of step counts for all integers in [1, x].
35     *
36     * A number n belongs to "level" i (1-indexed) if 4^(i-1) <= n <= 4^i - 1,
37     * meaning it takes i operations to reduce n to 0 by repeated division by 4.
38     *
39     * For each level i, this method counts how many numbers in [1, x] fall into
40     * that level and accumulates (count * i) into the result.
41     *
42     * @param x the upper bound of the range [1, x]
43     * @return the sum of step counts for all integers from 1 to x
44     */
45    private long countSteps(long x) {
46        long result = 0;
47        long power = 1; // power = 4^(i-1), the lower bound of the current level
48        int level = 1;  // current level index (number of operations for this level)
49
50        while (power <= x) {
51            // Upper bound of the current level is (4^i - 1) = power * 4 - 1,
52            // clamped by x. Lower bound is 'power'.
53            long count = Math.min(power * 4 - 1, x) - power + 1;
54            result += count * level;
55
56            level++;
57            power *= 4;
58        }
59        return result;
60    }
61}
62
1class Solution {
2public:
3    long long minOperations(vector<vector<int>>& queries) {
4        // f(x) returns the total number of divide-by-4 operations required
5        // to reduce every integer in the range [1, x] down to 0.
6        // A value v needs (floor(log4(v)) + 1) operations.
7        // All values in [4^(i-1), 4^i - 1] share the same cost i.
8        auto f = [&](long long x) -> long long {
9            long long total = 0;  // accumulated operation count
10            long long power = 1;  // current block start, equals 4^(i-1)
11            int cost = 1;         // number of operations for values in this block
12            while (power <= x) {
13                // Right boundary of the current block, clamped to x.
14                long long blockEnd = min(power * 4 - 1, x);
15                // Count of integers in this block within [1, x].
16                long long count = blockEnd - power + 1;
17                total += count * cost;
18                cost++;
19                power *= 4;  // move to the next block: 4^i
20            }
21            return total;
22        };
23
24        long long ans = 0;
25        for (auto& query : queries) {
26            int left = query[0], right = query[1];
27
28            // Total operations needed for the whole range [left, right].
29            long long sum = f(right) - f(left - 1);
30
31            // Operations needed by the single largest element 'right'.
32            long long maxCost = f(right) - f(right - 1);
33
34            // Each operation processes two numbers, so we need at least
35            // ceil(sum / 2) operations; but also at least maxCost since one
36            // element cannot be sped up by pairing.
37            ans += max((sum + 1) / 2, maxCost);
38        }
39        return ans;
40    }
41};
42
1/**
2 * Computes the total number of operations required to reduce every integer
3 * in the range [1, x] down to zero, where each operation divides a value by 4
4 * (using floor division). A number n located in the bucket [4^(i-1), 4^i - 1]
5 * needs exactly i operations to reach zero.
6 *
7 * @param x - The upper bound of the range [1, x].
8 * @returns The total operation count for all integers in [1, x].
9 */
10const f = (x: number): number => {
11    let result = 0;
12    let power = 1; // Represents 4^(i-1), the lower bound of the current bucket.
13    let steps = 1; // Number of operations needed for numbers in the current bucket.
14
15    while (power <= x) {
16        // Count how many integers fall within the current bucket [power, 4*power - 1],
17        // capped by x so we never count beyond the requested range.
18        const count = Math.min(power * 4 - 1, x) - power + 1;
19        result += count * steps;
20        steps++;
21        power *= 4; // Move to the next bucket.
22    }
23
24    return result;
25};
26
27/**
28 * For each query [l, r], determines the minimum number of operations to reduce
29 * all integers in [l, r] to zero. Each operation can divide up to two numbers
30 * by 4 simultaneously, so the answer is bounded below by:
31 *   - ceil(totalOperations / 2): because two numbers are handled per operation.
32 *   - maxOperations: the hardest single number still needs its full step count.
33 *
34 * @param queries - A list of [l, r] ranges.
35 * @returns The accumulated minimum operations across all queries.
36 */
37function minOperations(queries: number[][]): number {
38    let ans = 0;
39
40    for (const [l, r] of queries) {
41        // Total operations needed for all integers in [l, r].
42        const sum = f(r) - f(l - 1);
43        // Operations needed for the single hardest number, which is r.
44        const max = f(r) - f(r - 1);
45        // Pair up operations (two per step) but never go below the hardest element.
46        ans += Math.max(Math.ceil(sum / 2), max);
47    }
48
49    return ans;
50}
51

Time and Space Complexity

Time Complexity: O(q × log M)

The code processes each query in the queries list, where q is the number of queries. For each query [l, r], it performs the following operations:

  • It calls the helper function f three times: f(r), f(l - 1), f(r - 1).
  • Inside f(x), the while loop variable p starts at 1 and is multiplied by 4 each iteration (p *= 4), continuing while p <= x. This means the loop runs approximately log₄(x) times, which is O(log x).

Since x is bounded by the maximum value M in the query range, each call to f takes O(log M) time. With three calls per query and q queries total, the overall time complexity is O(q × log M).

Space Complexity: O(1)

The function f uses only a constant number of variables (res, p, i, cnt). The main loop uses the constant-space accumulator ans along with s and mx. No additional data structures that scale with input size are allocated, so the space complexity is O(1).

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Using ceil(single_cost / 2) as the only answer and ignoring the single-element lower bound

The most tempting mistake is to reason that since each operation processes two numbers, the answer is simply ceil(single_cost / 2). This overlooks a critical constraint.

Why it's wrong:

An operation must select two distinct elements and divides both. Consider a query where one element (the largest, r) needs many divisions but every other element needs very few. You cannot "pair up" the hardest element with itself — each operation on it must also waste a division on some other element that may already be at zero. Once all the smaller elements hit zero, the hardest element still must be paired with a zero each step.

Example:

Take the range [1, 100]. The element 100 needs 4 divisions (100 → 25 → 6 → 1 → 0), while many small elements need only 1. Suppose single_cost is small enough that ceil(single_cost / 2) < 4. You would return a number smaller than 4, but it's physically impossible to finish in fewer steps than the 4 operations forced by element 100.

Solution: Always take the maximum of the two bounds:

answer += max((single_cost + 1) // 2, max_cost)

This max_cost = total_operations(right) - total_operations(right - 1) isolates the operation count of the single hardest element and enforces it as a floor.


Pitfall 2: Off-by-one errors in the prefix-sum subtraction

When computing the cost over [left, right], it's easy to write total_operations(right) - total_operations(left) instead of total_operations(left - 1).

Why it's wrong:

total_operations(left) already includes the cost of the element left, so subtracting it excludes left from the range, giving the cost of [left + 1, right]. Since the range is inclusive of left, you must subtract total_operations(left - 1).

Solution:

single_cost = total_operations(right) - total_operations(left - 1)

Note that total_operations(0) correctly returns 0 because the loop condition power <= x (i.e., 1 <= 0) is immediately false, so the left = 1 edge case is handled safely.


Pitfall 3: Incorrect integer ceiling computation

Writing single_cost // 2 (floor division) instead of (single_cost + 1) // 2 undercounts when single_cost is odd.

Why it's wrong:

If single_cost = 7, you genuinely need ceil(7 / 2) = 4 operations, but 7 // 2 = 3. The leftover single division still requires a full operation (paired with a zero element).

Solution: Use the standard integer-ceiling idiom:

(single_cost + 1) // 2   # equals ceil(single_cost / 2)

Avoid math.ceil(single_cost / 2), which converts to floating point and can produce rounding errors for very large single_cost values.


Pitfall 4: Miscomputing the bucket boundaries in total_operations

A subtle error is forgetting to clamp the bucket's upper bound to x:

count = power * 4 - 1 - power + 1   # WRONG: counts beyond x

Why it's wrong:

The final (highest) bucket usually extends past x. Without clamping, you count values larger than x that don't exist in the range, inflating the result.

Solution: Always clamp with min:

count = min(power * 4 - 1, x) - power + 1

This ensures the last partial bucket contributes only the elements that are actually ≤ x.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More