Facebook Pixel

3806. Maximum Bitwise AND After Increment Operations

HardGreedyBit ManipulationArraySorting
LeetCode ↗

Problem Description

You are given an integer array nums and two integers k and m.

You may perform at most k operations. In one operation, you may choose any index i and increase nums[i] by 1.

Your task is to return an integer denoting the maximum possible bitwise AND of any subset of size m after performing up to k operations optimally.

In other words, you want to select a subset of exactly m elements from nums, and you are allowed to spend up to k total increment operations distributed across the array's elements however you like. After applying these operations, you compute the bitwise AND of the chosen m elements. Among all possible choices of subsets and all possible ways of distributing the operations, you should find the largest bitwise AND value that can be achieved.

For example, the bitwise AND of a subset has a particular bit set to 1 only if every element in that subset has that bit set to 1. By increasing certain elements, you may be able to "turn on" higher bits across enough elements so that the resulting bitwise AND becomes larger. The goal is to maximize this final AND value while staying within the budget of k operations.

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

How We Pick the Algorithm

Why Greedy Algorithms?

This problem maps to Greedy Algorithms through a short path in the full flowchart.

Computemax/min?yesGreedysolution?yesGreedyAlgorithms

Choosing which bits to set for maximum result follows a greedy strategy.

Open in Flowchart

Intuition

The key observation is that the bitwise AND result is dominated by its higher bits. A single high bit contributes more to the final value than all lower bits combined. For example, having bit 3 set (value 8) is worth more than having bits 0, 1, and 2 all set (value 7). This naturally suggests a greedy strategy: we should try to secure the highest bits first, and only then worry about lower bits.

So we build the answer bit by bit, from the highest bit down to the lowest. At each step, we maintain a current candidate answer target that represents the set of bits we have already committed to. We then ask: "Can we also turn on this next bit while keeping all the bits we already locked in?" To check this, we form a tentative value target = ans | (1 << bit) and test whether it is achievable within the budget k.

Why is the greedy order safe? Because once we successfully lock in a high bit, no combination of lower bits can ever surpass it. So committing to a high bit whenever possible never hurts us — it always leads to the maximum possible result.

The next piece is figuring out the cost to make a single element's bitwise AND contribution match target. For the AND of a subset to have all the bits in target set, every chosen element must already have those bits set after the increments. For one element x, we need to increase it just enough so that it covers all the 1 bits of target.

Here comes a clever detail. We look for the highest bit position where target has a 1 but x has a 0. Call this position j - 1. Above this position, x already matches target (or has the necessary bits), so we don't need to touch those. To satisfy the bit at position j - 1, we only need to raise the lower j bits of x up to the lower j bits of target. The cheapest way to do this is to simply set the lower portion of x equal to the lower portion of target, which costs (target & mask) - (x & mask) where mask = (1 << j) - 1. This works because incrementing carries propagate upward, but matching the lower j bits exactly is precisely enough to flip the deficient bit into place.

Once we know the cost for every element to reach target, we want to pick the m elements that are cheapest to upgrade — since the subset only needs m members. So we sort the costs and sum the smallest m of them. If that total is within k, the bit is affordable, and we permanently add it to ans. Otherwise, we drop this bit and move on to the next lower one.

By repeating this process across all bits, we greedily accumulate the maximum achievable bitwise AND, always favoring higher bits and always choosing the m elements that are cheapest to satisfy.

Pattern Learn more about Greedy and Sorting patterns.

Solution Approach

We use Greedy Bit Construction combined with Bit Manipulation. The implementation builds the answer from the highest bit down to the lowest, testing one bit at a time.

Step 1: Determine the bit range.

The largest value any element can possibly reach is max(nums) + k, since k is the total budget of increments. So the highest meaningful bit is bounded by:

mx = (max(nums) + k).bit_length()

We will iterate bit from mx - 1 down to 0.

Step 2: Maintain the running answer.

We keep a variable ans, initialized to 0, which holds all the bits we have already successfully committed to. We also pre-allocate a cost array of length len(nums) to reuse across iterations:

ans = 0
cost = [0] * len(nums)

Step 3: Try to add the current bit.

For each bit from high to low, we form a tentative target by turning on this bit on top of ans:

target = ans | (1 << bit)

This target is the set of bits we would like every chosen element to cover.

Step 4: Compute the cost for each element.

For each element x, we find the highest bit position where target has a 1 but x has a 0. This is computed with:

j = (target & ~x).bit_length()

Here target & ~x isolates the bits that target requires but x is missing. Its bit_length() gives the position j just above the highest deficient bit. We then only need to fix the lower j bits of x:

mask = (1 << j) - 1
cost[i] = (target & mask) - (x & mask)

The mask extracts the lower j bits. Raising the lower part of x to match the lower part of target costs exactly (target & mask) - (x & mask). If x already covers all required bits, then target & ~x is 0, j is 0, the mask is 0, and the cost is 0 — meaning no operations are needed.

Step 5: Select the cheapest m elements.

Since the subset only needs m members, we want the m elements that are cheapest to upgrade. We sort the cost array and sum the smallest m values:

cost.sort()
if sum(cost[:m]) <= k:
    ans = target

If this total cost stays within the budget k, the bit is affordable, so we permanently commit to it by updating ans = target. Otherwise, we leave ans unchanged and move on to the next lower bit.

Step 6: Return the result.

After processing all bits, ans holds the maximum achievable bitwise AND:

return ans

Complexity Analysis.

  • Let n be the length of nums and B be the number of bits (mx).
  • For each of the B bits, we compute the cost for all n elements in O(n) time and sort them in O(n log n) time.
  • The overall time complexity is O(B × n log n).
  • The space complexity is O(n) for the cost array.

The greedy correctness holds because securing a higher bit always outweighs any gain from lower bits, so committing to each affordable high bit in order guarantees the maximum final value.

Example Walkthrough

Let's trace through the solution with a small, concrete example.

Input: nums = [2, 5, 6], k = 3, m = 2

We want to select 2 elements and maximize their bitwise AND, using at most 3 increment operations.


Step 1: Determine the bit range.

max(nums) = 6, so max(nums) + k = 9
9 in binary = 1001 → bit_length() = 4

So mx = 4, and we iterate bit from 3 down to 0. Initialize ans = 0.

Binary forms for reference:

nums[0] = 2 = 0010
nums[1] = 5 = 0101
nums[2] = 6 = 0110

Step 2: Try bit = 3 (value 8).

target = ans | (1 << 3) = 0 | 1000 = 1000  (= 8)

Compute cost for each element to cover bit 3:

xx bintarget & ~xj = bit_lengthmask = (1<<j)-1cost = (target&mask)-(x&mask)
20010100041111(1000 & 1111) - (0010 & 1111) = 8 - 2 = 6
501011000411118 - 5 = 3
601101000411118 - 6 = 2
cost = [6, 3, 2] → sorted = [2, 3, 6]
sum of cheapest m=2 = 2 + 3 = 5

Is 5 <= k (3)? No. Drop bit 3. ans stays 0.


Step 3: Try bit = 2 (value 4).

target = 0 | (1 << 2) = 0100  (= 4)
xx bintarget & ~xjmaskcost
20010010030111(0100 & 0111) - (0010 & 0111) = 4 - 2 = 2
501010000000 (already has bit 2)
601100000000 (already has bit 2)
cost = [2, 0, 0] → sorted = [0, 0, 2]
sum of cheapest m=2 = 0 + 0 = 0

Is 0 <= k (3)? Yes. Commit bit 2. ans = 0100 (= 4).

(We pick elements 5 and 6, which already cover bit 2 — zero cost.)


Step 4: Try bit = 1 (value 2).

target = ans | (1 << 1) = 0100 | 0010 = 0110  (= 6)

Now every chosen element must cover bits 2 and 1.

xx bintarget & ~xjmaskcost
20010010030111(0110 & 0111) - (0010 & 0111) = 6 - 2 = 4
50101001020011(0110 & 0011) - (0101 & 0011) = 2 - 1 = 1
601100000000
cost = [4, 1, 0] → sorted = [0, 1, 4]
sum of cheapest m=2 = 0 + 1 = 1

Is 1 <= k (3)? Yes. Commit bit 1. ans = 0110 (= 6).

(We pick element 6 for free and upgrade element 5 from 5 to 6 at cost 1.)


Step 5: Try bit = 0 (value 1).

target = ans | (1 << 0) = 0110 | 0001 = 0111  (= 7)

Every chosen element must cover bits 2, 1, and 0.

xx bintarget & ~xjmaskcost
20010010130111(0111 & 0111) - (0010 & 0111) = 7 - 2 = 5
50101001020011(0111 & 0011) - (0101 & 0011) = 3 - 1 = 2
60110000110001(0111 & 0001) - (0110 & 0001) = 1 - 0 = 1
cost = [5, 2, 1] → sorted = [1, 2, 5]
sum of cheapest m=2 = 1 + 2 = 3

Is 3 <= k (3)? Yes. Commit bit 0. ans = 0111 (= 7).

(Upgrade element 6 → 7 at cost 1, and element 5 → 7 at cost 2, total 3 operations — exactly the budget.)


Step 6: Return the result.

All bits processed. ans = 7.

Verification: Pick elements 5 and 6. Spend 2 ops on 5 → 7 and 1 op on 6 → 7 (total 3 ops, within budget). Then:

7 AND 7 = 7

The maximum achievable bitwise AND is 7, which matches our greedy result. Notice how the algorithm wisely skipped the tempting high bit 3 (too expensive at cost 5) and instead accumulated bits 2, 1, and 0 to reach a value of 7 within budget.

Solution Implementation

1from typing import List
2
3
4class Solution:
5    def maximumAND(self, nums: List[int], k: int, m: int) -> int:
6        # Determine the highest bit we may need to consider.
7        # The largest achievable value is at most max(nums) + k,
8        # so its bit length tells us the topmost relevant bit.
9        max_bits = (max(nums) + k).bit_length()
10
11        # 'answer' is built greedily from the most significant bit downward.
12        answer = 0
13
14        # Reusable buffer holding the cost to raise each number so that
15        # it matches all bits currently set in the candidate target.
16        costs = [0] * len(nums)
17
18        # Try to turn on each bit from high to low.
19        for bit in range(max_bits - 1, -1, -1):
20            # Candidate value: current answer with this extra bit set.
21            target = answer | (1 << bit)
22
23            for index, value in enumerate(nums):
24                # Find the lowest bit position where 'target' requires a 1
25                # but 'value' currently has a 0. Bits above this position
26                # are already aligned, so we only need to "fill up" the
27                # suffix below 'highest_missing_bit'.
28                highest_missing_bit = (target & ~value).bit_length()
29
30                # Mask covering all bit positions below 'highest_missing_bit'.
31                suffix_mask = (1 << highest_missing_bit) - 1
32
33                # Cost to increment 'value' so that its lower suffix equals
34                # the target's lower suffix. Because increments can only add,
35                # we compute the numeric difference on the suffix part.
36                costs[index] = (target & suffix_mask) - (value & suffix_mask)
37
38            # Choose the 'm' cheapest numbers to satisfy this candidate target.
39            costs.sort()
40
41            # If the total cost for the cheapest m numbers fits the budget,
42            # commit to this bit being set.
43            if sum(costs[:m]) <= k:
44                answer = target
45
46        return answer
47
1class Solution {
2    public int maximumAND(int[] nums, int k, int m) {
3        // Find the maximum value in the array to determine the bit range we need to examine.
4        int maxValue = 0;
5        for (int x : nums) {
6            maxValue = Math.max(maxValue, x);
7        }
8        // Account for the budget k, since increments could push values higher.
9        maxValue += k;
10
11        // Determine the number of relevant bits (highest set bit position + 1).
12        int maxBits = 32 - Integer.numberOfLeadingZeros(maxValue);
13        int n = nums.length;
14
15        // 'answer' is built greedily from the most significant bit downward.
16        int answer = 0;
17        // 'cost' holds the cost for each element to reach the candidate target value.
18        int[] cost = new int[n];
19
20        // Greedily try to set each bit from the highest position to the lowest.
21        for (int bit = maxBits - 1; bit >= 0; bit--) {
22            // Candidate target: the current answer with the current bit turned on.
23            int target = answer | (1 << bit);
24
25            for (int i = 0; i < n; i++) {
26                int x = nums[i];
27                // 'diff' contains the bits required by target that x does not yet have.
28                int diff = target & ~x;
29                // 'j' is the position just above the highest missing bit (0 if nothing missing).
30                int j = diff == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(diff);
31                // Mask covering all bit positions below j.
32                int mask = (1 << j) - 1;
33                // Cost to raise x's lower bits up to target's lower bits.
34                cost[i] = (target & mask) - (x & mask);
35            }
36
37            // Sort costs ascending so we can pick the cheapest m elements.
38            Arrays.sort(cost);
39
40            // Sum the cost of the m cheapest elements.
41            long sum = 0;
42            for (int i = 0; i < m; i++) {
43                sum += cost[i];
44            }
45
46            // If we can afford this bit within budget k, keep it in the answer.
47            if (sum <= k) {
48                answer = target;
49            }
50        }
51
52        return answer;
53    }
54}
55
1class Solution {
2public:
3    int maximumAND(const vector<int>& nums, int k, int m) {
4        // Determine the highest possible value after adding k to the maximum element.
5        int max_val = ranges::max(nums) + k;
6
7        // Compute how many bits we need to consider.
8        // __builtin_clz counts leading zeros; (32 - clz) gives the bit-length.
9        int bit_count = max_val > 0 ? 32 - __builtin_clz(max_val) : 0;
10
11        int ans = 0;                       // Greedily built answer (target value so far).
12        vector<int> cost(nums.size());     // Cost for each element to reach the candidate target.
13
14        // Greedily try to set bits from the most significant to the least significant.
15        for (int bit = bit_count - 1; bit >= 0; bit--) {
16            // Candidate target: current answer with the current bit turned on.
17            int target = ans | (1 << bit);
18
19            // Compute the cost to upgrade each number so that it covers all set bits of target.
20            for (size_t i = 0; i < nums.size(); i++) {
21                int x = nums[i];
22
23                // Bits required by target that x currently lacks.
24                int diff = target & ~x;
25
26                // Highest bit position that must be fixed (0 if none).
27                int high_bit = diff == 0 ? 0 : 32 - __builtin_clz(diff);
28
29                // Mask covering all bit positions up to (and including) the highest needed bit.
30                long long mask = (1LL << high_bit) - 1;
31
32                // Minimum increment to make x's lower bits match target's lower bits.
33                // (target & mask) is the desired lower-bit pattern; (x & mask) is the current one.
34                cost[i] = (target & mask) - (x & mask);
35            }
36
37            // Pick the m cheapest numbers to satisfy the target.
38            ranges::sort(cost);
39            long long sum = accumulate(cost.begin(), cost.begin() + m, 0LL);
40
41            // If the m smallest costs fit within budget k, lock in this bit.
42            if (sum <= k) {
43                ans = target;
44            }
45        }
46
47        return ans;
48    }
49};
50
1/**
2 * Maximizes the AND value of m chosen numbers after spending at most k on increments.
3 * Uses a greedy bit-by-bit strategy from the most significant bit downward.
4 *
5 * @param nums - The input array of numbers.
6 * @param k - The total budget available for increments.
7 * @param m - The number of elements that must satisfy the target pattern.
8 * @returns The maximum achievable AND value.
9 */
10function maximumAND(nums: number[], k: number, m: number): number {
11    // Determine the highest bit position we need to consider.
12    // The maximum possible value after increments is max(nums) + k,
13    // so the number of significant bits is 32 - leading zeros of that value.
14    const maxBit: number = 32 - Math.clz32(Math.max(...nums) + k);
15
16    let answer: number = 0;
17    const n: number = nums.length;
18
19    // Reusable array storing the incremental cost for each element
20    // to conform to the candidate target pattern.
21    const cost: number[] = new Array(n);
22
23    // Greedily attempt to set each bit, from the most significant to the least.
24    for (let bit = maxBit - 1; bit >= 0; bit--) {
25        // Candidate target: the bits we've already locked in, plus the current bit.
26        const target: number = answer | (1 << bit);
27
28        // Compute the cost to bring each number up to the candidate target.
29        for (let i = 0; i < n; i++) {
30            const x: number = nums[i];
31
32            // Bits that the target requires but x does not currently have.
33            const diff: number = target & ~x;
34
35            // Highest set bit position in diff; this defines the relevant range
36            // of low-order bits we must account for when raising x to target.
37            const highestDiffBit: number = diff === 0 ? 0 : 32 - Math.clz32(diff);
38
39            // Mask covering all bits below highestDiffBit.
40            const mask: number = (1 << highestDiffBit) - 1;
41
42            // Incremental cost: align x's low bits to match target's low bits.
43            cost[i] = (target & mask) - (x & mask);
44        }
45
46        // Sort costs ascending so we can pick the m cheapest elements.
47        cost.sort((a, b) => a - b);
48
49        // Sum the cost of the m cheapest conversions.
50        let sum: number = 0;
51        for (let i = 0; i < m; i++) {
52            sum += cost[i];
53        }
54
55        // If we can afford it within budget k, lock in this bit.
56        if (sum <= k) {
57            answer = target;
58        }
59    }
60
61    return answer;
62}
63

Time and Space Complexity

  • Time Complexity: O(n × log n × log M), where n is the length of the array nums and M is the maximum value in the array nums.

    • The outer loop iterates over each bit position from mx - 1 down to 0. Since mx is the bit length of max(nums) + k, this loop runs O(log M) times.
    • Inside the outer loop, the first inner loop computes the cost for each of the n elements, taking O(n) time.
    • After computing the costs, cost.sort() is performed, which takes O(n × log n) time and dominates the O(n) cost computation.
    • The sum(cost[:m]) operation takes O(m) time, which is bounded by O(n).
    • Therefore, each iteration of the outer loop costs O(n × log n), and the total time complexity is O(log M × n × log n) = O(n × log n × log M).
  • Space Complexity: O(n), where n is the length of the array nums.

    • The auxiliary array cost stores one value per element, requiring O(n) space.
    • The sorting operation may require O(n) additional space depending on the implementation.
    • Hence, the overall space complexity is O(n).

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

Common Pitfalls

Pitfall: Miscomputing the increment cost by ignoring the "carry" effect of lower bits

The most common and subtle mistake in this problem is assuming that turning on a specific bit b in a number x costs simply (1 << b) - (x & (1 << b)) — i.e., treating each bit independently. This is wrong because incrementing only adds to a number, and adding to lower bits can cascade (carry) into higher bits in ways that interact with the target.

The correct insight, captured in the reference solution, is:

To make x cover all bits required by target, you only need to raise the suffix of x below the highest missing bit so that it matches the suffix of target.

Consider why the naive per-bit cost fails. Suppose target requires bits at positions 3 and 1, and x is missing bit 3. A naive approach might compute the cost to set bit 3 alone. But to guarantee the AND keeps bit 3 set, the entire lower portion of x must be increased to at least target's lower portion — otherwise the carry produced while raising bit 3 might disturb (zero out) a bit you previously committed to.

Here is a buggy implementation that falls into this trap:

# WRONG: treats each required bit independently
def cost_buggy(value, target):
    total = 0
    for b in range(target.bit_length()):
        if (target >> b) & 1 and not ((value >> b) & 1):
            total += (1 << b)   # naive "cost" to set bit b
    return total

This overcounts (or undercounts) because increments do not let you set arbitrary bits in isolation — you cannot "turn on" bit 3 of 4 (0b100) without passing through 0b101, 0b110, 0b111, etc.

The Correct Cost Computation

The reference solution handles this elegantly:

highest_missing_bit = (target & ~value).bit_length()
suffix_mask = (1 << highest_missing_bit) - 1
costs[index] = (target & suffix_mask) - (value & suffix_mask)
  • target & ~value isolates the bits required but missing.
  • Its bit_length() gives the position just above the highest missing bit. Everything above this is already satisfied and must remain untouched.
  • We then only need the lower suffix of value to reach the lower suffix of target. The difference (target & suffix_mask) - (value & suffix_mask) is exactly how many increments achieve this without carrying into the higher already-satisfied bits.

Because the suffix difference is always non-negative (any borrow would have been resolved at a higher missing bit), this formula never produces a negative cost.

A Second Pitfall: Forgetting that committing a high bit constrains lower bits

A related conceptual error is computing each bit's affordability independently rather than relative to the already-committed answer. The cost to add bit b depends on what bits are already locked in:

target = answer | (1 << bit)   # cost is measured against ALL committed bits

If you only test 1 << bit instead of answer | (1 << bit), you may report a bit as affordable in isolation, yet the combination with previously secured bits exceeds the budget. Always measure cost against the cumulative target, and only commit when the combined requirement fits within k.

Minor Pitfall: Edge cases with m and the bit range

  • m larger than available cheap elements: costs[:m] will simply take all m smallest after sorting; ensure m <= len(nums) per the problem constraints, otherwise sum(costs[:m]) silently sums fewer elements.
  • Underestimating max_bits: Using max(nums).bit_length() instead of (max(nums) + k).bit_length() would miss the highest reachable bits, since spending k increments can push a value into a new bit range. Always include + k.

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 the following is a good use case for backtracking?


Recommended Readings

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

Load More