questions C
This commit is contained in:
181
backend/data/questions/counting-bits.yaml
Normal file
181
backend/data/questions/counting-bits.yaml
Normal file
@@ -0,0 +1,181 @@
|
||||
title: Counting Bits
|
||||
slug: counting-bits
|
||||
difficulty: easy
|
||||
leetcode_id: 338
|
||||
leetcode_url: https://leetcode.com/problems/counting-bits/
|
||||
categories:
|
||||
- arrays
|
||||
- dynamic-programming
|
||||
- math
|
||||
patterns:
|
||||
- dynamic-programming
|
||||
|
||||
function_signature: "def count_bits(n: int) -> list[int]:"
|
||||
|
||||
test_cases:
|
||||
visible:
|
||||
- input: { n: 2 }
|
||||
expected: [0, 1, 1]
|
||||
- input: { n: 5 }
|
||||
expected: [0, 1, 1, 2, 1, 2]
|
||||
- input: { n: 0 }
|
||||
expected: [0]
|
||||
hidden:
|
||||
- input: { n: 1 }
|
||||
expected: [0, 1]
|
||||
- input: { n: 7 }
|
||||
expected: [0, 1, 1, 2, 1, 2, 2, 3]
|
||||
- input: { n: 10 }
|
||||
expected: [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2]
|
||||
|
||||
description: |
|
||||
Given an integer `n`, return an array `ans` of length `n + 1` such that for each `i` (`0 <= i <= n`), `ans[i]` is the **number of `1`'s** in the binary representation of `i`.
|
||||
|
||||
constraints: |
|
||||
- `0 <= n <= 10^5`
|
||||
|
||||
examples:
|
||||
- input: "n = 2"
|
||||
output: "[0, 1, 1]"
|
||||
explanation: "0 → 0 (zero 1's), 1 → 1 (one 1), 2 → 10 (one 1)"
|
||||
- input: "n = 5"
|
||||
output: "[0, 1, 1, 2, 1, 2]"
|
||||
explanation: "0 → 0, 1 → 1, 2 → 10, 3 → 11, 4 → 100, 5 → 101"
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine you're counting the number of `1` bits in binary for every number from `0` to `n`. The naive approach would be to convert each number to binary and count — but there's a beautiful pattern hiding in the numbers themselves.
|
||||
|
||||
Consider the relationship between a number and its half. When you divide a number by 2 (right shift), you simply remove the last bit. For example:
|
||||
- `5` in binary is `101` (two 1's)
|
||||
- `5 >> 1 = 2` in binary is `10` (one 1)
|
||||
|
||||
The only difference is whether the **last bit** (least significant bit) was a `1` or `0`. So if you already know how many 1's are in `n // 2`, you just need to check if `n` is odd (has a `1` in the last position).
|
||||
|
||||
This gives us the recurrence: `countBits(i) = countBits(i >> 1) + (i & 1)`
|
||||
|
||||
Think of it like building up your answers: once you know the bit count for smaller numbers, you can instantly compute it for larger ones by leveraging the relationship between a number and its right-shifted version.
|
||||
|
||||
approach: |
|
||||
We solve this using **Dynamic Programming** with a bit manipulation insight:
|
||||
|
||||
**Step 1: Create the result array**
|
||||
|
||||
- `ans`: Array of size `n + 1` initialised with zeros
|
||||
- `ans[0] = 0` since zero has no `1` bits (base case)
|
||||
|
||||
|
||||
|
||||
**Step 2: Build up using the recurrence relation**
|
||||
|
||||
- For each `i` from `1` to `n`:
|
||||
- `ans[i] = ans[i >> 1] + (i & 1)`
|
||||
- `i >> 1`: Right shift gives us the number with the last bit removed
|
||||
- `i & 1`: Checks if the current number is odd (last bit is `1`)
|
||||
|
||||
|
||||
|
||||
**Step 3: Return the result**
|
||||
|
||||
- Return `ans` containing the bit count for every number from `0` to `n`
|
||||
|
||||
|
||||
|
||||
The key insight is that `i >> 1` is always less than `i` (for `i > 0`), so we've already computed its answer. This allows us to solve the problem in a single pass with O(1) work per number.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Using Built-in Popcount for Each Number
|
||||
description: |
|
||||
A common first approach is to use `bin(i).count('1')` or `__builtin_popcount(i)` for each number from `0` to `n`.
|
||||
|
||||
While this works, it has **O(log i)** time per number (since each number has up to `log(i)` bits), giving overall **O(n log n)** time complexity.
|
||||
|
||||
The problem specifically asks for an O(n) solution, which requires recognising the DP pattern.
|
||||
wrong_approach: "Loop with bin(i).count('1') for each i"
|
||||
correct_approach: "Use DP recurrence: ans[i] = ans[i >> 1] + (i & 1)"
|
||||
|
||||
- title: Missing the Bit Shift Relationship
|
||||
description: |
|
||||
It's tempting to look for patterns like "powers of 2" or "consecutive numbers", but these don't lead to an elegant O(n) solution.
|
||||
|
||||
The key insight is that `i` and `i >> 1` differ by exactly one bit operation. Every bit in `i >> 1` was already in `i` (just shifted right), and we only need to account for the rightmost bit we "lost".
|
||||
|
||||
For example: `6 = 110` and `6 >> 1 = 3 = 11`. The bit counts are 2 and 2 respectively. Adding `6 & 1 = 0` gives us `2 + 0 = 2`. ✓
|
||||
wrong_approach: "Looking for complex mathematical patterns"
|
||||
correct_approach: "Recognise i >> 1 has same bits minus the LSB"
|
||||
|
||||
- title: Off-by-One in Array Size
|
||||
description: |
|
||||
The problem asks for numbers `0` through `n` inclusive, which means `n + 1` total numbers.
|
||||
|
||||
Creating an array of size `n` instead of `n + 1` will miss the last element or cause an index error.
|
||||
wrong_approach: "ans = [0] * n"
|
||||
correct_approach: "ans = [0] * (n + 1)"
|
||||
|
||||
key_takeaways:
|
||||
- "**Bit manipulation + DP**: Combining bit operations with dynamic programming often reveals elegant solutions"
|
||||
- "**Right shift insight**: `i >> 1` removes the last bit, so `countBits(i) = countBits(i >> 1) + (i & 1)`"
|
||||
- "**O(n) vs O(n log n)**: Recognising subproblem relationships can reduce complexity from O(n log n) to O(n)"
|
||||
- "**Build from smaller to larger**: Classic DP pattern — use already-computed answers for smaller inputs"
|
||||
|
||||
time_complexity: "O(n). We compute the answer for each number from `0` to `n` in constant time using the recurrence."
|
||||
space_complexity: "O(n). We store `n + 1` values in the result array (required by the problem output)."
|
||||
|
||||
solutions:
|
||||
- approach_name: Dynamic Programming with Bit Shift
|
||||
is_optimal: true
|
||||
code: |
|
||||
def count_bits(n: int) -> list[int]:
|
||||
# Result array: ans[i] will hold count of 1's in binary of i
|
||||
ans = [0] * (n + 1)
|
||||
|
||||
for i in range(1, n + 1):
|
||||
# i >> 1 removes last bit, i & 1 checks if last bit is 1
|
||||
# Since i >> 1 < i, we've already computed ans[i >> 1]
|
||||
ans[i] = ans[i >> 1] + (i & 1)
|
||||
|
||||
return ans
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Single pass from 1 to n with O(1) work each.
|
||||
|
||||
**Space Complexity:** O(n) — The output array of size n + 1.
|
||||
|
||||
This solution uses the recurrence `ans[i] = ans[i >> 1] + (i & 1)`. Right-shifting removes the last bit, and `i & 1` tells us if that bit was a `1`. Since we process numbers in order, `i >> 1` is always already computed.
|
||||
|
||||
- approach_name: Dynamic Programming with Last Set Bit
|
||||
is_optimal: true
|
||||
code: |
|
||||
def count_bits(n: int) -> list[int]:
|
||||
# Result array initialised with zeros
|
||||
ans = [0] * (n + 1)
|
||||
|
||||
for i in range(1, n + 1):
|
||||
# i & (i - 1) removes the rightmost set bit
|
||||
# So we add 1 to the count of the number without that bit
|
||||
ans[i] = ans[i & (i - 1)] + 1
|
||||
|
||||
return ans
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Single pass with O(1) work per number.
|
||||
|
||||
**Space Complexity:** O(n) — The output array.
|
||||
|
||||
Alternative DP approach using `i & (i - 1)`, which clears the rightmost `1` bit. For example, `6 & 5 = 110 & 101 = 100 = 4`. Since `i & (i - 1) < i`, we've already computed its bit count, and we just add `1` for the bit we removed.
|
||||
|
||||
- approach_name: Built-in Popcount
|
||||
is_optimal: false
|
||||
code: |
|
||||
def count_bits(n: int) -> list[int]:
|
||||
ans = []
|
||||
|
||||
for i in range(n + 1):
|
||||
# Convert to binary string and count '1' characters
|
||||
ans.append(bin(i).count('1'))
|
||||
|
||||
return ans
|
||||
explanation: |
|
||||
**Time Complexity:** O(n log n) — For each number, counting bits takes O(log i) time.
|
||||
|
||||
**Space Complexity:** O(n) — The output array.
|
||||
|
||||
This straightforward approach uses Python's built-in functions but doesn't achieve the O(n) time requested in the follow-up. Useful for understanding the problem but not optimal.
|
||||
Reference in New Issue
Block a user