LeetCode 868 - Binary Gap
The problem asks us to examine the binary representation of a positive integer n and determine the largest distance between two adjacent 1 bits. A binary number is made up of 0s and 1s.
Difficulty: 🟢 Easy
Topics: Bit Manipulation
Solution
Problem Understanding
The problem asks us to examine the binary representation of a positive integer n and determine the largest distance between two adjacent 1 bits.
A binary number is made up of 0s and 1s. For example:
22in binary is101105in binary is1018in binary is1000
We are interested only in positions where the bit is 1. Among those 1s, we look at pairs that are adjacent in the sense that there are no other 1s between them.
For example, in 10110:
- The first and second
1bits are separated by one0, so their distance is2 - The second and third
1bits are next to each other, so their distance is1
The answer is the maximum of these distances.
The input is a single integer n, where:
1 <= n <= 10^9
Since 10^9 fits comfortably within 32 bits, the binary representation is very small. This immediately suggests that even scanning every bit one by one will be extremely efficient.
There are several important edge cases:
- Numbers with only one
1bit, such as8(1000), should return0 - Numbers with consecutive
1s, such as3(11), should return1 - Numbers with large gaps between
1s, such as17(10001), should correctly compute the larger distance - The smallest valid input,
1, contains only a single1, so the answer is0
The problem guarantees that n is positive, so we never need to handle negative values or an empty binary representation.
Approaches
Brute Force Approach
A straightforward approach is to first convert the number into a binary string using a built-in conversion function.
For example:
22 -> "10110"
We can then scan the string from left to right and record the positions of every 1. Whenever we encounter a new 1, we calculate the distance from the previous 1 and update the maximum distance.
This approach is correct because every pair of adjacent 1s is examined exactly once.
Although this solution is already fast enough for the given constraints, it uses extra space for the binary string conversion.
Optimal Approach
A more efficient and elegant solution uses direct bit manipulation.
Instead of converting the number into a string, we process the bits one at a time using bitwise operations:
n & 1checks whether the current bit is1n >>= 1shifts the bits to the right
While scanning the bits, we track:
- The current bit position
- The position of the previous
1 - The maximum gap found so far
Whenever we encounter a new 1, we compute the distance from the previous 1.
This works well because binary representation is inherently bit-based, so bit manipulation lets us process the number directly without extra memory allocation.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(log n) | O(log n) | Converts integer to binary string and scans it |
| Optimal | O(log n) | O(1) | Processes bits directly using bit manipulation |
Algorithm Walkthrough
-
Initialize a variable
max_gapto0. This will store the largest distance found between adjacent1s. -
Initialize a variable
previous_one_positionto-1. This variable remembers the position of the last1bit we encountered. A value of-1means we have not seen any1yet. -
Initialize a variable
positionto0. This represents the current bit position while scanning from right to left. -
While
nis greater than0, continue processing bits one at a time. -
Check whether the current bit is
1usingn & 1. -
If the current bit is
1: -
If
previous_one_positionis not-1, compute the distance:
position - previous_one_position
- Update
max_gapif this distance is larger. - Store the current position as the new
previous_one_position. - Shift the number right by one bit using:
n >>= 1
This moves the next bit into the least significant position.
8. Increment position by 1.
9. After all bits have been processed, return max_gap.
Why it works
The algorithm scans every bit exactly once in order from least significant to most significant. Every time a 1 bit is encountered, the algorithm compares its position with the previous 1 bit position. Since adjacent 1s are encountered consecutively during the scan, every valid gap is measured exactly once. Taking the maximum of these distances guarantees the correct answer.
Python Solution
class Solution:
def binaryGap(self, n: int) -> int:
max_gap = 0
previous_one_position = -1
position = 0
while n > 0:
if n & 1:
if previous_one_position != -1:
gap = position - previous_one_position
max_gap = max(max_gap, gap)
previous_one_position = position
n >>= 1
position += 1
return max_gap
The implementation closely follows the algorithm described earlier.
The variable max_gap stores the best answer found so far. The variable previous_one_position tracks the location of the last 1 bit. The variable position keeps track of the current bit index.
The expression n & 1 checks whether the least significant bit is 1. If it is, we compute the distance from the previous 1 and update the answer if necessary.
The operation n >>= 1 shifts the bits to the right, allowing us to process the next bit during the next iteration.
Because we never store the binary representation explicitly, the solution uses constant extra space.
Go Solution
func binaryGap(n int) int {
maxGap := 0
previousOnePosition := -1
position := 0
for n > 0 {
if n&1 == 1 {
if previousOnePosition != -1 {
gap := position - previousOnePosition
if gap > maxGap {
maxGap = gap
}
}
previousOnePosition = position
}
n >>= 1
position++
}
return maxGap
}
The Go implementation mirrors the Python logic almost exactly.
One small difference is that Go does not provide Python's built-in max() function for integers, so we use a standard if statement to update maxGap.
Since the constraints are small, integer overflow is not a concern. The solution also uses constant extra space in Go.
Worked Examples
Example 1
Input:
n = 22
Binary representation:
10110
Processing occurs from right to left because we examine the least significant bit first.
| Iteration | Current Bit | Position | Previous 1 Position | Gap | Max Gap |
|---|---|---|---|---|---|
| 1 | 0 | 0 | -1 | - | 0 |
| 2 | 1 | 1 | -1 | - | 0 |
| 3 | 1 | 2 | 1 | 1 | 1 |
| 4 | 0 | 3 | 2 | - | 1 |
| 5 | 1 | 4 | 2 | 2 | 2 |
Final answer:
2
Example 2
Input:
n = 8
Binary representation:
1000
| Iteration | Current Bit | Position | Previous 1 Position | Gap | Max Gap |
|---|---|---|---|---|---|
| 1 | 0 | 0 | -1 | - | 0 |
| 2 | 0 | 1 | -1 | - | 0 |
| 3 | 0 | 2 | -1 | - | 0 |
| 4 | 1 | 3 | -1 | - | 0 |
There is only one 1, so no valid pair exists.
Final answer:
0
Example 3
Input:
n = 5
Binary representation:
101
| Iteration | Current Bit | Position | Previous 1 Position | Gap | Max Gap |
|---|---|---|---|---|---|
| 1 | 1 | 0 | -1 | - | 0 |
| 2 | 0 | 1 | 0 | - | 0 |
| 3 | 1 | 2 | 0 | 2 | 2 |
Final answer:
2
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(log n) | We process each bit of the number once |
| Space | O(1) | Only a few variables are used |
The number of iterations equals the number of bits in n. Since a number with value n contains approximately log2(n) bits, the running time is O(log n).
The algorithm does not allocate any extra data structures proportional to the input size, so the auxiliary space complexity is constant.
Test Cases
solution = Solution()
assert solution.binaryGap(22) == 2 # Example case: 10110
assert solution.binaryGap(8) == 0 # Only one 1 bit
assert solution.binaryGap(5) == 2 # 101
assert solution.binaryGap(1) == 0 # Smallest valid input
assert solution.binaryGap(2) == 0 # 10, only one 1
assert solution.binaryGap(3) == 1 # 11, adjacent bits
assert solution.binaryGap(9) == 3 # 1001
assert solution.binaryGap(17) == 4 # 10001
assert solution.binaryGap(20) == 2 # 10100
assert solution.binaryGap(6) == 1 # 110
assert solution.binaryGap(10) == 2 # 1010
assert solution.binaryGap(42) == 2 # 101010
assert solution.binaryGap(1041) == 5 # 10000010001
| Test | Why |
|---|---|
22 -> 2 |
Validates multiple adjacent gaps |
8 -> 0 |
Validates single 1 case |
5 -> 2 |
Validates separated 1s |
1 -> 0 |
Smallest valid input |
2 -> 0 |
Binary with one 1 and trailing zeros |
3 -> 1 |
Consecutive 1s |
9 -> 3 |
Larger gap between two 1s |
17 -> 4 |
Very large gap |
20 -> 2 |
Internal zero spacing |
6 -> 1 |
Consecutive high bits |
10 -> 2 |
Alternating bit pattern |
42 -> 2 |
Multiple equal gaps |
1041 -> 5 |
Larger binary representation stress case |
Edge Cases
One important edge case is when the number contains only a single 1 bit. Examples include 1, 2, 4, and 8. In these cases, no adjacent pair of 1s exists, so the answer must be 0. A buggy implementation might accidentally return an uninitialized value or compute an invalid distance. This implementation avoids that problem by initializing previous_one_position to -1 and only computing a gap after a previous 1 has already been seen.
Another important edge case is when the binary representation contains consecutive 1s, such as 3 (11) or 6 (110). The distance between consecutive bit positions is 1, which is the minimum valid gap. Some incorrect implementations mistakenly count the number of zeros between 1s instead of the distance between positions. This implementation correctly computes the positional difference, so consecutive 1s produce a gap of 1.
A third important edge case is when the maximum gap appears near the most significant bits, such as 17 (10001). An incorrect implementation might terminate too early or fail to track the correct bit position during shifts. This solution increments the position variable on every iteration, ensuring that all bit distances are measured accurately regardless of where the 1s appear in the number.