The Hamming weight of a number is the number of set bits (1-bits) in its binary representation. Given a positive integer n, return an array where the ith element is the Hamming weight of integer i for all integers from 0 to n.
Input: n = 7
Output: [0, 1, 1, 2, 1, 2, 2, 3]
Explanation:
| Number | Binary representation | Number of set bits |
| 0 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 10 | 1 |
| 3 | 11 | 2 |
| 4 | 100 | 1 |
| 5 | 101 | 2 |
| 6 | 110 | 2 |
| 7 | 111 | 3 |
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
A number in binary is a row of light switches, each either on (1) or off (0). Its Hamming weight is simply how many are on. The trick n & (n - 1) flips off the lowest on-switch, so counting how many times you can do that before the row goes dark counts the on-bits directly.
Removing the last bit of x (i.e. x >> 1) gives a smaller number we have already solved. So the count for x is the count for x >> 1 plus one more if x's last bit is set: dp[x] = dp[x >> 1] + (x & 1).
The most straightforward strategy is to individually count the number of bits for each number from 0 to n.
Consider a number x = 25 and its binary representation:
To count the number of set bits (1s) in a number, we can check each bit and increase a count whenever we find a set bit. Let’s see how this works.
For starters, we can determine the least significant bit (LSB) of x by performing x & 1, which masks all bits of x except the LSB: if x & 1 == 1, the LSB is 1. Otherwise, it’s 0. We can see this below for x = 25:
Now, how do we check the next bit? If we perform a bitwise right-shift operation on x, we shift all bits of x one position to the right. This effectively makes this next bit the new LSB:
We now have a process we can repeat to count the number of set bits in a number:
x & 1 == 1, increment our count. Otherwise, don’t.x.Continue with the two steps above until x equals 0, indicating there are no more set bits to count. Doing this for every number from 0 to n provides the answer.
from typing import List
def hamming_weights_of_integers(n: int) -> List[int]:
return [count_set_bits(x) for x in range(n + 1)]
def count_set_bits(x: int) -> int:
count = 0
# Count each set bit of 'x' until 'x' equals 0.
while x > 0:
# Increment the count if the LSB is 1.
count += x & 1
# Right shift 'x' to shift the next bit to the LSB position.
x >>= 1
return countint countSetBits(int x) {
int count = 0;
while (x > 0) {
count += x & 1; // add 1 if the LSB is set
x >>= 1; // drop the LSB
}
return count;
}
vector<int> hammingWeightsOfIntegers(int n) {
vector<int> res;
for (int x = 0; x <= n; x++) {
res.push_back(countSetBits(x));
}
return res;
}int[] hammingWeightsOfIntegers(int n) {
int[] res = new int[n + 1];
for (int x = 0; x <= n; x++) {
res[x] = countSetBits(x);
}
return res;
}
int countSetBits(int x) {
int count = 0;
while (x > 0) {
count += x & 1; // add 1 if the LSB is set
x >>= 1; // drop the LSB
}
return count;
}Time complexity: The time complexity of hamming_weights_of_integers is because for each integer from 0 to , counting the number of set bits takes logarithmic time, as there are approximately bits in that number. If we assume all integers have 32 bits, the time complexity simplifies to just , since counting the set bits for a number will take at most 32 steps, which we do for numbers.
Space complexity: The space complexity is because no extra space is used except the space occupied by the output.
In the previous approach, it's important to note that by the time we reach integer x, we have already computed the result for all integers from 0 to x - 1. If we find a way to leverage these previous results, we can improve the efficiency of constructing the output array.
It would be wise to find a way to take advantage of some optimal substructure by treating the results from integers 0 to x - 1 as potential subproblems of x. This is the beginning of a DP solution. Let dp[x] represent the number of set bits in integer x.
A predictable way to access a subproblem of dp[x] is to right-shift x by 1, effectively removing its LSB. This is the subproblem dp[x >> 1], and the only difference between this and dp[x] is the LSB which was just removed.
As mentioned earlier, the LSB of x can be found using x & 1. Therefore:
If the LSB of x is 0, there is no difference in the number of bits between x and x >> 1:
If the LSB of x is 1, the difference in the number of bits between x and x >> 1 is 1:
Therefore, we can obtain dp[x] by using the result of dp[x >> 1] and adding the LSB to it:
dp[x] = dp[x >> 1] + (x & 1)
Now, we just need to know what our base case is.
Base case
The simplest version of this problem is when n is 0. In this case, there are no set bits, so the number of set bits is 0. We can apply this base case by setting dp[0] to 0.
After the base case is set, we populate the rest of the DP array by applying our formula from dp[1] to dp[n]. The answer to the problem is then just the values in the DP array, containing the number of set bits for each number from 0 to n.
from typing import List
def hamming_weights_of_integers_dp(n: int) -> List[int]:
# Base case: the number of set bits in 0 is just 0. We set dp[0] to 0 by
# initializing the entire DP array to 0.
dp = [0] * (n + 1)
for x in range(1, n + 1):
# 'dp[x]' is obtained using the result of 'dp[x >> 1]', plus the LSB of 'x'.
dp[x] = dp[x >> 1] + (x & 1)
return dpvector<int> hammingWeightsOfIntegersDp(int n) {
// dp[x] = number of set bits in x. Base case dp[0] = 0.
vector<int> dp(n + 1, 0);
for (int x = 1; x <= n; x++) {
dp[x] = dp[x >> 1] + (x & 1);
}
return dp;
}int[] hammingWeightsOfIntegersDp(int n) {
// dp[x] = number of set bits in x. Base case dp[0] = 0.
int[] dp = new int[n + 1];
for (int x = 1; x <= n; x++) {
dp[x] = dp[x >> 1] + (x & 1);
}
return dp;
}Time complexity: The time complexity of hamming_weights_of_integers_dp is since we populate each element of the DP array once.
Space complexity: The space complexity is because no extra space is used, aside from the space taken up by the output, which is the DP array in this case.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
| x | x>>1 | x&1 | dp[x] |
|---|---|---|---|
| 1 | 0 | 1 | 0 + 1 = 1 |
| 2 | 1 | 0 | 1 + 0 = 1 |
| 3 | 1 | 1 | 1 + 1 = 2 |
| 4 | 2 | 0 | 1 + 0 = 1 |
| 5 | 2 | 1 | 1 + 1 = 2 |
| 6 | 3 | 0 | 2 + 0 = 2 |
| 7 | 3 | 1 | 2 + 1 = 3 |
Result [0, 1, 1, 2, 1, 2, 2, 3]. ✓
The array has a single slot; the loop from 1 to n never runs, so we return the base case [0] directly. No shifting, no bit tests.
Output [0]. ✗