Skip to content

Coding patternsBlind 75

Counting Bits

Count the 1 bits of every number from 0 to n. Solved two ways, counting each number from scratch in O(n log n) and reusing the answer for i >> 1 in O(n), with a step-through, code in five languages and the Big O worked out from the code.

You are given a whole number limit. For every number i from 0 to limit, count the 1 bits in the binary form of i. Return the n + 1 counts as an array, where entry i is the count for i.

Example 1

Input: limit = 7

Output: [0, 1, 1, 2, 1, 2, 2, 3]

For example, 5 is 101, so entry 5 is 2. And 7 is 111, so entry 7 is 3.

bit_counts
0011122314252637

Example 2

Input: limit = 2

Output: [0, 1, 1]

0 has no 1 bits. 1 is 1 and 2 is 10, so each has one.

Example 3

Input: limit = 0

Output: [0]

Only the number 0, which has no 1 bits.

  • 0 ≤ limit ≤ 100,000. Below, n is limit.
Is 0 included?
Yes. The array has n + 1 entries, from 0 to n.
Does the output array count as extra memory?
No. We must return it anyway, so we only count memory on top of it.
Can we use a built-in bit count?
It works, but the follow-up asks for one pass in O(n) without it.

We need an answer for every number up to limit, and each number is close to a smaller one: i >> 1 is i with its last bit dropped. Building each answer from a smaller answer we already have is dynamic programming.

This problem has two approaches. A third one, using i & (i − 1), is also O(n) and works the same way.

Reuse i >> 1 O(n)

Shifting i right by one, i >> 1, drops its last bit. Every other bit stays a bit. So:

bit_counts[i] = bit_counts[i >> 1] + (i & 1)

i >> 1 is smaller than i, so its answer is already in the array when we reach i. Fill the array from 1 up to limit, starting from bit_counts[0] = 0.

bit_counts
0001020304050607
  1. bit_counts = [0] * (limit + 1)
  2. for i in range(1, limit + 1):
  3. bit_counts[i] = bit_counts[i >> 1] + (i & 1)
  4. return bit_counts

Shifting i right by one drops its last bit. So i has the 1 bits of i >> 1, plus 1 if its last bit is 1. bit_counts[0] = 0 to start.

1 / 9
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Reuse i >> 1: it is i without its last bit, and its answer is already known.
// One lookup per number: O(n) time, O(1) extra memory.
public int[] countBits(int limit) {
int[] bitCounts = new int[limit + 1];
for (int i = 1; i <= limit; i++) {
// Keep the parentheses: in most languages + binds tighter than &.
bitCounts[i] = bitCounts[i >> 1] + (i & 1);
}
return bitCounts;
}
}
Time O(n)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The lit line does one array read, one shift, one & and one add. That is 1 step. It sits inside one loop.

    1. bit_counts = [0] * (limit + 1)
    2. for i in range(1, limit + 1):
    3. bit_counts[i] = bit_counts[i >> 1] + (i & 1)
    4. return bit_counts
  2. 2 Count the runs on Example 1

    Example 1 has n = 7. The loop runs for i = 1 to 7. The table shows which entry each run reads and what it adds.

    7 runs.

    i1234567
    reads bit_counts[…]0112233
    adds1010101
    total
    7 runs
  3. 3 Write the count for any n

    The loop runs once per number from 1 to limit:

    n runs

    Check it with Example 1: n = 7, 7 runs, against 12 inner runs for counting each number.

  4. 4 Keep only the part that grows fastest

    There is nothing to drop. The count is already n.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    Besides the output, the code keeps only i. The extra space is O(1).

    bit_counts (the output)i

2,000 random limits from 0 to 70, which cover 0, 1 and the powers of two where the count drops back to 1. Count each number and reuse i >> 1 gave the same answer every time.

Largest inputsResult
limit = 100,000 (the most allowed). Reuse i >> 1.0.004 s, and about 0.8 MB, all of it the answer list of 100,001 counts. Nothing else grows.
limit = 100,000. Count each number.0.05 s, about 10 times slower: it counts up to 17 bits for each number.