Counting Bits asks you to count the set bits of every number from 0 to n. The naive answer counts each number from scratch; the elegant answer notices that the count for i is hiding inside a number you already solved.
Problem. Given an integer n, return an array ans of length n + 1 where ans[i] is the number
of 1-bits in the binary representation of i.
Example: n = 8 → [0, 1, 1, 2, 1, 2, 2, 3, 1] (e.g. 5 is 101, which has two 1-bits).
The slow way first
The obvious idea: for each i from 0 to n, count its bits with a small loop (peel off the lowest bit until the number is zero). Counting one number takes up to O(log i) steps, so the whole thing is O(n log n).
The question to ask: while I am computing the answer for i, have I already computed something almost identical? It turns out I have — the answer for a smaller number is one shift away.
The idea: reuse the number with its low bit removed
Take any i. If you drop its lowest bit with a right shift, you get i >> 1 — a smaller number we filled in earlier. Dropping that one bit changes the count by exactly the bit you dropped, which is i & 1 (1 if i is odd, 0 if even). So:
dp[i] = dp[i >> 1] + (i & 1)Because i >> 1 is always smaller than i, its answer is already in the array by the time we need it. Every value costs O(1).
Walk through it
Step through the animation. The pointer i moves left to right filling dp. At each stop, the cell that lights up to the left is dp[i >> 1] — the value being reused. For example at i = 5, 5 >> 1 = 2, so we read dp[2] = 1 and add 5 & 1 = 1 to get dp[5] = 2.
Pseudocode
make an array dp of length n + 1, all zeros
dp[0] = 0 # zero has no set bits
for i from 1 to n:
dp[i] = dp[i >> 1] + (i & 1) # reuse a smaller answer, add the low bit
return dpThe Python solution
def count_bits(n):
dp = [0] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
# i >> 1 drops the low bit; (i & 1) adds it back
dp[i] = dp[i >> 1] + (i & 1)
return dpdpis the answer array;dp[i]will hold the set-bit count ofi.dp[0] = 0is the base case — zero has no1-bits and seeds every later value.i >> 1isiwith its lowest bit removed, a strictly smaller index we already filled.i & 1is that lowest bit:1wheniis odd,0when even.- Line 6 is the whole algorithm — one
O(1)recurrence per number.
Complexity
| Case | Time | Notes |
|---|---|---|
| Count each number alone | O(n log n) (moderate) | log i bits per number |
| DP recurrence (this solution) | O(n) (moderate) | one O(1) step per number |
O(n) (moderate)We spend O(n) extra space for the dp array (which is also the required output), and fill it in a single linear pass. The trick — express the answer for i in terms of a smaller, already-solved subproblem — is the essence of dynamic programming.
When this pattern shows up
When a problem asks you to compute a value for every input up to n, look for a recurrence that
links each answer to a smaller one. Bit tricks like i >> 1 and i & 1 turn "recompute from scratch"
into "reuse and adjust," which is exactly what DP rewards.
Make sure the value you depend on is already filled. Here i >> 1 is always less than i, so iterating
i upward guarantees dp[i >> 1] exists. If your recurrence pointed to a larger index, the array would
not be ready yet.
Practice
Using dp[i] = dp[i >> 1] + (i & 1), what is dp[6]? Which earlier value does it reuse?
1. Why does dp[i] = dp[i >> 1] + (i & 1) work?
2. What does (i & 1) evaluate to?
3. Why must we iterate i from low to high?
4. What is the time complexity of this DP solution?