LeetCode 3133 - Minimum Array End
The problem asks us to construct a strictly increasing array nums of length n such that the bitwise AND of all elements equals x. Among all valid arrays, we want to minimize the last element, nums[n - 1]. The constraints are important: - Every element must be a positive integer.
Difficulty: 🟡 Medium
Topics: Bit Manipulation
Solution
Problem Understanding
The problem asks us to construct a strictly increasing array nums of length n such that the bitwise AND of all elements equals x.
Among all valid arrays, we want to minimize the last element, nums[n - 1].
The constraints are important:
- Every element must be a positive integer.
- The array must be strictly increasing.
- The bitwise AND of all elements must equal exactly
x.
The most important observation is that if the AND of all numbers is x, then every bit that is set in x must also be set in every element of the array. Otherwise, that bit would disappear from the final AND result.
For example:
- If
x = 100₂(which is4), then every number in the array must contain that bit. - We are allowed to add extra
1bits in some positions, because AND only preserves bits that are1in every number.
The goal is to make the sequence increasing while keeping the numbers as small as possible.
The constraints go up to 10^8, which means brute force generation of many candidate numbers can become infeasible. We need a solution based on direct bit manipulation rather than simulation.
There are several important edge cases:
- When
n = 1, the answer is simplyx, because a single-element array already satisfies the AND condition. - When
xalready has many bits set, there may be very few low positions available for modification. - Large values of
nmay require using higher unused bits to encode distinct increasing values. - We must ensure we never unset any bit that exists in
x.
Approaches
Brute Force Approach
A straightforward idea is to start from x and search for numbers greater than the previous element that still preserve the overall AND equal to x.
For each candidate number:
- It must contain all
1bits fromx. - We append it to the array if it is larger than the previous value.
We continue until the array reaches size n.
This works because any number that preserves all bits of x can participate in an AND result of x.
However, this approach is too slow. If x is small and n is large, we may need to scan many integers to find valid candidates. In the worst case, the search space becomes enormous.
Key Insight
The crucial observation is that the valid numbers are exactly those formed by:
- Keeping every
1bit ofx - Freely choosing values for positions where
xhas0bits
Those zero-bit positions act like "slots" where we can encode increasing numbers.
Instead of generating candidates one by one, we can directly construct the smallest possible final number.
Think of the sequence as:
nums[0] = x- The remaining numbers are formed by filling the zero-bit positions of
xusing binary representations of increasing integers.
The minimum possible last element corresponds to encoding n - 1 into the zero-bit positions of x.
This gives a direct bit manipulation solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(answer) | O(1) | Scans candidate numbers until enough valid values are found |
| Optimal | O(log n + log x) | O(1) | Directly maps bits of n - 1 into zero positions of x |
Algorithm Walkthrough
Step 1: Convert the problem into bit placement
Every number in the array must preserve all 1 bits of x.
That means:
- Bits that are
1inxare fixed - Bits that are
0inxmay vary
These variable positions are where we encode increasing values.
Step 2: Observe the ordering pattern
The smallest valid number is simply x.
The next valid numbers are obtained by turning on combinations of zero-bit positions.
For example, if:
x = 100₂
The zero positions are:
bit 0, bit 1, bit 3, bit 4, ...
We can map integers into these positions:
| Encoded Value | Result |
|---|---|
| 0 | 100₂ = 4 |
| 1 | 101₂ = 5 |
| 2 | 110₂ = 6 |
| 3 | 111₂ = 7 |
So the sequence naturally becomes increasing.
Step 3: Use n - 1
Since the first number corresponds to encoded value 0, the final element corresponds to encoded value:
n - 1
We therefore need to insert the binary bits of n - 1 into the zero-bit positions of x.
Step 4: Iterate through bit positions
We scan bit positions from low to high.
For each bit:
- If the bit in
xis1, we skip it because it is fixed. - If the bit in
xis0, we take the next bit fromn - 1.
If that bit from n - 1 is 1, we set this position in the answer.
Then we move to the next bit of n - 1.
Step 5: Return the constructed number
After all bits of n - 1 are consumed, the constructed value is the minimum possible final element.
Why it works
The algorithm works because every valid number must contain all 1 bits from x. The remaining positions are independent and can encode arbitrary binary values.
By placing the bits of n - 1 into the lowest available zero positions, we generate the smallest possible number that is the (n - 1)-th valid increasing value after x.
This guarantees:
- The array can contain
nstrictly increasing values - Every number preserves the AND equal to
x - The final element is minimized
Python Solution
class Solution:
def minEnd(self, n: int, x: int) -> int:
n -= 1
result = x
bit_position = 0
while n > 0:
# Only use positions where x has a 0 bit
if ((x >> bit_position) & 1) == 0:
# Take the next bit from n
if n & 1:
result |= (1 << bit_position)
n >>= 1
bit_position += 1
return result
The implementation starts by converting n into n - 1, because the first valid number corresponds to encoded value 0.
result begins as x, since every valid number must preserve all set bits of x.
We then iterate through bit positions from least significant to most significant.
Whenever we encounter a zero-bit position in x, we use it as an available slot for encoding bits from n - 1.
If the current bit of n - 1 is 1, we set that bit in result.
After processing a slot, we shift n right to consume the used bit.
At the end, result contains the minimum possible final array element.
Go Solution
func minEnd(n int, x int) int64 {
n--
result := int64(x)
bitPosition := 0
for n > 0 {
// Use only positions where x has a 0 bit
if ((x >> bitPosition) & 1) == 0 {
// Insert next bit from n
if (n & 1) == 1 {
result |= (1 << bitPosition)
}
n >>= 1
}
bitPosition++
}
return result
}
The Go implementation follows exactly the same logic as the Python version.
One important detail is the use of int64 for the result. The final number can exceed the range of a standard 32-bit integer because we may need to place bits into high positions. Using int64 guarantees safe handling of all cases.
Worked Examples
Example 1
Input:
n = 3
x = 4
Binary form:
x = 100₂
n - 1 = 2 = 10₂
We insert bits of 10₂ into zero positions of 100₂.
| Bit Position | x Bit | Next Bit from n-1 | Result |
|---|---|---|---|
| 0 | 0 | 0 | 100₂ |
| 1 | 0 | 1 | 110₂ |
Final result:
110₂ = 6
Answer:
6
Possible array:
[4, 5, 6]
Example 2
Input:
n = 2
x = 7
Binary form:
x = 111₂
n - 1 = 1 = 1₂
The first available zero bit is at position 3.
| Bit Position | x Bit | Next Bit from n-1 | Result |
|---|---|---|---|
| 0 | 1 | skip | 111₂ |
| 1 | 1 | skip | 111₂ |
| 2 | 1 | skip | 111₂ |
| 3 | 0 | 1 | 1111₂ |
Final result:
1111₂ = 15
Answer:
15
Possible array:
[7, 15]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(log n + log x) | We scan enough bit positions to process all bits of n and x |
| Space | O(1) | Only a few variables are used |
The algorithm is extremely efficient because it avoids generating intermediate numbers. Instead, it directly constructs the answer using bit operations.
The number of iterations is bounded by the number of relevant bit positions, which is at most around 60 for typical integer sizes.
Test Cases
solution = Solution()
assert solution.minEnd(3, 4) == 6 # Provided example
assert solution.minEnd(2, 7) == 15 # Provided example
assert solution.minEnd(1, 5) == 5 # Single element array
assert solution.minEnd(1, 1) == 1 # Smallest possible values
assert solution.minEnd(2, 1) == 3 # First zero bit used
assert solution.minEnd(3, 1) == 5 # Multiple encodings
assert solution.minEnd(4, 2) == 7 # Mixed zero and one bits
assert solution.minEnd(5, 8) == 12 # Higher bit positions
assert solution.minEnd(10, 3) == 39 # Larger n with dense bits
assert solution.minEnd(100000000, 1) > 0 # Stress test
assert solution.minEnd(6, 4) == 13 # Multiple zero slots used
assert solution.minEnd(8, 0b1010) == 31 # Alternating bits
| Test | Why |
|---|---|
n=3, x=4 |
Validates the first sample |
n=2, x=7 |
Validates handling of consecutive set bits |
n=1, x=5 |
Tests single-element edge case |
n=2, x=1 |
Tests smallest expandable value |
n=4, x=2 |
Tests mixed binary structure |
n=5, x=8 |
Tests higher-bit placement |
n=10, x=3 |
Tests many inserted bits |
n=100000000, x=1 |
Stress test for performance |
n=6, x=4 |
Tests multiple available positions |
n=8, x=1010₂ |
Tests alternating bit pattern |
Edge Cases
Edge Case 1: n = 1
If the array contains only one element, then the AND of the array is simply that element itself.
So the minimum valid array is:
[x]
and the answer is exactly x.
The implementation handles this naturally because n - 1 becomes 0, so the loop never executes and result remains equal to x.
Edge Case 2: x Has Many Consecutive Set Bits
Consider:
x = 111111₂
There are no available low-bit positions for encoding additional values.
The algorithm correctly skips all occupied positions and eventually uses higher unused bits.
For example:
x = 7 = 111₂
n = 2
The next available position is bit 3, producing:
1111₂ = 15
Edge Case 3: Large n
Large values of n require many encoding bits.
A naive solution might attempt to generate all intermediate numbers, which becomes prohibitively slow.
The optimal solution never generates the sequence explicitly. Instead, it directly maps the binary representation of n - 1 into available bit positions.
This guarantees excellent performance even near the maximum constraints.
Edge Case 4: Sparse Bit Structure in x
Suppose:
x = 100000₂
There are many available low-bit positions.
The algorithm efficiently fills these positions from lowest to highest, guaranteeing the smallest possible final value.
Using lower positions first is essential because lower bits contribute less to the numeric value.