title: Bitwise AND of Numbers Range slug: bitwise-and-of-numbers-range difficulty: medium leetcode_id: 201 leetcode_url: https://leetcode.com/problems/bitwise-and-of-numbers-range/ categories: - math patterns: - binary-search description: | Given two integers `left` and `right` that represent the range `[left, right]`, return *the bitwise AND of all numbers in this range, inclusive*. constraints: | - `0 <= left <= right <= 2^31 - 1` examples: - input: "left = 5, right = 7" output: "4" explanation: "The bitwise AND of 5 (101), 6 (110), and 7 (111) is 4 (100)." - input: "left = 0, right = 0" output: "0" explanation: "The only number in the range is 0, so the result is 0." - input: "left = 1, right = 2147483647" output: "0" explanation: "The range includes both odd and even numbers spanning many bit positions. Since at least one number has a 0 in every bit position (except none share all 1s), the result is 0." explanation: intuition: | Imagine writing out all numbers from `left` to `right` in binary, stacked vertically like a table. When you AND all these numbers together, any bit position that has even a single `0` in the column becomes `0` in the result. The key insight is this: as numbers increment, the **rightmost bits flip frequently**. Every time a number increases by 1, the least significant bit toggles. Every 2 increments, the second bit toggles. And so on. Think of it like an odometer: the rightmost digit changes fastest. If `left` and `right` differ by any amount, the lower bits will have seen both `0` and `1` values at some point in the range — meaning those bits will AND to `0`. The only bits that survive the AND operation are the **common prefix bits** — the leftmost bits where `left` and `right` are identical. These bits never change throughout the entire range. So the problem reduces to: **find the common prefix of `left` and `right` in binary**. approach: | We solve this by finding the **common binary prefix** of `left` and `right`: **Step 1: Understand the core operation** - We need to shift both numbers right until they become equal - This effectively removes the differing lower bits - Count how many shifts we perform   **Step 2: Shift until equal** - While `left != right`: - Right-shift both `left` and `right` by 1 - Increment a shift counter - When they're equal, we've found the common prefix   **Step 3: Restore the prefix to its original position** - Left-shift the common prefix back by the number of shifts we counted - This gives us the answer with zeros in all the "volatile" bit positions   **Alternative: Brian Kernighan's Algorithm** - Instead of shifting both numbers, we can repeatedly clear the rightmost set bit of `right` until `right <= left` - The expression `right & (right - 1)` clears the rightmost `1` bit - When `right <= left`, whatever remains in `right` is the common prefix common_pitfalls: - title: The Brute Force Trap description: | The naive approach is to iterate from `left` to `right` and AND all numbers: ```python result = left for num in range(left + 1, right + 1): result &= num ``` With constraints up to `2^31 - 1`, this can mean iterating over **2 billion numbers**. This will cause a Time Limit Exceeded (TLE) error. The key realisation is that we don't need to AND every number — we just need to find where the binary representations diverge. wrong_approach: "Iterating and ANDing all numbers in range" correct_approach: "Find common prefix using bit shifts" - title: Missing the Pattern in Binary description: | Consider `left = 5` (101) and `right = 7` (111): ``` 5: 101 6: 110 7: 111 ``` Notice that the two rightmost bits vary across the range. Only the leftmost bit (position 2) stays constant. The AND result is `100` = 4. If you try to analyse this without understanding the binary pattern, you might miss that we're simply looking for where the numbers "agree" in their most significant bits. wrong_approach: "Trying to compute AND bit by bit without seeing the prefix pattern" correct_approach: "Recognise that only the common prefix survives" - title: Off-by-One in Shift Count description: | When implementing the shift approach, ensure you're counting shifts correctly and shifting back by the same amount. A common mistake is forgetting to shift the result back to its original position, returning just the prefix value instead of the prefix in its correct bit position. wrong_approach: "Returning the prefix without left-shifting back" correct_approach: "Track shift count and restore position at the end" key_takeaways: - "**Bit manipulation insight**: When ANDing a range of consecutive integers, only the common binary prefix survives — all differing lower bits become 0" - "**Brian Kernighan's trick**: `n & (n - 1)` clears the rightmost set bit — useful for many bit manipulation problems" - "**Avoid iteration**: Problems involving ranges of numbers often have mathematical shortcuts that avoid iterating through every element" - "**Binary perspective**: Many number-range problems become clearer when you visualise the numbers in binary and look for patterns" time_complexity: "O(log n). We perform at most 31 shifts (for 32-bit integers), as each shift reduces the number of significant bits by one." space_complexity: "O(1). We only use a constant number of variables (`shift` counter) regardless of input size." solutions: - approach_name: Bit Shifting is_optimal: true code: | def range_bitwise_and(left: int, right: int) -> int: # Count how many bits we need to shift off shift = 0 # Shift both numbers right until they're equal # This finds the common prefix while left < right: left >>= 1 right >>= 1 shift += 1 # Shift the common prefix back to its original position return left << shift explanation: | **Time Complexity:** O(log n) — At most 31 iterations for 32-bit integers. **Space Complexity:** O(1) — Only one counter variable used. We shift both numbers right until they match, counting the shifts. The remaining value is the common prefix. We then shift it back left to restore the original bit positions, filling the shifted positions with zeros. - approach_name: Brian Kernighan's Algorithm is_optimal: true code: | def range_bitwise_and(left: int, right: int) -> int: # Keep clearing the rightmost set bit of right # until right is <= left while right > left: # n & (n-1) clears the rightmost 1 bit right = right & (right - 1) # What remains is the common prefix return right explanation: | **Time Complexity:** O(log n) — Each iteration clears one bit, at most 31 bits. **Space Complexity:** O(1) — No additional space used. Brian Kernighan's bit trick: `n & (n - 1)` turns off the rightmost `1` bit. We repeatedly apply this to `right` until it's no longer greater than `left`. The remaining bits are exactly the common prefix that all numbers in the range share. - approach_name: Brute Force is_optimal: false code: | def range_bitwise_and(left: int, right: int) -> int: result = left # AND all numbers in the range for num in range(left + 1, right + 1): result &= num # Early exit if result becomes 0 if result == 0: break return result explanation: | **Time Complexity:** O(n) where n = right - left — Must iterate through entire range in worst case. **Space Complexity:** O(1) — Only tracking the running result. This approach directly computes the AND of all numbers. While correct, it's far too slow for large ranges. The early exit optimisation helps when the result reaches 0, but doesn't save us for ranges where the answer is non-zero. Included to illustrate why the bit manipulation approach is necessary.