Skip to content
← All posts

Minimum Non-Zero Product of the Array Elements

5 min read
leetcodeproblemmediummathgreedy

You are given a positive integer p. Consider an array nums that contains every integer in the range [1, 2^p - 1], written in binary. You are allowed to swap any two bits between any two elements, any number of times. Return the minimum non-zero product of nums after performing the swaps, modulo 10^9 + 7.

This is LeetCode 1969: Minimum Non-Zero Product of the Array Elements. A few examples:

  • p = 1 returns 1 (nums = [1], product is 1)
  • p = 2 returns 6 (nums = [1, 2, 3], after swaps: [1, 1, 3], product = 3)
  • p = 3 returns 1512 (nums = [1, 2, 3, 4, 5, 6, 7], after swaps: product = 7 * 6^3 = 1512)

At first glance, the freedom to swap any bits between any elements feels overwhelming. There are a huge number of possible configurations. But a greedy observation collapses the problem into a single closed-form formula.

Original (p = 3)1001201030114100510161107111swap bitsAfter Swaps7111max6110pair10016110pair10016110pair1001max value (2^p - 1)paired (6, 1)
For p = 3, the array is [1, 2, 3, 4, 5, 6, 7]. By swapping bits, we form 3 pairs of (6, 1) and keep one 7. Product = 7 * 6^3 * 1^3 = 1512.

The approach

The key insight is that swapping bits between numbers lets you redistribute 1-bits freely. To minimize the product, you want to make as many numbers as small as possible (ideally 1) while keeping every number non-zero.

Consider the numbers from 1 to 2^p - 1. Notice that numbers naturally pair up into complements: for any number k in this range, 2^p - 1 - k is also in the range, and their binary representations are bitwise complements. The pairs are:

  • (1, 2^p - 2)
  • (2, 2^p - 3)
  • (3, 2^p - 4)
  • ... and so on

The number 2^p - 1 (all bits set to 1) has no complement in the range besides 0, so it stands alone.

For each pair (k, 2^p - 1 - k), the two numbers share a fixed sum of 2^p - 1. Their product is k * (2^p - 1 - k). To minimize this product, you want to push the values as far apart as possible. Since both must remain non-zero, the best you can do is (1, 2^p - 2), giving a product of 1 * (2^p - 2) = 2^p - 2. You achieve this by swapping all the 1-bits from one number to the other.

This is the AM-GM inequality in action. For a fixed sum, the product of two positive integers is minimized when the values are as far apart as possible, and maximized when they are equal.

There are 2^(p-1) - 1 such pairs (half the range minus the unpaired max), and the lone maximum 2^p - 1 appears once. The answer is:

(2^p - 1) * (2^p - 2)^(2^(p-1) - 1)  mod (10^9 + 7)

Since 2^(p-1) - 1 can be enormous (up to about 2^59 for p = 60), you need fast modular exponentiation to compute (2^p - 2)^(2^(p-1) - 1) mod (10^9 + 7) efficiently.

Python solution

def minNonZeroProduct(p):
    MOD = 10**9 + 7
    max_val = (1 << p) - 1
    pair_val = max_val - 1
    pair_count = (1 << (p - 1)) - 1
    return max_val % MOD * pow(pair_val % MOD, pair_count, MOD) % MOD

Walkthrough

Let's trace through minNonZeroProduct(3) to see every step of the reasoning.

Walking through minNonZeroProduct(3):

Step 1: Compute key values from p

max value:2^p - 1 = 2^3 - 1 = 7
pair value:2^p - 2 = 2^3 - 2 = 6
pair count:2^(p-1) - 1 = 2^2 - 1 = 3

These three values define the entire answer. Everything flows from p.

Step 2: Understand the pairing

pair 1:(1, 6) from original (1, 6)sum = 7
pair 2:(1, 6) from original (2, 5)sum = 7
pair 3:(1, 6) from original (3, 4)sum = 7
unpaired:7(all bits are 1, no complement)

Numbers that sum to 2^p - 1 are complementary. Swapping makes one into 1 and the other into 2^p - 2.

Step 3: Why (1, 6) minimizes each pair's product

original (1, 6):1 * 6 = 6minimum
original (2, 5):2 * 5 = 10
original (3, 4):3 * 4 = 12

For any pair summing to 7, the product is minimized when one value is as small as possible. 1 * 6 = 6 beats 2 * 5 = 10 or 3 * 4 = 12.

Step 4: Compute the answer with modular exponentiation

formula:(2^p - 1) * (2^p - 2)^(2^(p-1) - 1) mod (10^9 + 7)
plug in:7 * 6^3 mod (10^9 + 7)
6^3:6 * 6 = 36, then 36 * 6 = 216
final:7 * 216 = 1512

We use fast power (binary exponentiation) to compute 6^3 mod (10^9 + 7), then multiply by 7.

Result: minNonZeroProduct(3) = 1512

The formula (2^p - 1) * (2^p - 2)^(2^(p-1) - 1) gives the answer directly. For large p, we use Python's built-in pow(base, exp, mod) for O(log n) modular exponentiation.

The solution computes three values from p, then does a single modular exponentiation. Python's built-in pow(base, exp, mod) uses binary exponentiation internally, running in O(log exp) time. For p = 60, that is roughly 60 squarings and multiplications, not 2^59 of them.

The formula works because every pair's contribution to the product is identical after optimal swaps. Once you see that each pair becomes (1, 2^p - 2), the entire product collapses into a single exponentiation.

Complexity analysis

ApproachTimeSpace
Brute force (try all swap configs)ExponentialExponential
Greedy formula with modular expO(p)O(1)

The O(p) time comes from pow(pair_val, pair_count, MOD), which does O(log pair_count) = O(p) modular multiplications. Each multiplication is O(1) since we work with numbers less than 10^9 + 7. No extra data structures are needed.

Edge cases to watch for

Before submitting, make sure your solution handles these:

  • p = 1: The array is just [1]. The product is 1. The formula gives max_val = 1, pair_count = 0, and 1 * 0^0 should be 1 * 1 = 1. Python's pow(0, 0, MOD) returns 1, so this works.
  • p = 2: The array is [1, 2, 3]. One pair: (1, 2) becomes (1, 2), plus the max 3. Product = 3 * 2 = 6.
  • Large p (up to 60): 2^p - 1 can be up to about 1.15 * 10^18. You must take the modulus before multiplying. The exponent 2^(p-1) - 1 can be about 5.76 * 10^17, but pow(base, exp, mod) handles this efficiently.
  • Modular arithmetic order: Be careful with when you apply % MOD. The base pair_val should be reduced mod MOD before exponentiation, and the final multiplication by max_val should also be done mod MOD.

The building blocks

This problem is built on three reusable patterns:

Greedy pairing by complement. When elements pair up by a fixed sum or XOR complement, you can reason about each pair independently. The optimal choice for each pair is the same: push values to the extremes. This pattern appears in problems involving XOR complements, partition optimization, and min/max product reasoning.

Modular exponentiation. Computing a^b mod m in O(log b) time using binary exponentiation is a fundamental building block. It appears in cryptography (RSA), combinatorics (computing large binomial coefficients mod a prime), and anywhere you need to raise a number to a huge power under a modulus. Python's built-in pow(a, b, m) handles this, but knowing how it works (squaring at each step, multiplying when the bit is set) is essential for understanding why it is efficient.

Closed-form reduction. The swap freedom in this problem looks like it requires simulation, but the greedy insight reduces everything to a formula. Recognizing when a problem has a closed-form answer (instead of requiring search or simulation) is a skill that saves enormous implementation effort.

From understanding to recall

You have seen how the complement pairing works, why pushing values to extremes minimizes the product, and how the formula collapses into a single modular exponentiation. The solution is just four lines of code.

But under interview pressure, you need the connection between "free bit swaps" and "complement pairs become (1, 2^p - 2)" to surface immediately. That only happens if you have practiced the pattern enough times that the reasoning is automatic: complement pairs, push to extremes, single formula with fast power.

Spaced repetition locks this in. You solve the problem from scratch today, again in two days, then four days, then a week. Each time, you reconstruct the pairing argument and the formula from memory. After a few rounds, the pattern is yours.

Related posts

  • Pow(x, n) - The binary exponentiation technique that powers the modular exponentiation in this problem
  • Divide Two Integers - Another problem where bit manipulation turns a brute-force approach into an efficient one
  • Factorial Trailing Zeroes - A math problem where counting prime factors gives you an O(log n) formula instead of computing the answer directly