LeetCode 1680 - Concatenation of Consecutive Binary Numbers
The problem asks us to build a very large binary number by concatenating the binary representations of every integer fro
Difficulty: 🟡 Medium
Topics: Math, Bit Manipulation, Simulation
Solution
Problem Understanding
The problem asks us to build a very large binary number by concatenating the binary representations of every integer from 1 through n, in order, and then return its decimal value modulo 10^9 + 7.
For example, if n = 3:
1in binary is"1"2in binary is"10"3in binary is"11"
Concatenating them produces:
"1" + "10" + "11" = "11011"
The binary string "11011" equals 27 in decimal, so the answer is 27.
The important detail is that we are not asked to return the binary string itself. Instead, we must compute its decimal value efficiently.
The constraint 1 <= n <= 10^5 is critical. A naive implementation that literally builds the binary string would eventually create a string with hundreds of thousands of characters. Converting that huge string back into an integer would also be expensive and unnecessary.
The problem guarantees that n is always at least 1, so we do not need to handle empty input cases. However, there are still important implementation concerns:
- The concatenated value grows extremely quickly, so integer overflow becomes an issue in languages with fixed integer sizes.
- Constructing the full binary string wastes memory.
- We must apply modulo
10^9 + 7continuously during computation.
The key challenge is finding a way to simulate binary concatenation mathematically without actually building the enormous binary string.
Approaches
Brute Force Approach
The most direct solution is:
- Convert every number from
1toninto binary. - Append each binary string to a growing result string.
- Convert the final binary string into a decimal integer.
- Return the value modulo
10^9 + 7.
This approach is conceptually simple because it follows the problem statement literally.
For example, if n = 5:
1 -> "1"
2 -> "10"
3 -> "11"
4 -> "100"
5 -> "101"
Concatenated:
"11011100101"
After building the full string, we could parse it as a binary integer.
The issue is efficiency. The total binary length grows roughly as:
$$O(n \log n)$$
For n = 100000, the resulting binary string becomes extremely large. String concatenation and big integer conversion become expensive operations.
Although this might barely work in some high level languages with optimized big integers, it is not the intended solution and is inefficient in both memory and runtime.
Optimal Approach
The key observation is that concatenating binary numbers can be simulated using bit shifting.
Suppose our current accumulated value is:
result
and we want to append the binary representation of some number x.
Appending binary digits is equivalent to:
- Shift
resultleft by the number of bits inx - Add
x
Mathematically:
$$result = (result \ll bits) + x$$
For example:
result = binary "11011" = 27
x = binary "100" = 4
Appending "100" means:
"11011" -> "11011100"
Shifting left by 3 bits:
27 << 3 = 216
Then add 4:
216 + 4 = 220
Binary of 220:
11011100
Exactly what we wanted.
The remaining challenge is determining how many bits each number requires.
A number gains a new binary digit whenever it becomes a power of two:
1 -> 1 bit
2 -> 2 bits
4 -> 3 bits
8 -> 4 bits
16 -> 5 bits
So whenever x is a power of two, we increment our bit length counter.
This gives an elegant linear time solution with constant extra space.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n log n) | O(n log n) | Builds the entire binary string explicitly |
| Optimal | O(n) | O(1) | Uses bit shifting to simulate concatenation |
Algorithm Walkthrough
- Initialize a variable
result = 0.
This variable stores the decimal value of the concatenated binary sequence processed so far.
2. Initialize bit_length = 0.
This tracks how many binary digits the current number requires.
3. Iterate through all integers num from 1 to n.
For each number, we append its binary representation to the accumulated result.
4. Check whether num is a power of two.
A number is a power of two if:
$$num & (num - 1) = 0$$
This works because powers of two contain exactly one set bit in binary.
5. If num is a power of two, increment bit_length.
This means we now need one additional binary digit for all numbers in this range.
6. Shift result left by bit_length.
Left shifting creates enough empty binary positions to append the new number.
7. Add num to the shifted value.
This effectively concatenates the binary digits of num.
8. Apply modulo 10^9 + 7.
This prevents overflow and keeps the number manageable.
9. After processing all numbers, return result.
Why it works
At every iteration, result stores the exact decimal value of the concatenated binary string formed from 1 through num - 1.
When processing num, shifting left by its bit length creates exactly enough empty binary positions to append the new binary digits. Adding num fills those positions with the correct bits.
Because this invariant holds at every step, the final value is the decimal representation of the full concatenated binary sequence.
Python Solution
class Solution:
def concatenatedBinary(self, n: int) -> int:
MOD = 10**9 + 7
result = 0
bit_length = 0
for num in range(1, n + 1):
if (num & (num - 1)) == 0:
bit_length += 1
result = ((result << bit_length) + num) % MOD
return result
The implementation starts by defining the modulo constant 10^9 + 7, since the result can grow extremely large.
The variable result stores the decimal value of the concatenated binary sequence processed so far.
The variable bit_length tracks how many bits are required to represent the current number. Instead of recomputing this with logarithms or string conversion, the code updates it only when the current number is a power of two.
Inside the loop, the condition:
(num & (num - 1)) == 0
checks whether num is a power of two. If it is, the binary representation length increases by one.
The core operation is:
result = ((result << bit_length) + num) % MOD
The left shift creates space for the new binary digits, and adding num appends those digits.
Modulo is applied immediately after every update to avoid large intermediate values.
Go Solution
func concatenatedBinary(n int) int {
const MOD int = 1000000007
result := 0
bitLength := 0
for num := 1; num <= n; num++ {
if (num & (num - 1)) == 0 {
bitLength++
}
result = ((result << bitLength) + num) % MOD
}
return result
}
The Go implementation follows the exact same logic as the Python solution.
One important consideration in Go is integer overflow. Since intermediate values can become large, applying modulo during every iteration is essential.
Go integers are fixed width, unlike Python's arbitrary precision integers. However, because we reduce modulo 10^9 + 7 after every operation, standard integer arithmetic remains safe for this problem.
Worked Examples
Example 1
Input:
n = 1
| num | Binary | bit_length | result before | result after |
|---|---|---|---|---|
| 1 | 1 | 1 | 0 | 1 |
Final answer:
1
Example 2
Input:
n = 3
| num | Binary | bit_length | Operation | result |
|---|---|---|---|---|
| 1 | 1 | 1 | (0 << 1) + 1 | 1 |
| 2 | 10 | 2 | (1 << 2) + 2 | 6 |
| 3 | 11 | 2 | (6 << 2) + 3 | 27 |
Binary progression:
1
110
11011
Final answer:
27
Example 3
Input:
n = 12
| num | Binary | bit_length | result after step |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
| 2 | 10 | 2 | 6 |
| 3 | 11 | 2 | 27 |
| 4 | 100 | 3 | 220 |
| 5 | 101 | 3 | 1765 |
| 6 | 110 | 3 | 14126 |
| 7 | 111 | 3 | 113015 |
| 8 | 1000 | 4 | 1808248 |
| 9 | 1001 | 4 | 28931977 |
| 10 | 1010 | 4 | 462911642 |
| 11 | 1011 | 4 | 406586234 |
| 12 | 1100 | 4 | 505379714 |
Final answer:
505379714
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each number from 1 to n is processed once |
| Space | O(1) | Only a few integer variables are used |
The algorithm performs a constant amount of work per iteration. The power of two check, left shift, addition, and modulo operation are all constant time operations.
No auxiliary data structures are created, and the algorithm never stores the actual concatenated binary string, so the extra space usage remains constant.
Test Cases
solution = Solution()
assert solution.concatenatedBinary(1) == 1
# Smallest valid input
assert solution.concatenatedBinary(3) == 27
# Basic example from prompt
assert solution.concatenatedBinary(12) == 505379714
# Larger example with modulo behavior
assert solution.concatenatedBinary(2) == 6
# "110" in binary equals 6
assert solution.concatenatedBinary(4) == 220
# Tests transition to a new bit length
assert solution.concatenatedBinary(5) == 1765
# Verifies continued concatenation after bit-length increase
assert solution.concatenatedBinary(7) == 113015
# Last number before another power-of-two boundary
assert solution.concatenatedBinary(8) == 1808248
# Tests another bit-length transition
assert solution.concatenatedBinary(100000) >= 0
# Stress test for upper constraint
| Test | Why |
|---|---|
n = 1 |
Validates smallest possible input |
n = 3 |
Verifies basic concatenation logic |
n = 12 |
Confirms modulo handling |
n = 2 |
Tests simple multi-number concatenation |
n = 4 |
Checks power-of-two boundary behavior |
n = 5 |
Ensures bit length remains correct after transition |
n = 7 |
Tests values immediately before next power of two |
n = 8 |
Validates another bit-length increase |
n = 100000 |
Stress test for maximum constraint |
Edge Cases
One important edge case is the smallest possible input, n = 1. In this scenario, the algorithm only processes a single number. The implementation correctly initializes bit_length to 1 when encountering the first power of two and returns 1 immediately after the first iteration.
Another critical edge case occurs at powers of two such as 2, 4, 8, and 16. These numbers require one additional binary digit compared to the previous numbers. Forgetting to update the bit length at these boundaries would produce incorrect concatenation results because the left shift would not create enough space for the appended bits. The implementation handles this correctly using the power-of-two check:
(num & (num - 1)) == 0
A third important edge case involves very large values of n, especially near the upper constraint 100000. The concatenated binary number becomes astronomically large, far exceeding standard integer sizes. A naive solution that constructs the actual binary string could consume excessive memory and runtime. This implementation avoids that entirely by maintaining only the current modular value and never storing the full binary representation.
Another subtle edge case is integer overflow in fixed-width languages like Go. Repeated shifting and addition can easily exceed 32-bit integer limits. Applying modulo during every iteration keeps intermediate values bounded and ensures correctness throughout the computation.