diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java b/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java new file mode 100644 index 000000000000..8d2c757e5e0a --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java @@ -0,0 +1,63 @@ +package com.thealgorithms.bitmanipulation; + +/** + * Implementation to count number of bits to be flipped to convert A to B + * + * Problem: Given two numbers A and B, count the number of bits needed to be + * flipped to convert A to B. + * + * Example: + * A = 10 (01010 in binary) + * B = 20 (10100 in binary) + * XOR = 30 (11110 in binary) - positions where bits differ + * Answer: 4 bits need to be flipped + * + * Time Complexity: O(log n) - where n is the number of set bits + * Space Complexity: O(1) + * + *@author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class CountBitsFlip { + + private CountBitsFlip() { + throw new AssertionError("No instances."); + } + + /** + * Counts the number of bits that need to be flipped to convert a to b + * + * Algorithm: + * 1. XOR a and b to get positions where bits differ + * 2. Count the number of set bits in the XOR result + * 3. Use Brian Kernighan's algorithm: n & (n-1) removes rightmost set bit + * + * @param a the source number + * @param b the target number + * @return the number of bits to flip to convert A to B + */ + public static long countBitsFlip(long a, long b) { + int count = 0; + + // XOR gives us positions where bits differ + long xorResult = a ^ b; + + // Count set bits using Brian Kernighan's algorithm + while (xorResult != 0) { + xorResult = xorResult & (xorResult - 1); // Remove rightmost set bit + count++; + } + + return count; + } + + /** + * Alternative implementation using Long.bitCount(). + * + * @param a the source number + * @param b the target number + * @return the number of bits to flip to convert a to b + */ + public static long countBitsFlipAlternative(long a, long b) { + return Long.bitCount(a ^ b); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java new file mode 100644 index 000000000000..f33fa0fefbc0 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java @@ -0,0 +1,94 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Random; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for CountBitsFlip. + * Covers: + * - simple examples + * - zeros and identical values + * - negative numbers and two's complement edge cases + * - Long.MIN_VALUE / Long.MAX_VALUE + * - randomized consistency checks between two implementations + */ +@DisplayName("CountBitsFlip Tests") +class CountBitsFlipTest { + + @Test + @DisplayName("Example: A=10, B=20 => 4 bits") + void exampleTenTwenty() { + long a = 10L; + long b = 20L; + long expected = 4L; + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b), "Brian Kernighan implementation should return 4"); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b), "Long.bitCount implementation should return 4"); + } + + @Test + @DisplayName("Identical values => 0 bits") + void identicalValues() { + long a = 123456789L; + long b = 123456789L; + long expected = 0L; + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Both zeros => 0 bits") + void bothZeros() { + assertEquals(0L, CountBitsFlip.countBitsFlip(0L, 0L)); + assertEquals(0L, CountBitsFlip.countBitsFlipAlternative(0L, 0L)); + } + + @Test + @DisplayName("Small example: A=15 (1111), B=8 (1000) => 3 bits") + void smallExample() { + long a = 15L; // 1111 + long b = 8L; // 1000 + long expected = 3L; // differs in three low bits + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Negative values: -1 vs 0 => 64 bits (two's complement all ones)") + void negativeVsZero() { + long a = -1L; + long b = 0L; + long expected = 64L; // all 64 bits differ + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Long.MIN_VALUE vs Long.MAX_VALUE => 64 bits") + void minMaxLongs() { + long a = Long.MIN_VALUE; + long b = Long.MAX_VALUE; + long expected = 64L; // MAX ^ MIN yields all ones on 64-bit long + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Randomized consistency: both implementations agree across many pairs") + void randomizedConsistency() { + final int iterations = 1000; + final Random rnd = new Random(12345L); // deterministic seed for reproducibility + + for (int i = 0; i < iterations; i++) { + long a = rnd.nextLong(); + long b = rnd.nextLong(); + + long res1 = CountBitsFlip.countBitsFlip(a, b); + long res2 = CountBitsFlip.countBitsFlipAlternative(a, b); + + assertEquals(res2, res1, () -> String.format("Mismatch for a=%d, b=%d: impl1=%d, impl2=%d", a, b, res1, res2)); + } + } +}