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] - input: { n: 3 } expected: [0, 1, 1, 2] - input: { n: 15 } expected: [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] - input: { n: 4 } expected: [0, 1, 1, 2, 1] 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.