mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-12-19 07:00:35 +08:00
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
This commit is contained in:
committed by
GitHub
parent
3979e824b7
commit
93811614b8
@@ -1,79 +1,79 @@
|
||||
package com.thealgorithms.bitmanipulation;
|
||||
|
||||
public class CountSetBits {
|
||||
/**
|
||||
* Utility class to count total set bits from 1 to N
|
||||
* A set bit is a bit in binary representation that is 1
|
||||
*
|
||||
* @author navadeep
|
||||
*/
|
||||
public final class CountSetBits {
|
||||
|
||||
/**
|
||||
* The below algorithm is called as Brian Kernighan's algorithm
|
||||
* We can use Brian Kernighan’s algorithm to improve the above naive algorithm’s performance.
|
||||
The idea is to only consider the set bits of an integer by turning off its rightmost set bit
|
||||
(after counting it), so the next iteration of the loop considers the next rightmost bit.
|
||||
|
||||
The expression n & (n-1) can be used to turn off the rightmost set bit of a number n. This
|
||||
works as the expression n-1 flips all the bits after the rightmost set bit of n, including the
|
||||
rightmost set bit itself. Therefore, n & (n-1) results in the last bit flipped of n.
|
||||
|
||||
For example, consider number 52, which is 00110100 in binary, and has a total 3 bits set.
|
||||
|
||||
1st iteration of the loop: n = 52
|
||||
|
||||
00110100 & (n)
|
||||
00110011 (n-1)
|
||||
~~~~~~~~
|
||||
00110000
|
||||
|
||||
|
||||
2nd iteration of the loop: n = 48
|
||||
|
||||
00110000 & (n)
|
||||
00101111 (n-1)
|
||||
~~~~~~~~
|
||||
00100000
|
||||
|
||||
|
||||
3rd iteration of the loop: n = 32
|
||||
|
||||
00100000 & (n)
|
||||
00011111 (n-1)
|
||||
~~~~~~~~
|
||||
00000000 (n = 0)
|
||||
|
||||
* @param num takes Long number whose number of set bit is to be found
|
||||
* @return the count of set bits in the binary equivalent
|
||||
*/
|
||||
public long countSetBits(long num) {
|
||||
long cnt = 0;
|
||||
while (num > 0) {
|
||||
cnt++;
|
||||
num &= (num - 1);
|
||||
}
|
||||
return cnt;
|
||||
private CountSetBits() {
|
||||
// Utility class, prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* This approach takes O(1) running time to count the set bits, but requires a pre-processing.
|
||||
* Counts total number of set bits in all numbers from 1 to n
|
||||
* Time Complexity: O(log n)
|
||||
*
|
||||
* So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk.
|
||||
*
|
||||
* Then the range is from 0-255 (0 to 2^7).
|
||||
* So, we may need to count set bits from 0 to 255 in individual chunks.
|
||||
*
|
||||
* @param num takes a long number
|
||||
* @return the count of set bits in the binary equivalent
|
||||
* @param n the upper limit (inclusive)
|
||||
* @return total count of set bits from 1 to n
|
||||
* @throws IllegalArgumentException if n is negative
|
||||
*/
|
||||
public int lookupApproach(int num) {
|
||||
int[] table = new int[256];
|
||||
table[0] = 0;
|
||||
|
||||
for (int i = 1; i < 256; i++) {
|
||||
table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2
|
||||
public static int countSetBits(int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Input must be non-negative");
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
res += table[num & 0xff];
|
||||
num >>= 8;
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
// Find the largest power of 2 <= n
|
||||
int x = largestPowerOf2InNumber(n);
|
||||
|
||||
// Total bits at position x: x * 2^(x-1)
|
||||
int bitsAtPositionX = x * (1 << (x - 1));
|
||||
|
||||
// Remaining numbers after 2^x
|
||||
int remainingNumbers = n - (1 << x) + 1;
|
||||
|
||||
// Recursively count for the rest
|
||||
int rest = countSetBits(n - (1 << x));
|
||||
|
||||
return bitsAtPositionX + remainingNumbers + rest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the position of the most significant bit in n
|
||||
*
|
||||
* @param n the number
|
||||
* @return position of MSB (0-indexed from right)
|
||||
*/
|
||||
private static int largestPowerOf2InNumber(int n) {
|
||||
int position = 0;
|
||||
while ((1 << position) <= n) {
|
||||
position++;
|
||||
}
|
||||
return position - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative naive approach - counts set bits by iterating through all numbers
|
||||
* Time Complexity: O(n log n)
|
||||
*
|
||||
* @param n the upper limit (inclusive)
|
||||
* @return total count of set bits from 1 to n
|
||||
*/
|
||||
public static int countSetBitsNaive(int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Input must be non-negative");
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
count += Integer.bitCount(i);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
package com.thealgorithms.bitmanipulation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CountSetBitsTest {
|
||||
class CountSetBitsTest {
|
||||
|
||||
@Test
|
||||
void testSetBits() {
|
||||
CountSetBits csb = new CountSetBits();
|
||||
assertEquals(1L, csb.countSetBits(16));
|
||||
assertEquals(4, csb.countSetBits(15));
|
||||
assertEquals(5, csb.countSetBits(10000));
|
||||
assertEquals(5, csb.countSetBits(31));
|
||||
void testCountSetBitsZero() {
|
||||
assertEquals(0, CountSetBits.countSetBits(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBitsLookupApproach() {
|
||||
CountSetBits csb = new CountSetBits();
|
||||
assertEquals(1L, csb.lookupApproach(16));
|
||||
assertEquals(4, csb.lookupApproach(15));
|
||||
assertEquals(5, csb.lookupApproach(10000));
|
||||
assertEquals(5, csb.lookupApproach(31));
|
||||
void testCountSetBitsOne() {
|
||||
assertEquals(1, CountSetBits.countSetBits(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsSmallNumber() {
|
||||
assertEquals(4, CountSetBits.countSetBits(3)); // 1(1) + 10(1) + 11(2) = 4
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsFive() {
|
||||
assertEquals(7, CountSetBits.countSetBits(5)); // 1 + 1 + 2 + 1 + 2 = 7
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsTen() {
|
||||
assertEquals(17, CountSetBits.countSetBits(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsLargeNumber() {
|
||||
assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsPowerOfTwo() {
|
||||
assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountSetBitsNegativeInput() {
|
||||
assertThrows(IllegalArgumentException.class, () -> CountSetBits.countSetBits(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNaiveApproachMatchesOptimized() {
|
||||
for (int i = 0; i <= 100; i++) {
|
||||
assertEquals(CountSetBits.countSetBitsNaive(i), CountSetBits.countSetBits(i), "Mismatch at n = " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user