Files
codetutor/backend/data/questions/number-of-1-bits.yaml
2025-05-25 12:43:25 +01:00

184 lines
7.7 KiB
YAML

title: Number of 1 Bits
slug: number-of-1-bits
difficulty: easy
leetcode_id: 191
leetcode_url: https://leetcode.com/problems/number-of-1-bits/
categories:
- math
patterns: []
function_signature: "def hamming_weight(n: int) -> int:"
test_cases:
visible:
- input: { n: 11 }
expected: 3
- input: { n: 128 }
expected: 1
- input: { n: 1 }
expected: 1
hidden:
- input: { n: 7 }
expected: 3
- input: { n: 255 }
expected: 8
- input: { n: 16 }
expected: 1
description: |
Given a positive integer `n`, write a function that returns the number of **set bits** in its binary representation (also known as the [Hamming weight](http://en.wikipedia.org/wiki/Hamming_weight)).
A *set bit* is a bit that has a value of `1`.
constraints: |
- `1 <= n <= 2^31 - 1`
examples:
- input: "n = 11"
output: "3"
explanation: "The binary representation of 11 is `1011`, which has three set bits."
- input: "n = 128"
output: "1"
explanation: "The binary representation of 128 is `10000000`, which has one set bit."
- input: "n = 2147483645"
output: "30"
explanation: "The binary representation has 30 set bits (all bits except the 2nd position are 1)."
explanation:
intuition: |
Imagine each bit in the binary representation as a light switch — either on (`1`) or off (`0`). Your task is simply to count how many switches are in the "on" position.
The naive approach would be to check each of the 32 bit positions one by one. But there's a clever trick using bit manipulation that lets you count *only* the `1` bits, skipping all the `0`s entirely.
The key insight is the operation `n & (n - 1)`. Subtracting `1` from a number flips all the bits from the rightmost `1` bit to the end. When you AND the original number with `n - 1`, the rightmost `1` bit gets cleared. For example:
- `12` in binary is `1100`
- `11` in binary is `1011`
- `12 & 11 = 1000` — the rightmost `1` bit is gone!
By repeatedly clearing the rightmost `1` bit and counting how many times we do this, we get the exact count of set bits. This approach is proportional to the number of `1` bits, not the total number of bits.
approach: |
We solve this using the **Brian Kernighan's Algorithm**:
**Step 1: Initialise the counter**
- `count`: Set to `0` to track the number of set bits
&nbsp;
**Step 2: Loop while n is not zero**
- While `n > 0`:
- Apply `n = n & (n - 1)` — this clears the rightmost set bit
- Increment `count` by `1`
&nbsp;
**Step 3: Return the count**
- Return `count` which now holds the number of `1` bits
&nbsp;
This algorithm is elegant because it only iterates as many times as there are set bits. A number with just one `1` bit (like `128`) completes in one iteration, while a number with many `1` bits takes proportionally more.
common_pitfalls:
- title: Checking All 32 Bits
description: |
A common approach is to loop through all 32 bits and check each one:
```
for i in range(32):
if n & (1 << i):
count += 1
```
While this works and has O(1) time (constant 32 iterations), it's less efficient than the `n & (n - 1)` approach when there are few set bits.
For example, the number `128` has only one set bit. The bit-by-bit approach still checks all 32 positions, while Brian Kernighan's algorithm finishes in just one iteration.
wrong_approach: "Loop through all 32 bit positions"
correct_approach: "Use n & (n - 1) to clear bits one at a time"
- title: Using Right Shift with Signed Integers
description: |
In some languages, using `n >> 1` on a negative number (or treating the input as signed) can cause issues due to sign extension — the sign bit gets replicated.
Python handles arbitrary-precision integers, so this isn't an issue in Python. But in languages like C or Java, you'd want to use unsigned right shift (`>>>`) or work with unsigned types to avoid infinite loops with numbers that have the high bit set.
wrong_approach: "Using signed right shift on potentially negative values"
correct_approach: "Use unsigned operations or the n & (n - 1) trick"
- title: Forgetting Zero Has No Set Bits
description: |
While the problem states `n >= 1`, it's worth noting that `0` has zero set bits. If your algorithm starts with `count = 1` instead of `count = 0`, or doesn't handle the case where `n` is already zero, you could return incorrect results.
The `n & (n - 1)` approach handles this naturally — if `n = 0`, the loop never executes and returns `0`.
key_takeaways:
- "**Brian Kernighan's Algorithm**: `n & (n - 1)` clears the rightmost set bit — a fundamental bit manipulation trick"
- "**Efficiency proportional to set bits**: The algorithm runs in O(k) time where k is the number of `1` bits, not the total bit width"
- "**Hamming weight applications**: Counting set bits is used in error detection, cryptography, and similarity measures between binary strings"
- "**Follow-up optimisation**: For repeated calls, precompute a lookup table for bytes (256 entries) and process the number 8 bits at a time"
time_complexity: "O(k) where k is the number of set bits. Each iteration clears exactly one `1` bit, so we loop at most k times (maximum 31 for a 32-bit integer)."
space_complexity: "O(1). We only use a single counter variable regardless of the input size."
solutions:
- approach_name: Brian Kernighan's Algorithm
is_optimal: true
code: |
def hamming_weight(n: int) -> int:
# Count of set bits
count = 0
while n:
# n & (n - 1) clears the rightmost set bit
# This works because n - 1 flips all bits from
# the rightmost 1 to the end
n = n & (n - 1)
count += 1
return count
explanation: |
**Time Complexity:** O(k) — where k is the number of set bits (at most 31).
**Space Complexity:** O(1) — only a counter variable.
The trick `n & (n - 1)` exploits the binary representation: subtracting 1 flips the rightmost `1` and all bits after it, so AND-ing with the original clears exactly that bit. We count how many times we can do this before `n` becomes zero.
- approach_name: Bit-by-Bit Check
is_optimal: false
code: |
def hamming_weight(n: int) -> int:
count = 0
# Check each of the 32 bit positions
while n:
# Check if the last bit is 1
count += n & 1
# Right shift to check the next bit
n >>= 1
return count
explanation: |
**Time Complexity:** O(log n) — or O(32) for a 32-bit integer.
**Space Complexity:** O(1) — only a counter variable.
This approach checks each bit by AND-ing with `1` and right-shifting. It always processes all bits of the number, unlike Brian Kernighan's algorithm which only iterates once per set bit.
- approach_name: Built-in Population Count
is_optimal: false
code: |
def hamming_weight(n: int) -> int:
# Python's bit_count() method (Python 3.10+)
return n.bit_count()
# Alternative using bin() string manipulation
# return bin(n).count('1')
explanation: |
**Time Complexity:** O(1) — built-in operations are highly optimised, often using CPU popcount instructions.
**Space Complexity:** O(1) — no additional space.
Modern Python (3.10+) provides `int.bit_count()` which uses optimised CPU instructions when available. The `bin(n).count('1')` alternative creates a string, making it less efficient. While practical for production code, understanding the underlying algorithms is valuable for interviews.