Counting Bits
Problem
Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1's in the binary representation of i.
Examples
Example 1:
Input: n = 2
Output: [0,1,1]
Explanation:
0 --> 0
1 --> 1
2 --> 10
Example 2:
Input: n = 5
Output: [0,1,1,2,1,2]
Explanation:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
Solution
Video explanation
Here is the video explaining below methods in detail. Please check it out:
<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/Qt9At02GGWc" frameborder="0" allowfullscreen></iframe></div>
Method 1 - Naive
Here is the naive approach:
- Loop through all integers from
0ton. - For each integer, count the number of 1's in its binary representation using repeated division by 2 (to extract each bit).
- Store the count for the integer in the result array.
Code
Java
class Solution {
public int[] countBits(int n) {
int[] ans = new int[n + 1];
for (int i = 0; i <= n; i++) {
ans[i] = countOnes(i);
}
return ans;
}
private int countOnes(int x) {
int count = 0;
while (x != 0) {
if (x % 2 == 1) {
count++;
}
x /= 2; // Move to the next bit
}
return count;
}
}
Python
class Solution:
def countBits(self, n: int) -> List[int]:
ans: List[int] = [0] * (n + 1)
for i in range(n + 1):
ans[i] = self._count_ones(i)
return ans
def _count_ones(self, x: int) -> int:
count: int = 0
while x != 0:
if x % 2 == 1:
count += 1
x //= 2 # Move to the next bit
return count
Complexity
- ⏰ Time complexity:
O(n log n), log N time to findcountOnesas we divide number logN times and we have to do it for n numbers. - 🧺 Space complexity:
O(1)
Method 2 - DP following power of 2
This method focuses on leveraging the properties of powers of 2. Every power of 2 (2^m) has exactly one set bit in its binary representation at mth position. Non-power-of-2 numbers can be expressed as:
i = 2^m + r
where r is the remainder. By observing this, we can relate the set bits of these numbers to their remainder.
Now, suppose n = 10. For that lets look at the numbers from 1 to 10:
| 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 |
| 8 | 1000 | 1 |
| 9 | 1001 | 2 |
| 10 | 1010 | 2 |
From the table, we can see the pattern emerge for calculating the count of set bits (dp[i]) for each number.
So, dp[0] = 0 is the base case.
Here’s how the recurrence relation applies:
dp[0] = 0(base case)dp[1] = dp[0] + 1 ⇨ 1 + dp[i - 1]dp[2] = dp[0] + 1 ⇨ 1 + dp[i - 2]dp[3] = dp[1] + 1 ⇨ 1 + dp[i - 2]dp[4] = dp[0] + 1 ⇨ 1 + dp[i - 4]dp[5] = dp[1] + 1 ⇨ 1 + dp[i - 4]dp[6] = dp[2] + 1 ⇨ 1 + dp[i - 4]dp[7] = dp[3] + 1 ⇨ 1 + dp[i - 4]dp[8] = dp[0] + 1 ⇨ 1 + dp[i - 8]dp[9] = dp[1] + 1 ⇨ 1 + dp[i - 8]dp[10] = dp[2] + 1 ⇨ 1 + dp[i - 8]
The offset corresponds to the nearest power of 2 for each number. As soon as we reach the next power of 2 (2, 4, 8, ...), we update the offset to that power of 2. This avoids recalculating results unnecessarily.
Using this insight, we can iterate through numbers from 1 to n (starting with the base case dp[0] = 0) and compute the count of set bits for each number using this recurrence formula:
Where offset is the largest power of 2 less than or equal to i.
Approach
- Use dynamic programming to compute set bits iteratively by tracking the
offset, which is the largest power of 2 less than or equal to the current number. - For each number
i, use the recurrence relation:
Here, `offset` corresponds to `2^m`, the largest power of 2 encountered so far.
- Incrementally compute results by reusing those from smaller subproblems (numbers closer to powers of 2).
Code
Java
class Solution {
public int[] countBits(int n) {
int[] dp = new int[n + 1];
int offset = 1;
for (int i = 1; i < n + 1; i++){
if (offset * 2 == i){
offset *= 2;
}
dp[i] = dp[i - offset] + 1;
}
return dp;
}
}
Python
class Solution:
def countBits(self, n: int) -> List[int]:
dp: List[int] = [0] * (n + 1)
offset: int = 1
for i in range(1, n + 1):
if offset * 2 == i:
offset *= 2
dp[i] = dp[i - offset] + 1
return dp
Complexity
- ⏰ Time complexity:
O(n). The algorithm iterates through numbers from1ton, performing a constant-time calculation for each number based on the recurrence relation. Thus, the time complexity isO(n). - 🧺 Space complexity:
O(n). The algorithm uses an arraydpof sizen + 1to store the set bit counts for each number. Hence, the space complexity isO(n).
Method 3 - Using DP based on division by 2
We can refine the solution further by focusing solely on how the number x changes after dividing the number by 2 which is same as right shifting the number by 1.
Lets take 2 numbers x and y where x / 2 = y. Now, there are 2 main cases.
- Case 1:
xis Even- When
xis even, its least significant bit (LSB) is0. - Dividing
xby 2 (or right-shifting it) givesy, which has the same number of set bits asx. - For example:
x = 6 (110)andy = 3 (011)6 / 2 = 3- Both
6and3have the same number of set bits (2 set bits in this case).
- When
- Case 2:
xis Odd- When
xis odd, its least significant bit (LSB) is1. - Dividing
xby 2 (or right-shifting it) givesy, and the number of set bits inxis1more than iny. - For example:
x = 7 (111)andy = 3 (011)7 / 2 = 3Number of set bits in 7 = 3, whileNumber of set bits in 3 = 2.
- When
From these cases, we can define the recurrence relation as:
This uses simple arithmetic to determine whether the least significant bit contributes an additional 1.
Code
Java
class Solution {
public int[] countBits(int n) {
int[] dp = new int[n + 1]; // Array to store the number of 1's for each number
for (int i = 1; i <= n; i++) {
// If odd: Add 1 to the count of set bits of i / 2
// If even: The count is the same as i / 2
dp[i] = dp[i / 2] + (i % 2);
}
return dp;
}
}
Python
class Solution:
def countBits(self, n: int) -> List[int]:
dp: List[int] = [0] * (n + 1) # Array to store the results
for i in range(1, n + 1):
# If odd: Add 1 to the count of set bits of i // 2
# If even: The count is the same as i // 2
dp[i] = dp[i / 2] + (i % 1)
return dp
Complexity
- ⏰ Time complexity:
O(n). The loop runs from1ton, and each computation(bitwise AND and right shift) takes constant time (O(1)). - 🧺 Space complexity:
O(n). We maintain an array to store the results for all numbers up ton.
Method 4 - Using DP based on division by 2 but fully bitwise
Alternatively, as shown in the Number of 1 Bits problem, we can express this with binary operators for efficiency:
i >> 1: This shifts the numberito the right by 1 bit (essentially dividingiby 2), leveraging previously computed results.(i & 1): Checks if the last bit ofiis 1 (which checks whetheriis odd).
So,
- If
iis even: There is no additional1in its binary representation compared toi >> 1. - If
iis odd: There is exactly one additional1compared toi >> 1.
Approach
dp[0] = 0(base case, 0 has no set bits).- For each number
ifrom 1 ton, computedp[i]using the transition function above.
Code
Java
class Solution {
public int[] countBits(int n) {
int[] ans = new int[n + 1]; // DP table to store number of set bits
for (int i = 1; i <= n; i++) {
// Transition: ans[i >> 1] gives the count for i // 2, and (i & 1) adds 1 if i is odd
dp[i] = dp[i >> 1] + (i & 1);
}
return ans;
}
}
Python
class Solution:
def countBits(self, n: int) -> List[int]:
ans: List[int] = [0] * (n + 1) # DP table to store number of set bits
for i in range(1, n + 1):
# Transition: ans[i // 2] gives the count for i >> 1, and (i & 1) adds 1 if i is odd
dp[i] = dp[i >> 1] + (i & 1)
return ans
Complexity
- ⏰ Time complexity:
O(n). The loop runs forncomputations, and each computation takes constant time, hence it isO(n). - 🧺 Space complexity:
O(n)used bydparray.