Editorial
Core insight
Counting Bits · Bit Manipulation Patterns
Core Insight for Counting Bits
The key intuition for the LeetCode 338 Solution lies in observing how the binary representation changes as numbers increase. Specifically, consider the relationship between a number and (integer division).
In binary, performing integer division by 2 is equivalent to a right shift operation (>> 1).
- If we have a number , which is 13.
- Right shifting it gives , which is 6.
Notice that contains all the set bits of , plus potentially one more bit at the Least Significant Bit (LSB) position.
- If is even, the LSB is 0. The number of set bits in is exactly the same as in .
- If is odd, the LSB is 1. The number of set bits in is the number of set bits in plus 1.
This allows us to formulate a recurrence relation (transition equation) suitable for Dynamic Programming:
Here, i >> 1 looks up the result for a smaller number that has already been computed, and i & 1 checks if the current number is odd (adding 1 if true, 0 if false).
Visual Description:
Imagine filling the array from left to right. To find the value for index 14 (1110), you look at index 7 (111). Since 14 is 7 shifted left with a 0 appended, they have the same bit count. To find the value for index 15 (1111), you look at index 7 (111) and add 1 because 15 is 7 shifted left with a 1 appended. The algorithm builds the solution based on this "prefix" property of binary numbers.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 338: Counting Bits Solution & Explanation
Problem Overview
TL;DR: The optimal solution utilizes dynamic programming to generate bit counts in time by reusing the count of set bits from the number i / 2.
The LeetCode 338 problem, titled Counting Bits, asks us to compute the number of 1s (set bits) in the binary representation for every integer from 0 to n. Instead of returning a single value, we must return an array of length n + 1 where the index i stores the population count (Hamming weight) of the integer i.
This is a fundamental problem for understanding how bitwise operations can be combined with dynamic programming to optimize runtime from superlinear to linear.
Brute Force Approach for Counting Bits
The most straightforward way to solve this problem is to iterate through every number from 0 to n and count the bits for each number individually.
For a specific integer k, we can count bits by repeatedly checking the least significant bit (LSB) and right-shifting the number until it becomes 0.
Pseudo-code:
function countBits(n):
result = []
for i from 0 to n:
count = 0
num = i
while num > 0:
if num is odd:
count = count + 1
num = num >> 1
result.append(count)
return resultWhy this fails (or is suboptimal): While this approach produces the correct result, it performs redundant calculations. The time complexity is because counting bits for a number takes time (proportional to the number of bits). The problem specifically requests a linear time solution, . In a strict interview setting, the brute force approach demonstrates a lack of insight into the relationship between binary representations of consecutive or related numbers.
Algorithm Strategy: Bit Manipulation Patterns
We implement this using a single pass Dynamic Programming approach.
- State Definition: Let
ansbe an array of sizen + 1.ans[i]represents the number of set bits in the integeri. - Base Case:
ans[0] = 0. The number 0 has zero set bits. - Recurrence Relation: For every from 1 to , calculate
ans[i]using the value stored atans[i >> 1](which isans[i // 2]).ans[i] = ans[i >> 1] + (i & 1)
- Iteration: Iterate linearly from 1 to . Since
i >> 1is strictly less thani, the value needed for the calculation is guaranteed to be available in theansarray.
This strategy ensures we strictly satisfy the time complexity constraint and use no built-in bit counting functions.
Execution Flow
Let's trace the algorithm for n = 5. We initialize ans array of size 6 with ans[0] = 0.
-
i = 1 (Binary
1):- Calculate
1 >> 1which is0. - Check LSB
1 & 1which is1. ans[1] = ans[0] + 1=>0 + 1 = 1.- Array:
[0, 1, ...]
- Calculate
-
i = 2 (Binary
10):- Calculate
2 >> 1which is1. - Check LSB
2 & 1which is0. ans[2] = ans[1] + 0=>1 + 0 = 1.- Array:
[0, 1, 1, ...]
- Calculate
-
i = 3 (Binary
11):- Calculate
3 >> 1which is1. - Check LSB
3 & 1which is1. ans[3] = ans[1] + 1=>1 + 1 = 2.- Array:
[0, 1, 1, 2, ...]
- Calculate
-
i = 4 (Binary
100):- Calculate
4 >> 1which is2. - Check LSB
4 & 1which is0. ans[4] = ans[2] + 0=>1 + 0 = 1.- Array:
[0, 1, 1, 2, 1, ...]
- Calculate
-
i = 5 (Binary
101):- Calculate
5 >> 1which is2. - Check LSB
5 & 1which is1. ans[5] = ans[2] + 1=>1 + 1 = 2.- Array:
[0, 1, 1, 2, 1, 2]
- Calculate
The final output is [0, 1, 1, 2, 1, 2].
Proof of Correctness
The correctness relies on the inductive property of the integers.
- Base Case: For , the number of bits is 0. The array is initialized correctly.
- Inductive Step: Assume that for all ,
ans[k]correctly contains the bit count of .- Consider . The value
i >> 1(integer division by 2) is strictly less than for all . Therefore,ans[i >> 1]is already computed and correct by our assumption. - The binary representation of consists of the bits of shifted to the left, plus the LSB of .
- Therefore, the count of set bits in is strictly the count of set bits in plus the LSB.
- The formula
ans[i >> 1] + (i & 1)captures this logic perfectly.
- Consider . The value
Pattern Reuse Notes
The Bitwise DP pattern and the specific logic used here (relating a number to its shifted version) is useful in several other scenarios:
- Reverse Bits: Understanding how bits shift is crucial for reversing bits in or steps.
- Integer Replacement: Problems involving transforming integers using operations like division by 2 often utilize similar recursive structures or bitwise logic.
- Gray Code: Generating Gray Codes relies heavily on understanding how bit patterns change between adjacent indices, often using XOR and shifts similar to the logic found here.
While no other problems in the current curriculum map identically to this subpattern, mastering the i vs i >> 1 relationship is foundational for advanced bit manipulation questions.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 338
1class Solution {
2public:
3 vector<int> countBits(int n) {
4 // Initialize vector of size n + 1 with 0s
5 vector<int> ans(n + 1, 0);
6
7 // Iterate from 1 to n to fill the DP table
8 for (int i = 1; i <= n; ++i) {
9 // relation: ans[i] = ans[i / 2] + (i % 2)
10 // implemented using bitwise operators for efficiency
11 ans[i] = ans[i >> 1] + (i & 1);
12 }
13
14 return ans;
15 }
16};Java Solution for LeetCode 338
1class Solution {
2 public int[] countBits(int n) {
3 // Initialize result array of size n + 1
4 int[] ans = new int[n + 1];
5
6 // ans[0] is implicitly 0, so we start from 1
7 for (int i = 1; i <= n; i++) {
8 // ans[i >> 1] retrieves the count for i / 2
9 // (i & 1) checks if the current number is odd
10 ans[i] = ans[i >> 1] + (i & 1);
11 }
12
13 return ans;
14 }
15}Python Solution for LeetCode 338
1class Solution:
2 def countBits(self, n: int) -> List[int]:
3 # Initialize DP array with 0 for the base case
4 ans = [0] * (n + 1)
5
6 # Iterate from 1 to n
7 for i in range(1, n + 1):
8 # i >> 1 is equivalent to i // 2
9 # i & 1 is equivalent to i % 2
10 ans[i] = ans[i >> 1] + (i & 1)
11
12 return ans