Skip to content

Commit 9381161

Browse files
authored
feat: add Count Set Bits algorithm (#7072)
* feat: add Count Set Bits algorithm (issue #6931) * fix: correct CountSetBits algorithm logic * style: apply clang-format to CountSetBits files * fix: correct test expectations for CountSetBits * fix: correct test expectations for CountSetBits
1 parent 3979e82 commit 9381161

File tree

2 files changed

+100
-70
lines changed

2 files changed

+100
-70
lines changed
Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,79 @@
11
package com.thealgorithms.bitmanipulation;
22

3-
public class CountSetBits {
3+
/**
4+
* Utility class to count total set bits from 1 to N
5+
* A set bit is a bit in binary representation that is 1
6+
*
7+
* @author navadeep
8+
*/
9+
public final class CountSetBits {
10+
11+
private CountSetBits() {
12+
// Utility class, prevent instantiation
13+
}
414

515
/**
6-
* The below algorithm is called as Brian Kernighan's algorithm
7-
* We can use Brian Kernighan’s algorithm to improve the above naive algorithm’s performance.
8-
The idea is to only consider the set bits of an integer by turning off its rightmost set bit
9-
(after counting it), so the next iteration of the loop considers the next rightmost bit.
10-
11-
The expression n & (n-1) can be used to turn off the rightmost set bit of a number n. This
12-
works as the expression n-1 flips all the bits after the rightmost set bit of n, including the
13-
rightmost set bit itself. Therefore, n & (n-1) results in the last bit flipped of n.
14-
15-
For example, consider number 52, which is 00110100 in binary, and has a total 3 bits set.
16-
17-
1st iteration of the loop: n = 52
18-
19-
00110100 & (n)
20-
00110011 (n-1)
21-
~~~~~~~~
22-
00110000
16+
* Counts total number of set bits in all numbers from 1 to n
17+
* Time Complexity: O(log n)
18+
*
19+
* @param n the upper limit (inclusive)
20+
* @return total count of set bits from 1 to n
21+
* @throws IllegalArgumentException if n is negative
22+
*/
23+
public static int countSetBits(int n) {
24+
if (n < 0) {
25+
throw new IllegalArgumentException("Input must be non-negative");
26+
}
2327

28+
if (n == 0) {
29+
return 0;
30+
}
2431

25-
2nd iteration of the loop: n = 48
32+
// Find the largest power of 2 <= n
33+
int x = largestPowerOf2InNumber(n);
2634

27-
00110000 & (n)
28-
00101111 (n-1)
29-
~~~~~~~~
30-
00100000
35+
// Total bits at position x: x * 2^(x-1)
36+
int bitsAtPositionX = x * (1 << (x - 1));
3137

38+
// Remaining numbers after 2^x
39+
int remainingNumbers = n - (1 << x) + 1;
3240

33-
3rd iteration of the loop: n = 32
41+
// Recursively count for the rest
42+
int rest = countSetBits(n - (1 << x));
3443

35-
00100000 & (n)
36-
00011111 (n-1)
37-
~~~~~~~~
38-
00000000 (n = 0)
44+
return bitsAtPositionX + remainingNumbers + rest;
45+
}
3946

40-
* @param num takes Long number whose number of set bit is to be found
41-
* @return the count of set bits in the binary equivalent
42-
*/
43-
public long countSetBits(long num) {
44-
long cnt = 0;
45-
while (num > 0) {
46-
cnt++;
47-
num &= (num - 1);
47+
/**
48+
* Finds the position of the most significant bit in n
49+
*
50+
* @param n the number
51+
* @return position of MSB (0-indexed from right)
52+
*/
53+
private static int largestPowerOf2InNumber(int n) {
54+
int position = 0;
55+
while ((1 << position) <= n) {
56+
position++;
4857
}
49-
return cnt;
58+
return position - 1;
5059
}
5160

5261
/**
53-
* This approach takes O(1) running time to count the set bits, but requires a pre-processing.
62+
* Alternative naive approach - counts set bits by iterating through all numbers
63+
* Time Complexity: O(n log n)
5464
*
55-
* So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk.
56-
*
57-
* Then the range is from 0-255 (0 to 2^7).
58-
* So, we may need to count set bits from 0 to 255 in individual chunks.
59-
*
60-
* @param num takes a long number
61-
* @return the count of set bits in the binary equivalent
65+
* @param n the upper limit (inclusive)
66+
* @return total count of set bits from 1 to n
6267
*/
63-
public int lookupApproach(int num) {
64-
int[] table = new int[256];
65-
table[0] = 0;
66-
67-
for (int i = 1; i < 256; i++) {
68-
table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2
68+
public static int countSetBitsNaive(int n) {
69+
if (n < 0) {
70+
throw new IllegalArgumentException("Input must be non-negative");
6971
}
7072

71-
int res = 0;
72-
for (int i = 0; i < 4; i++) {
73-
res += table[num & 0xff];
74-
num >>= 8;
73+
int count = 0;
74+
for (int i = 1; i <= n; i++) {
75+
count += Integer.bitCount(i);
7576
}
76-
77-
return res;
77+
return count;
7878
}
7979
}
Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
11
package com.thealgorithms.bitmanipulation;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
45

56
import org.junit.jupiter.api.Test;
67

7-
public class CountSetBitsTest {
8+
class CountSetBitsTest {
89

910
@Test
10-
void testSetBits() {
11-
CountSetBits csb = new CountSetBits();
12-
assertEquals(1L, csb.countSetBits(16));
13-
assertEquals(4, csb.countSetBits(15));
14-
assertEquals(5, csb.countSetBits(10000));
15-
assertEquals(5, csb.countSetBits(31));
11+
void testCountSetBitsZero() {
12+
assertEquals(0, CountSetBits.countSetBits(0));
1613
}
1714

1815
@Test
19-
void testSetBitsLookupApproach() {
20-
CountSetBits csb = new CountSetBits();
21-
assertEquals(1L, csb.lookupApproach(16));
22-
assertEquals(4, csb.lookupApproach(15));
23-
assertEquals(5, csb.lookupApproach(10000));
24-
assertEquals(5, csb.lookupApproach(31));
16+
void testCountSetBitsOne() {
17+
assertEquals(1, CountSetBits.countSetBits(1));
18+
}
19+
20+
@Test
21+
void testCountSetBitsSmallNumber() {
22+
assertEquals(4, CountSetBits.countSetBits(3)); // 1(1) + 10(1) + 11(2) = 4
23+
}
24+
25+
@Test
26+
void testCountSetBitsFive() {
27+
assertEquals(7, CountSetBits.countSetBits(5)); // 1 + 1 + 2 + 1 + 2 = 7
28+
}
29+
30+
@Test
31+
void testCountSetBitsTen() {
32+
assertEquals(17, CountSetBits.countSetBits(10));
33+
}
34+
35+
@Test
36+
void testCountSetBitsLargeNumber() {
37+
assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42
38+
}
39+
40+
@Test
41+
void testCountSetBitsPowerOfTwo() {
42+
assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13
43+
}
44+
45+
@Test
46+
void testCountSetBitsNegativeInput() {
47+
assertThrows(IllegalArgumentException.class, () -> CountSetBits.countSetBits(-1));
48+
}
49+
50+
@Test
51+
void testNaiveApproachMatchesOptimized() {
52+
for (int i = 0; i <= 100; i++) {
53+
assertEquals(CountSetBits.countSetBitsNaive(i), CountSetBits.countSetBits(i), "Mismatch at n = " + i);
54+
}
2555
}
2656
}

0 commit comments

Comments
 (0)