From 0906bf47aa51ec89105d1cc2b9fa3430af782b3c Mon Sep 17 00:00:00 2001 From: Navadeep0007 <2400030007@kluniversity.in> Date: Sat, 15 Nov 2025 23:45:17 +0530 Subject: [PATCH 1/5] feat: add Count Set Bits algorithm (issue #6931) --- .../bitmanipulation/CountSetBits.java | 114 +++++++++--------- .../bitmanipulation/CountSetBitsTest.java | 56 +++++++-- 2 files changed, 100 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java index 242f35fc35f2..237d1993b30d 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java @@ -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 Your GitHub Username + */ +public final class CountSetBits { + + private CountSetBits() { + // Utility class, prevent instantiation + } /** - * 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 + * Counts total number of set bits in all numbers from 1 to n + * Time Complexity: O(log n) + * + * @param n the upper limit (inclusive) + * @return total count of set bits from 1 to n + * @throws IllegalArgumentException if n is negative + */ + public static int countSetBits(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); + } + if (n == 0) { + return 0; + } - 2nd iteration of the loop: n = 48 + // Find the position of the most significant bit + int powerOf2 = largestPowerOf2(n); - 00110000 & (n) - 00101111 (n-1) - ~~~~~~~~ - 00100000 + // Count set bits at position powerOf2 + int bitsAtMsb = powerOf2 * (1 << (powerOf2 - 1)); + // Count remaining set bits from MSB position + int msbRemainder = n - (1 << powerOf2) + 1; - 3rd iteration of the loop: n = 32 + // Recursively count for remaining numbers + int rest = n - (1 << powerOf2); - 00100000 & (n) - 00011111 (n-1) - ~~~~~~~~ - 00000000 (n = 0) + return bitsAtMsb + msbRemainder + countSetBits(rest); + } - * @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); + /** + * Finds the position of the largest power of 2 less than or equal to n + * + * @param n the number + * @return position of largest power of 2 + */ + private static int largestPowerOf2(int n) { + int position = 0; + while ((1 << position) <= n) { + position++; } - return cnt; + return position - 1; } /** - * This approach takes O(1) running time to count the set bits, but requires a pre-processing. + * Alternative naive approach - counts set bits by iterating through all numbers + * Time Complexity: O(n 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 */ - 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 countSetBitsNaive(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; + int count = 0; + for (int i = 1; i <= n; i++) { + count += Integer.bitCount(i); } - - return res; + return count; } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java index 61e0757f9c12..e0fd0b2619cb 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java @@ -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(93, CountSetBits.countSetBits(20)); + } + + @Test + void testCountSetBitsPowerOfTwo() { + assertEquals(9, CountSetBits.countSetBits(8)); // 2^3 + } + + @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); + } } } From 0c838e8292e1fe5a8f61a769e9af5fb5e15fd700 Mon Sep 17 00:00:00 2001 From: Navadeep0007 <2400030007@kluniversity.in> Date: Sat, 15 Nov 2025 23:56:19 +0530 Subject: [PATCH 2/5] fix: correct CountSetBits algorithm logic --- .../bitmanipulation/CountSetBits.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java index 237d1993b30d..9f0802c5f25d 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java @@ -4,7 +4,7 @@ * Utility class to count total set bits from 1 to N * A set bit is a bit in binary representation that is 1 * - * @author Your GitHub Username + * @author navadeep */ public final class CountSetBits { @@ -29,28 +29,28 @@ public static int countSetBits(int n) { return 0; } - // Find the position of the most significant bit - int powerOf2 = largestPowerOf2(n); - - // Count set bits at position powerOf2 - int bitsAtMsb = powerOf2 * (1 << (powerOf2 - 1)); - - // Count remaining set bits from MSB position - int msbRemainder = n - (1 << powerOf2) + 1; - - // Recursively count for remaining numbers - int rest = n - (1 << powerOf2); - - return bitsAtMsb + msbRemainder + countSetBits(rest); + // 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 largest power of 2 less than or equal to n + * Finds the position of the most significant bit in n * * @param n the number - * @return position of largest power of 2 + * @return position of MSB (0-indexed from right) */ - private static int largestPowerOf2(int n) { + private static int largestPowerOf2InNumber(int n) { int position = 0; while ((1 << position) <= n) { position++; From 18ae4266e6b57b7f8943df302e10b845e64b3da6 Mon Sep 17 00:00:00 2001 From: Navadeep0007 <2400030007@kluniversity.in> Date: Sun, 16 Nov 2025 00:00:12 +0530 Subject: [PATCH 3/5] style: apply clang-format to CountSetBits files --- .../com/thealgorithms/bitmanipulation/CountSetBits.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java index 9f0802c5f25d..7df522ca8f69 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java @@ -31,16 +31,16 @@ public static int countSetBits(int n) { // 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; } From 65aa85c27f982773466acc5f15f2d97392b7e949 Mon Sep 17 00:00:00 2001 From: Navadeep0007 <2400030007@kluniversity.in> Date: Sun, 16 Nov 2025 00:08:28 +0530 Subject: [PATCH 4/5] fix: correct test expectations for CountSetBits --- .../com/thealgorithms/bitmanipulation/CountSetBitsTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java index e0fd0b2619cb..233a7f4fd331 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java @@ -34,12 +34,13 @@ void testCountSetBitsTen() { @Test void testCountSetBitsLargeNumber() { - assertEquals(93, CountSetBits.countSetBits(20)); + assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42 } + @Test void testCountSetBitsPowerOfTwo() { - assertEquals(9, CountSetBits.countSetBits(8)); // 2^3 + assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13 } @Test From 0ab23dc53e316f658b76c4336005e676fe574f88 Mon Sep 17 00:00:00 2001 From: Navadeep0007 <2400030007@kluniversity.in> Date: Sun, 16 Nov 2025 00:12:09 +0530 Subject: [PATCH 5/5] fix: correct test expectations for CountSetBits --- .../com/thealgorithms/bitmanipulation/CountSetBitsTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java index 233a7f4fd331..757c6edc0151 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java @@ -34,13 +34,12 @@ void testCountSetBitsTen() { @Test void testCountSetBitsLargeNumber() { - assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42 + assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42 } - @Test void testCountSetBitsPowerOfTwo() { - assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13 + assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13 } @Test