Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Return an array where ans[i] is the number of set bits in i, for all i from 0 to n. The naive answer popcounts each number independently in O(n log n). The optimal answer reuses earlier results: dp[i] = dp[i >> 1] + (i & 1), giving O(n) time and O(1) extra work per number.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can spot the overlapping subproblem hiding in the binary representation — that bits(i) relates to bits(i/2) — and turn an O(n log n) popcount loop into an O(n) DP.
Where candidates slip
Stopping at the per-number popcount (correct but O(n log n)), or deriving a recurrence but mis-handling the base case dp[0] = 0 or the odd-bit correction.
How to stand out
Derive the recurrence from the binary structure out loud (right-shift removes the lowest bit; i & 1 restores it), mention the alternative dp[i & (i-1)] + 1, and state extra space is O(1) since the array is the required output.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewCount bits for every i in 0..nCount each number independently?YesNo, reuseExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Per-Number Popcount (Brian Kernighan)
1Hint 1Explore hintHide hint
You already know how to count the set bits of one number. What if you just do that for every i from 0 to n?
2Hint 2Explore hintHide hint
For each i, run Brian Kernighan's n & (n - 1) loop to count its bits and store the result at ans[i].
3Hint 3Explore hintHide hint
Each number costs O(number of set bits) ≤ O(log i), and there are n numbers → O(n log n) overall.
A · Core idea
Solve every index independently: for each i from 0 to n, count its set bits with Brian Kernighan's i & (i - 1) loop and store that in ans[i].
Why this is the honest starting point. It composes a tool we already trust (single-number popcount) over a range. There's no insight required — it's a loop wrapped around a loop — which is exactly why it's the right thing to state first before optimising.
Why it's O(n log n) and not O(n). Each number takes time proportional to its number of set bits, which is at most log i + 1. Adding that up over 0..n gives roughly n log n total operations. The follow-up explicitly asks you to remove that log factor, which is the cue to look for shared structure between consecutive numbers.
Why we don't ship it. It's correct and passes, but it throws away an obvious relationship: the bit count of i is almost the bit count of a smaller number we just computed. Recomputing from scratch each time is the waste the DP eliminates.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 5i=0
0 has no set bits
ans = [0][0, 1, 1, 2, 1, 2]C · Code with teaching comments
Reference implementation previewpublic int[] countBits(int n) { int[] ans = new int[n + 1]; for (int i = 0; i <= n; i++) { int x = i, count = 0; while (x != 0) {Explore the full codeHide the full code
D · Why it works and complexityO(n log n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Doing independent work per number is the classic 'no memoisation' smell. The moment you notice consecutive numbers share most of their bits, you should suspect a recurrence — that's the jump to the DP.
ComplexityO(n log n) time, O(1) space — For each of the n + 1 numbers we run a popcount loop that costs up to O(log i) (number of set bits). Summed across the range that's O(n log n) time; only the output array, no extra space.
E · Edge cases and pitfalls5n = 0 — Loop runs once for i = 0; inner while never executes → ans = [0].Explore edge casesHide edge cases
Edge cases handled
n = 0Loop runs once for i = 0; inner while never executes → ans = [0].n = 1ans[0] = 0, ans[1] = 1 → [0, 1].
Pitfalls
- Mutating i directly instead of a copy — you'd lose the loop index.
- Calling a builtin popcount and claiming O(n) — true with hardware popcount, but it dodges the follow-up's intent to show a recurrence.
- Off-by-one on the array size — it must be n + 1, not n.
Approach 2 of 2 · Optimal
DP — Reuse the Right-Shifted Result (Optimal)
1Hint 1Explore hintHide hint
How does the binary form of i relate to the binary form of i / 2 (i.e. i >> 1)?
2Hint 2Explore hintHide hint
i >> 1 is i with its lowest bit removed. So i has all the set bits of i >> 1, plus possibly the one bit you removed.
3Hint 3Explore hintHide hint
That removed bit is exactly i & 1. So dp[i] = dp[i >> 1] + (i & 1), and i >> 1 < i means it's already computed.
A · Core idea
Fill the answer array left to right. For each i, look up the already-computed count for i >> 1 and add the lowest bit i & 1.
Why `i >> 1` is the subproblem. Right-shifting by one deletes the lowest bit of i — it's i with its last binary digit chopped off. Crucially i >> 1 < i, so by the time we reach i we have already filled dp[i >> 1]. The number i therefore inherits every set bit of i >> 1.
Why we add `i & 1`. The only bit i >> 1 doesn't account for is the one we chopped off — the lowest bit of i. That bit is 1 when i is odd and 0 when even, which is exactly i & 1. So the full count is bits(i >> 1) + (i & 1). Concretely, 6 = 110 shifts to 3 = 11 (two set bits) and 6 & 1 = 0, giving 2; 7 = 111 shifts to the same 3 = 11 but 7 & 1 = 1, giving 3.
Why the base case is `dp[0] = 0`. Zero has no set bits, and every recurrence eventually bottoms out at it (repeated halving reaches 0). Initialising the array to zeros gives this for free, so the loop can start at i = 1.
An equivalent recurrence worth naming. dp[i] = dp[i & (i - 1)] + 1: i & (i - 1) clears the lowest set bit, producing a smaller solved number, and we add 1 for the bit we cleared. Either recurrence is O(1) per element — mention both to show range.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 5Interactive diagram
Each number reuses its right-shifted self
i=1dp[i] points back to dp[i >> 1], a smaller index already solved; the label adds (i & 1).
[0, 1, 1, 2, 1, 2]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The whole speedup comes from noticing that binary numbers nest: every number is a smaller number with one extra bit on the end. Once you see bit count as 'count of the prefix + the last bit', the O(n) DP writes itself. This 'derive the big case from a structurally smaller, already-solved case' move is the essence of DP.
ComplexityO(n) time, O(1) space — Each index does O(1) work — one array lookup, one shift, one AND, one add. Across n + 1 indices that's O(n) time; the only storage is the required output array, so O(1) extra space.
E · Edge cases and pitfalls5n = 0 — Loop body never runs; dp stays [0]. The zero-initialised array is already the answer.Explore edge casesHide edge cases
Edge cases handled
n = 0Loop body never runs; dp stays [0]. The zero-initialised array is already the answer.n = 8dp[8] = dp[4] + 0 = 1 (8 = 1000). Powers of two correctly resolve to a single set bit via the chain 8→4→2→1.
Pitfalls
- Forgetting dp[0] = 0 base case — though zero-initialising the array handles it implicitly.
- Writing dp[i] = dp[i >> 1] + 1 unconditionally — you must add (i & 1), which is 0 for even i.
- Using the recurrence with i decreasing or referencing a not-yet-filled index — always shift toward smaller, already-computed indices.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[] countBits(int n) { int[] dp = new int[n + 1]; for (int i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); }Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Adding 1 instead of (i & 1)
Only odd numbers gain a set bit over their right-shifted half. Adding 1 for even i overcounts every even index.
Explore the code comparisonHide the code comparison
Adding 1 instead of (i & 1)
Only odd numbers gain a set bit over their right-shifted half. Adding 1 for even i overcounts every even index.
Don't write this
for i in range(1, n + 1):
dp[i] = dp[i >> 1] + 1 # wrong: even i adds a phantom bitWrite this instead
for i in range(1, n + 1):
dp[i] = dp[i >> 1] + (i & 1) # add the actual lowest bit2Off-by-one array size
The output must cover 0..n inclusive — that's n + 1 entries. Sizing it n drops the answer for i = n.
Explore the code comparisonHide the code comparison
Off-by-one array size
The output must cover 0..n inclusive — that's n + 1 entries. Sizing it n drops the answer for i = n.
Don't write this
int[] dp = new int[n]; // missing index n
for (int i = 1; i <= n; i++) {
dp[i] = dp[i >> 1] + (i & 1); // ArrayIndexOutOfBounds at i = n
}Write this instead
int[] dp = new int[n + 1]; // covers 0..n
for (int i = 1; i <= n; i++) {
dp[i] = dp[i >> 1] + (i & 1);
}Revise
Carry the pattern into the interview
Pattern to remember
DP over bit structure (offline popcount)
Code pattern to memorize
dp[0] = 0
for i in 1..n:
dp[i] = dp[i >> 1] + (i & 1)
return dpRules to remember
- 1
i >> 1drops the lowest bit — it'siwith its last digit chopped off, a smaller index already solved. - 2
i & 1is the bit you chopped: 1 ifiis odd, 0 if even. - 3So
bits(i) = bits(i / 2) + (last bit)— one O(1) recurrence reuses every earlier answer. - 4Alternative recurrence:
dp[i] = dp[i & (i - 1)] + 1(Kernighan: clear lowest set bit, add 1). - 5The output array of size n + 1 is the answer, not auxiliary space — extra space is O(1).
Use this pattern when
- You need popcount for a whole range 0..n, not a single number
- A bigger number's bit count can be built from a smaller, already-computed one
- The interviewer asks you to beat the obvious 'call popcount n times'
- Any problem where dropping/clearing one bit yields a solved subproblem
Choose another pattern when
- You only need the count for one number → Brian Kernighan, no array (Number of 1 Bits)
- You need the actual bits, not the count → shift-and-mask each position
- n is tiny → the O(n log n) per-number popcount is fine and simpler to write
- Memory is the constraint and you can't even store the output → reconsider the problem
Interview takeaway
Counting Bits is the gateway 'bit DP' problem: the count for i is the count for a smaller number (i >> 1) plus one cheap correction. The pattern — solve a number by reusing a structurally-smaller one — is the same instinct behind every overlapping-subproblem DP.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternBit DP / offline popcount. Recognise it when you need set-bit counts for a whole range and a larger number's answer can be built from a structurally smaller one (i >> 1 or i & (i - 1)). It's the bit-manipulation face of overlapping-subproblem DP.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Number of 1 BitsThe single-number building block: Brian Kernighan's n & (n - 1) popcount.
- Hamming DistanceComing soonPopcount of a ^ b. Same counting machinery on the XOR of two numbers.
- Number of Steps to Reduce a Number to ZeroComing soonHalve if even, subtract 1 if odd — steps = bits + (highest bit position). Same shift/odd structure.
- Total Hamming DistanceComing soonSum pairwise bit differences column by column — count set bits per position across all numbers.