questions B (backspace - burst-balloons)

This commit is contained in:
2025-05-24 22:06:49 +01:00
parent 9eaafe4649
commit 1e0aebfbfd
67 changed files with 13945 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
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
&nbsp;
**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
&nbsp;
**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
&nbsp;
**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.