From 6c8de5608776ac5f2c3be9fa1a09282057989382 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 9 Sep 2025 07:31:44 +0200
Subject: [PATCH 001/203] chore(deps): bump gitpod/workspace-java-21 from
2025-06-18-16-47-14 to 2025-08-25-18-17-39 (#6523)
chore(deps): bump gitpod/workspace-java-21
Bumps gitpod/workspace-java-21 from 2025-06-18-16-47-14 to 2025-08-25-18-17-39.
---
updated-dependencies:
- dependency-name: gitpod/workspace-java-21
dependency-version: 2025-08-25-18-17-39
dependency-type: direct:production
...
Signed-off-by: dependabot[bot] Example: Input: [7, 2, 9, 4, 1] Output: [1, 2, 4, 7, 9]
+ *
+ * Time Complexity:
+ * - Inserting n elements into the PriorityQueue → O(n log n)
+ * - Polling n elements → O(n log n)
+ * - Total: O(n log n)
+ *
+ * Space Complexity: O(n) for the PriorityQueue
+ *
+ * @see
+ * Heap / PriorityQueue
+ */
+public final class PriorityQueueSort {
+
+ // Private constructor to prevent instantiation (utility class)
+ private PriorityQueueSort() {
+ }
+
+ /**
+ * Sorts the given array in ascending order using a PriorityQueue.
+ *
+ * @param arr the array to be sorted
+ * @return the sorted array (in-place)
+ */
+ public static int[] sort(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ return arr;
+ }
+
+ PriorityQueue This class provides a fast binary (Stein's) GCD implementation for {@code long}
+ * inputs and a BigInteger-backed API for full 2's-complement range support (including
+ * {@code Long.MIN_VALUE}). The {@code long} implementation is efficient and avoids
+ * division/modulo operations. For edge-cases that overflow signed-64-bit ranges
+ * (e.g., gcd(Long.MIN_VALUE, 0) = 2^63), use the BigInteger API {@code gcdBig}.
+ *
+ * Behaviour:
+ * Handles negative inputs. If either input is {@code Long.MIN_VALUE} the
+ * method delegates to the BigInteger implementation and will throw {@link ArithmeticException}
+ * if the result cannot be represented as a signed {@code long}.
+ *
+ * @param a first value (may be negative)
+ * @param b second value (may be negative)
+ * @return non-negative gcd as a {@code long}
+ * @throws ArithmeticException when the exact gcd does not fit into a signed {@code long}
+ */
+ public static long gcd(long a, long b) {
+ // Trivial cases
+ if (a == 0L) {
+ return absOrThrowIfOverflow(b);
+ }
+ if (b == 0L) {
+ return absOrThrowIfOverflow(a);
+ }
+
+ // If either is Long.MIN_VALUE, absolute value doesn't fit into signed long.
+ if (a == Long.MIN_VALUE || b == Long.MIN_VALUE) {
+ // Delegate to BigInteger and try to return a long if it fits
+ BigInteger g = gcdBig(BigInteger.valueOf(a), BigInteger.valueOf(b));
+ return g.longValueExact();
+ }
+
+ // Work with non-negative long values now (safe because we excluded Long.MIN_VALUE)
+ a = (a < 0) ? -a : a;
+ b = (b < 0) ? -b : b;
+
+ // Count common factors of 2
+ int commonTwos = Long.numberOfTrailingZeros(a | b);
+
+ // Remove all factors of 2 from a
+ a >>= Long.numberOfTrailingZeros(a);
+
+ while (b != 0L) {
+ // Remove all factors of 2 from b
+ b >>= Long.numberOfTrailingZeros(b);
+
+ // Now both a and b are odd. Ensure a <= b
+ if (a > b) {
+ long tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // b >= a; subtract a from b (result is even)
+ b = b - a;
+ }
+
+ // Restore common powers of two
+ return a << commonTwos;
+ }
+
+ /**
+ * Helper to return absolute value of x unless x == Long.MIN_VALUE, in which
+ * case we delegate to BigInteger and throw to indicate overflow.
+ */
+ private static long absOrThrowIfOverflow(long x) {
+ if (x == Long.MIN_VALUE) {
+ // |Long.MIN_VALUE| = 2^63 which does not fit into signed long
+ throw new ArithmeticException("Absolute value of Long.MIN_VALUE does not fit into signed long. Use gcdBig() for full-range support.");
+ }
+ return (x < 0) ? -x : x;
+ }
+
+ /**
+ * Computes GCD for an array of {@code long} values. Returns 0 for empty/null arrays.
+ * If any intermediate gcd cannot be represented in signed long (rare), an ArithmeticException
+ * will be thrown.
+ */
+ public static long gcd(long... values) {
+
+ if (values == null || values.length == 0) {
+ return 0L;
+ }
+ long result = values[0];
+ for (int i = 1; i < values.length; i++) {
+ result = gcd(result, values[i]);
+ if (result == 1L) {
+ return 1L; // early exit
+ }
+ }
+ return result;
+ }
+
+ /**
+ * BigInteger-backed gcd that works for the full integer range (and beyond).
+ * This is the recommended method when inputs may be Long.MIN_VALUE or when you
+ * need an exact result even if it is greater than Long.MAX_VALUE.
+ * @param a first value (may be negative)
+ * @param b second value (may be negative)
+ * @return non-negative gcd as a {@link BigInteger}
+ */
+ public static BigInteger gcdBig(BigInteger a, BigInteger b) {
+
+ if (a == null || b == null) {
+ throw new NullPointerException("Arguments must not be null");
+ }
+ return a.abs().gcd(b.abs());
+ }
+
+ /**
+ * Convenience overload that accepts signed-64 inputs and returns BigInteger gcd.
+ */
+ public static BigInteger gcdBig(long a, long b) {
+ return gcdBig(BigInteger.valueOf(a), BigInteger.valueOf(b));
+ }
+
+ /**
+ * int overload for convenience.
+ */
+ public static int gcd(int a, int b) {
+ return (int) gcd((long) a, (long) b);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java
new file mode 100644
index 000000000000..207fdc1e57a0
--- /dev/null
+++ b/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java
@@ -0,0 +1,110 @@
+package com.thealgorithms.bitmanipulation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.math.BigInteger;
+import org.junit.jupiter.api.Test;
+
+public class BitwiseGCDTest {
+
+ @Test
+ public void testGcdBasic() {
+ assertEquals(6L, BitwiseGCD.gcd(48L, 18L));
+ }
+
+ @Test
+ public void testGcdZeroAndNonZero() {
+ assertEquals(5L, BitwiseGCD.gcd(0L, 5L));
+ assertEquals(5L, BitwiseGCD.gcd(5L, 0L));
+ }
+
+ @Test
+ public void testGcdBothZero() {
+ assertEquals(0L, BitwiseGCD.gcd(0L, 0L));
+ }
+
+ @Test
+ public void testGcdNegativeInputs() {
+ assertEquals(6L, BitwiseGCD.gcd(-48L, 18L));
+ assertEquals(6L, BitwiseGCD.gcd(48L, -18L));
+ assertEquals(6L, BitwiseGCD.gcd(-48L, -18L));
+ }
+
+ @Test
+ public void testGcdIntOverload() {
+ assertEquals(6, BitwiseGCD.gcd(48, 18));
+ }
+
+ @Test
+ public void testGcdArray() {
+ long[] values = {48L, 18L, 6L};
+ assertEquals(6L, BitwiseGCD.gcd(values));
+ }
+
+ @Test
+ public void testGcdEmptyArray() {
+ long[] empty = {};
+ assertEquals(0L, BitwiseGCD.gcd(empty));
+ }
+
+ @Test
+ public void testGcdCoprime() {
+ assertEquals(1L, BitwiseGCD.gcd(17L, 13L));
+ }
+
+ @Test
+ public void testGcdPowersOfTwo() {
+ assertEquals(1024L, BitwiseGCD.gcd(1L << 20, 1L << 10));
+ }
+
+ @Test
+ public void testGcdLargeNumbers() {
+ assertEquals(6L, BitwiseGCD.gcd(270L, 192L));
+ }
+
+ @Test
+ public void testGcdEarlyExitArray() {
+ long[] manyCoprimes = {7L, 11L, 13L, 17L, 19L, 23L, 29L};
+ assertEquals(1L, BitwiseGCD.gcd(manyCoprimes));
+ }
+
+ @Test
+ public void testGcdSameNumbers() {
+ assertEquals(42L, BitwiseGCD.gcd(42L, 42L));
+ }
+
+ @Test
+ public void testGcdLongMinValueBigInteger() {
+ // gcd(Long.MIN_VALUE, 0) = |Long.MIN_VALUE| = 2^63; must use BigInteger to represent it
+ BigInteger expected = BigInteger.ONE.shiftLeft(63); // 2^63
+ assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, 0L));
+ }
+
+ @Test
+ public void testGcdLongMinValueLongOverloadThrows() {
+ // The long overload cannot return 2^63 as a positive signed long, so it must throw
+ assertThrows(ArithmeticException.class, () -> BitwiseGCD.gcd(Long.MIN_VALUE, 0L));
+ }
+
+ @Test
+ public void testGcdWithLongMinAndOther() {
+ // gcd(Long.MIN_VALUE, 2^10) should be 2^10
+ long p = 1L << 10;
+ BigInteger expected = BigInteger.valueOf(p);
+ assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, p));
+ }
+
+ @Test
+ public void testGcdWithBothLongMin() {
+ // gcd(Long.MIN_VALUE, Long.MIN_VALUE) = 2^63
+ BigInteger expected = BigInteger.ONE.shiftLeft(63);
+ assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, Long.MIN_VALUE));
+ }
+
+ @Test
+ public void testGcdEdgeCasesMixed() {
+ assertEquals(1L, BitwiseGCD.gcd(1L, Long.MAX_VALUE));
+ assertEquals(1L, BitwiseGCD.gcd(Long.MAX_VALUE, 1L));
+ }
+}
From ef681e844de46e2b2ce57170c67ea6dc1f52c0be Mon Sep 17 00:00:00 2001
From: Milad Sadeghi
+ * Example:
+ * Input: "abc", "12345"
+ * Output: "a1b2c345"
+ *
+ * Input: "abcd", "12"
+ * Output: "a1b2cd"
+ *
+ * @author Milad Sadeghi
+ */
+public final class AlternativeStringArrange {
+
+ // Private constructor to prevent instantiation
+ private AlternativeStringArrange() {
+ }
+
+ /**
+ * Arranges two strings by alternating their characters.
+ *
+ * @param firstString the first input string
+ * @param secondString the second input string
+ * @return a new string with characters from both strings arranged alternately
+ */
+ public static String arrange(String firstString, String secondString) {
+ StringBuilder result = new StringBuilder();
+ int length1 = firstString.length();
+ int length2 = secondString.length();
+ int minLength = Math.min(length1, length2);
+
+ for (int i = 0; i < minLength; i++) {
+ result.append(firstString.charAt(i));
+ result.append(secondString.charAt(i));
+ }
+
+ if (length1 > length2) {
+ result.append(firstString.substring(minLength));
+ } else if (length2 > length1) {
+ result.append(secondString.substring(minLength));
+ }
+
+ return result.toString();
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java
new file mode 100644
index 000000000000..9e8ae9e9f153
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java
@@ -0,0 +1,23 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class AlternativeStringArrangeTest {
+
+ // Method to provide test data
+ private static Stream The input graph is represented as an adjacency list where {@code adjacency.get(u)} returns the
+ * set of vertices adjacent to {@code u}. The algorithm runs in time proportional to the number of
+ * maximal cliques produced and is widely used for clique enumeration problems.
+ * The algorithm runs in O(V * E^2) time and is a specific implementation of the Ford–Fulkerson
+ * method where the augmenting paths are found using breadth-first search (BFS) to ensure the
+ * shortest augmenting paths (in terms of the number of edges) are used.
+ * The graph is represented with a capacity matrix where {@code capacity[u][v]} denotes the
+ * capacity of the edge from {@code u} to {@code v}. Negative capacities are not allowed. This class supports conversions between the following units:
+ * The conversion is based on predefined constants in seconds.
+ * Results are rounded to three decimal places for consistency.
+ *
+ * This class is final and cannot be instantiated.
+ *
+ * @see Wikipedia: Unit of time
+ */
+public final class TimeConverter {
+
+ private TimeConverter() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Supported time units with their equivalent in seconds.
+ */
+ private enum TimeUnit {
+ SECONDS(1.0),
+ MINUTES(60.0),
+ HOURS(3600.0),
+ DAYS(86400.0),
+ WEEKS(604800.0),
+ MONTHS(2629800.0), // 30.44 days
+ YEARS(31557600.0); // 365.25 days
+
+ private final double seconds;
+
+ TimeUnit(double seconds) {
+ this.seconds = seconds;
+ }
+
+ public double toSeconds(double value) {
+ return value * seconds;
+ }
+
+ public double fromSeconds(double secondsValue) {
+ return secondsValue / seconds;
+ }
+ }
+
+ private static final Map This class provides methods to calculate:
+ * This class is final and cannot be instantiated.
+ *
+ * @see Wikipedia: Persistence of a number
+ */
+public final class NumberPersistence {
+
+ // Private constructor to prevent instantiation
+ private NumberPersistence() {
+ }
+
+ /**
+ * Calculates the multiplicative persistence of a given number.
+ *
+ * Multiplicative persistence is the number of steps required to reduce a number to a single digit
+ * by multiplying its digits repeatedly.
+ *
+ * @param num the number to calculate persistence for; must be non-negative
+ * @return the multiplicative persistence of the number
+ * @throws IllegalArgumentException if the input number is negative
+ */
+ public static int multiplicativePersistence(int num) {
+ if (num < 0) {
+ throw new IllegalArgumentException("multiplicativePersistence() does not accept negative values");
+ }
+
+ int steps = 0;
+ while (num >= 10) {
+ int product = 1;
+ int temp = num;
+ while (temp > 0) {
+ product *= temp % 10;
+ temp /= 10;
+ }
+ num = product;
+ steps++;
+ }
+ return steps;
+ }
+
+ /**
+ * Calculates the additive persistence of a given number.
+ *
+ * Additive persistence is the number of steps required to reduce a number to a single digit
+ * by summing its digits repeatedly.
+ *
+ * @param num the number to calculate persistence for; must be non-negative
+ * @return the additive persistence of the number
+ * @throws IllegalArgumentException if the input number is negative
+ */
+ public static int additivePersistence(int num) {
+ if (num < 0) {
+ throw new IllegalArgumentException("additivePersistence() does not accept negative values");
+ }
+
+ int steps = 0;
+ while (num >= 10) {
+ int sum = 0;
+ int temp = num;
+ while (temp > 0) {
+ sum += temp % 10;
+ temp /= 10;
+ }
+ num = sum;
+ steps++;
+ }
+ return steps;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java b/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java
new file mode 100644
index 000000000000..3f7e70167fc2
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java
@@ -0,0 +1,42 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class NumberPersistenceTest {
+
+ @ParameterizedTest(name = "multiplicativePersistence({0}) = {1}")
+ @CsvSource({"0, 0", "7, 0", "217, 2", "39, 3", "999, 4"})
+ @DisplayName("Test multiplicative persistence with valid inputs")
+ void testMultiplicativePersistenceValid(int input, int expected) {
+ assertEquals(expected, NumberPersistence.multiplicativePersistence(input));
+ }
+
+ @ParameterizedTest(name = "multiplicativePersistence({0}) throws IllegalArgumentException")
+ @ValueSource(ints = {-1, -100, -9999})
+ @DisplayName("Test multiplicative persistence with negative numbers")
+ void testMultiplicativePersistenceNegative(int input) {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> NumberPersistence.multiplicativePersistence(input));
+ assertEquals("multiplicativePersistence() does not accept negative values", exception.getMessage());
+ }
+
+ @ParameterizedTest(name = "additivePersistence({0}) = {1}")
+ @CsvSource({"0, 0", "5, 0", "199, 3", "999, 2", "1234, 2"})
+ @DisplayName("Test additive persistence with valid inputs")
+ void testAdditivePersistenceValid(int input, int expected) {
+ assertEquals(expected, NumberPersistence.additivePersistence(input));
+ }
+
+ @ParameterizedTest(name = "additivePersistence({0}) throws IllegalArgumentException")
+ @ValueSource(ints = {-1, -100, -9999})
+ @DisplayName("Test additive persistence with negative numbers")
+ void testAdditivePersistenceNegative(int input) {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> NumberPersistence.additivePersistence(input));
+ assertEquals("additivePersistence() does not accept negative values", exception.getMessage());
+ }
+}
From a0b6c52790c9ad85581c91dac666adaf09706147 Mon Sep 17 00:00:00 2001
From: Banula Kumarage <63106638+BanulaKumarage@users.noreply.github.com>
Date: Sat, 4 Oct 2025 19:26:08 +0530
Subject: [PATCH 031/203] Dev: Added algorithm to find the nth number in the
Sylvester Sequence (#6613)
Added algorithm to find the nth number in the SylvesterSequence
---
.../recursion/SylvesterSequence.java | 50 ++++++++++++++++++
.../recursion/SylvesterSequenceTest.java | 51 +++++++++++++++++++
2 files changed, 101 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/recursion/SylvesterSequence.java
create mode 100644 src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java
diff --git a/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java b/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java
new file mode 100644
index 000000000000..2b39f66c7936
--- /dev/null
+++ b/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java
@@ -0,0 +1,50 @@
+package com.thealgorithms.recursion;
+
+import java.math.BigInteger;
+
+/**
+ * A utility class for calculating numbers in Sylvester's sequence.
+ *
+ * Sylvester's sequence is a sequence of integers where each term is calculated
+ * using the formula:
+ * This class is final and cannot be instantiated.
+ *
+ * @see Wikipedia: Sylvester sequence
+ */
+public final class SylvesterSequence {
+
+ // Private constructor to prevent instantiation
+ private SylvesterSequence() {
+ }
+
+ /**
+ * Calculates the nth number in Sylvester's sequence.
+ *
+ * The sequence is defined recursively, with the first term being 2:
+ *
+ * The implementation follows the classic approach of:
+ *
+ * For background see:
+ * Perlin Noise.
+ *
+ *
+ * Constraints and notes:
+ *
+ * This class demonstrates how to insert an element at a specific position and
+ * delete an element from a specific position in an integer array. Since arrays
+ * in Java have fixed size, insertion creates a new array with increased size,
+ * and deletion shifts elements to fill the gap.
+ *
+ * Time Complexity:
+ *
+ * Space Complexity:
+ *
+ * Creates a new array with size = original array size + 1.
+ * Elements at positions <= insertPos retain their positions,
+ * while elements at positions > insertPos are shifted right by one position.
+ *
+ * Creates a new array with size = original array size - 1.
+ * Elements after the deletion position are shifted left by one position.
+ *
+ * This method interactively:
+ *
+ *
+ */
+public final class BitwiseGCD {
+
+ private BitwiseGCD() {
+ }
+
+ /**
+ * Computes GCD of two long values using Stein's algorithm (binary GCD).
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * a(n) = a(n-1) * (a(n-1) - 1) + 1
+ *
+ * with the first term being 2.
+ *
+ *
+ * a(1) = 2
+ * a(n) = a(n-1) * (a(n-1) - 1) + 1 for n > 1
+ *
+ *
+ * @param n the position in the sequence (must be greater than 0)
+ * @return the nth number in Sylvester's sequence
+ * @throws IllegalArgumentException if n is less than or equal to 0
+ */
+ public static BigInteger sylvester(int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("sylvester() does not accept negative numbers or zero.");
+ }
+ if (n == 1) {
+ return BigInteger.valueOf(2);
+ } else {
+ BigInteger prev = sylvester(n - 1);
+ // Sylvester sequence formula: a(n) = a(n-1) * (a(n-1) - 1) + 1
+ return prev.multiply(prev.subtract(BigInteger.ONE)).add(BigInteger.ONE);
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java b/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java
new file mode 100644
index 000000000000..3ea5641ac484
--- /dev/null
+++ b/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.recursion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigInteger;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class SylvesterSequenceTest {
+
+ /**
+ * Provides test cases for valid Sylvester sequence numbers.
+ * Format: { n, expectedValue }
+ */
+ static Stream
+ *
+ *
+ *
+ *
*/
+
public final class PerlinNoise {
private PerlinNoise() {
}
/**
- * @param width width of noise array
- * @param height height of noise array
- * @param octaveCount numbers of layers used for blending noise
- * @param persistence value of impact each layer get while blending
- * @param seed used for randomizer
- * @return float array containing calculated "Perlin-Noise" values
+ * Generate a 2D array of blended noise values normalized to [0, 1].
+ *
+ * @param width width of the noise array (columns)
+ * @param height height of the noise array (rows)
+ * @param octaveCount number of octaves (layers) to blend; must be >= 1
+ * @param persistence per-octave amplitude multiplier in (0, 1]
+ * @param seed seed for the random base grid
+ * @return a {@code width x height} array containing blended noise values in [0,
+ * 1]
*/
static float[][] generatePerlinNoise(int width, int height, int octaveCount, float persistence, long seed) {
- final float[][] base = new float[width][height];
- final float[][] perlinNoise = new float[width][height];
- final float[][][] noiseLayers = new float[octaveCount][][];
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be > 0");
+ }
+ if (octaveCount < 1) {
+ throw new IllegalArgumentException("octaveCount must be >= 1");
+ }
+ if (!(persistence > 0f && persistence <= 1f)) { // using > to exclude 0 and NaN
+ throw new IllegalArgumentException("persistence must be in (0, 1]");
+ }
+ final float[][] base = createBaseGrid(width, height, seed);
+ final float[][][] layers = createLayers(base, width, height, octaveCount);
+ return blendAndNormalize(layers, width, height, persistence);
+ }
+
+ /** Create the base random lattice values in [0,1). */
+ static float[][] createBaseGrid(int width, int height, long seed) {
+ final float[][] base = new float[width][height];
Random random = new Random(seed);
- // fill base array with random values as base for noise
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
base[x][y] = random.nextFloat();
}
}
+ return base;
+ }
- // calculate octaves with different roughness
+ /** Pre-compute each octave layer at increasing frequency. */
+ static float[][][] createLayers(float[][] base, int width, int height, int octaveCount) {
+ final float[][][] noiseLayers = new float[octaveCount][][];
for (int octave = 0; octave < octaveCount; octave++) {
noiseLayers[octave] = generatePerlinNoiseLayer(base, width, height, octave);
}
+ return noiseLayers;
+ }
+ /** Blend layers using geometric amplitudes and normalize to [0,1]. */
+ static float[][] blendAndNormalize(float[][][] layers, int width, int height, float persistence) {
+ final int octaveCount = layers.length;
+ final float[][] out = new float[width][height];
float amplitude = 1f;
float totalAmplitude = 0f;
- // calculate perlin noise by blending each layer together with specific persistence
for (int octave = octaveCount - 1; octave >= 0; octave--) {
amplitude *= persistence;
totalAmplitude += amplitude;
-
+ final float[][] layer = layers[octave];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
- // adding each value of the noise layer to the noise
- // by increasing amplitude the rougher noises will have more impact
- perlinNoise[x][y] += noiseLayers[octave][x][y] * amplitude;
+ out[x][y] += layer[x][y] * amplitude;
}
}
}
- // normalize values so that they stay between 0..1
+ if (totalAmplitude <= 0f || Float.isInfinite(totalAmplitude) || Float.isNaN(totalAmplitude)) {
+ throw new IllegalStateException("Invalid totalAmplitude computed during normalization");
+ }
+
+ final float invTotal = 1f / totalAmplitude;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
- perlinNoise[x][y] /= totalAmplitude;
+ out[x][y] *= invTotal;
}
}
-
- return perlinNoise;
+ return out;
}
/**
- * @param base base random float array
- * @param width width of noise array
+ * Generate a single octave layer by bilinear interpolation of a base grid at a
+ * given octave (period = 2^octave).
+ *
+ * @param base base random float array of size {@code width x height}
+ * @param width width of noise array
* @param height height of noise array
- * @param octave current layer
- * @return float array containing calculated "Perlin-Noise-Layer" values
+ * @param octave current octave (0 for period 1, 1 for period 2, ...)
+ * @return float array containing the octave's interpolated values
*/
static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height, int octave) {
float[][] perlinNoiseLayer = new float[width][height];
- // calculate period (wavelength) for different shapes
+ // Calculate period (wavelength) for different shapes.
int period = 1 << octave; // 2^k
float frequency = 1f / period; // 1/2^k
for (int x = 0; x < width; x++) {
- // calculates the horizontal sampling indices
+ // Calculate the horizontal sampling indices.
int x0 = (x / period) * period;
int x1 = (x0 + period) % width;
- float horizintalBlend = (x - x0) * frequency;
+ float horizontalBlend = (x - x0) * frequency;
for (int y = 0; y < height; y++) {
- // calculates the vertical sampling indices
+ // Calculate the vertical sampling indices.
int y0 = (y / period) * period;
int y1 = (y0 + period) % height;
float verticalBlend = (y - y0) * frequency;
- // blend top corners
- float top = interpolate(base[x0][y0], base[x1][y0], horizintalBlend);
+ // Blend top corners.
+ float top = interpolate(base[x0][y0], base[x1][y0], horizontalBlend);
- // blend bottom corners
- float bottom = interpolate(base[x0][y1], base[x1][y1], horizintalBlend);
+ // Blend bottom corners.
+ float bottom = interpolate(base[x0][y1], base[x1][y1], horizontalBlend);
- // blend top and bottom interpolation to get the final blend value for this cell
+ // Blend top and bottom interpolation to get the final value for this cell.
perlinNoiseLayer[x][y] = interpolate(top, bottom, verticalBlend);
}
}
@@ -105,16 +162,21 @@ static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height,
}
/**
- * @param a value of point a
- * @param b value of point b
- * @param alpha determine which value has more impact (closer to 0 -> a,
- * closer to 1 -> b)
- * @return interpolated value
+ * Linear interpolation between two values.
+ *
+ * @param a value at alpha = 0
+ * @param b value at alpha = 1
+ * @param alpha interpolation factor in [0, 1]
+ * @return interpolated value {@code (1 - alpha) * a + alpha * b}
*/
static float interpolate(float a, float b, float alpha) {
return a * (1 - alpha) + alpha * b;
}
+ /**
+ * Small demo that prints a text representation of the noise using a provided
+ * character set.
+ */
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
@@ -148,7 +210,7 @@ public static void main(String[] args) {
final char[] chars = charset.toCharArray();
final int length = chars.length;
final float step = 1f / length;
- // output based on charset
+ // Output based on charset thresholds.
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
float value = step;
diff --git a/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java b/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java
new file mode 100644
index 000000000000..88c043ad9aa3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java
@@ -0,0 +1,103 @@
+package com.thealgorithms.others;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class PerlinNoiseTest {
+
+ @Test
+ @DisplayName("generatePerlinNoise returns array with correct dimensions")
+ void testDimensions() {
+ int w = 8;
+ int h = 6;
+ float[][] noise = PerlinNoise.generatePerlinNoise(w, h, 4, 0.6f, 123L);
+ assertThat(noise).hasDimensions(w, h);
+ }
+
+ @Test
+ @DisplayName("All values are within [0,1] after normalization")
+ void testRange() {
+ int w = 16;
+ int h = 16;
+ float[][] noise = PerlinNoise.generatePerlinNoise(w, h, 5, 0.7f, 42L);
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ assertThat(noise[x][y]).isBetween(0f, 1f);
+ }
+ }
+ }
+
+ @Test
+ @DisplayName("Deterministic for same parameters and seed")
+ void testDeterminism() {
+ int w = 10;
+ int h = 10;
+ long seed = 98765L;
+ float[][] a = PerlinNoise.generatePerlinNoise(w, h, 3, 0.5f, seed);
+ float[][] b = PerlinNoise.generatePerlinNoise(w, h, 3, 0.5f, seed);
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ assertThat(a[x][y]).isEqualTo(b[x][y]);
+ }
+ }
+ }
+
+ @Test
+ @DisplayName("Different seeds produce different outputs (probabilistically)")
+ void testDifferentSeeds() {
+ int w = 12;
+ int h = 12;
+ float[][] a = PerlinNoise.generatePerlinNoise(w, h, 4, 0.8f, 1L);
+ float[][] b = PerlinNoise.generatePerlinNoise(w, h, 4, 0.8f, 2L);
+
+ // Count exact equalities; expect very few or none.
+ int equalCount = 0;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ if (Float.compare(a[x][y], b[x][y]) == 0) {
+ equalCount++;
+ }
+ }
+ }
+ assertThat(equalCount).isLessThan(w * h / 10); // less than 10% equal exact values
+ }
+
+ @Test
+ @DisplayName("Interpolation endpoints are respected")
+ void testInterpolateEndpoints() {
+ assertThat(PerlinNoise.interpolate(0f, 1f, 0f)).isEqualTo(0f);
+ assertThat(PerlinNoise.interpolate(0f, 1f, 1f)).isEqualTo(1f);
+ assertThat(PerlinNoise.interpolate(0.2f, 0.8f, 0.5f)).isEqualTo(0.5f);
+ }
+
+ @Test
+ @DisplayName("Single octave reduces to bilinear interpolation of base grid")
+ void testSingleOctaveLayer() {
+ int w = 8;
+ int h = 8;
+ long seed = 7L;
+ float[][] base = PerlinNoise.createBaseGrid(w, h, seed);
+ float[][] layer = PerlinNoise.generatePerlinNoiseLayer(base, w, h, 0); // period=1
+ // With period = 1, x0=x, x1=(x+1)%w etc. Values should be smooth and within
+ // [0,1]
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ assertThat(layer[x][y]).isBetween(0f, 1f);
+ }
+ }
+ }
+
+ @Test
+ @DisplayName("Invalid inputs are rejected")
+ void testInvalidInputs() {
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(0, 5, 1, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, -1, 1, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 0, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, 0f, 1L)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, Float.NaN, 1L)).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, 1.1f, 1L)).isInstanceOf(IllegalArgumentException.class);
+ }
+}
From 2a004a0141156bbb1cecfffa7708377bc6baad5f Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 12 Oct 2025 11:07:53 +0530
Subject: [PATCH 057/203] refactor: Enhance docs, code, add tests in
`InsertDeleteInArray` (#6647)
---
.../others/InsertDeleteInArray.java | 189 ++++++++++++---
.../others/InsertDeleteInArrayTest.java | 220 ++++++++++++++++++
2 files changed, 375 insertions(+), 34 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/others/InsertDeleteInArrayTest.java
diff --git a/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java b/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java
index 15093549871b..06a2539ee8b7 100644
--- a/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java
+++ b/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java
@@ -1,51 +1,172 @@
package com.thealgorithms.others;
+import java.util.Arrays;
import java.util.Scanner;
+/**
+ * Utility class for performing insert and delete operations on arrays.
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author TheAlgorithms community
+ * @see Array
+ * Data Structure
+ */
public final class InsertDeleteInArray {
private InsertDeleteInArray() {
}
+ /**
+ * Inserts an element at the specified position in the array.
+ *
+ *
+ *
+ * Tests cover: + *
+ * Time Complexity: O(n log n) where n is the number of unique characters
+ * Space Complexity: O(n)
+ *
+ * @see Huffman
+ * Coding
+ */
public final class Huffman {
private Huffman() {
}
- // recursive function to print the
- // huffman-code through the tree traversal.
- // Here s is the huffman - code generated.
- public static void printCode(HuffmanNode root, String s) {
- // base case; if the left and right are null
- // then its a leaf node and we print
- // the code s generated by traversing the tree.
- if (root.left == null && root.right == null && Character.isLetter(root.c)) {
- // c is the character in the node
- System.out.println(root.c + ":" + s);
-
- return;
+ /**
+ * Builds a Huffman tree from the given character array and their frequencies.
+ *
+ * @param charArray array of characters
+ * @param charFreq array of frequencies corresponding to the characters
+ * @return root node of the Huffman tree
+ * @throws IllegalArgumentException if arrays are null, empty, or have different
+ * lengths
+ */
+ public static HuffmanNode buildHuffmanTree(char[] charArray, int[] charFreq) {
+ if (charArray == null || charFreq == null) {
+ throw new IllegalArgumentException("Character array and frequency array cannot be null");
+ }
+ if (charArray.length == 0 || charFreq.length == 0) {
+ throw new IllegalArgumentException("Character array and frequency array cannot be empty");
+ }
+ if (charArray.length != charFreq.length) {
+ throw new IllegalArgumentException("Character array and frequency array must have the same length");
}
- // if we go to left then add "0" to the code.
- // if we go to the right add"1" to the code.
- // recursive calls for left and
- // right sub-tree of the generated tree.
- printCode(root.left, s + "0");
- printCode(root.right, s + "1");
- }
+ int n = charArray.length;
+ PriorityQueue
+ * The algorithm simulates all possible moves in a game tree and chooses the
+ * move that minimizes the maximum possible loss. The algorithm assumes both
+ * players play optimally.
+ *
+ *
+ * Time Complexity: O(b^d) where b is the branching factor and d is the depth
+ *
+ * Space Complexity: O(d) for the recursive call stack
+ *
+ *
+ * See more:
+ *
+ * This method recursively evaluates the game tree using the minimax algorithm.
+ * At each level, the maximizer tries to maximize the score while the minimizer
+ * tries to minimize it.
+ *
+ * @param depth The current depth in the game tree (0 at root).
+ * @param isMaximizer True if it is the maximizer's turn; false for minimizer.
+ * @param index Index of the current node in the game tree.
+ * @param verbose True to print each player's choice during evaluation.
* @return The optimal score for the player that made the first move.
*/
public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) {
@@ -75,7 +120,7 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) {
}
// Leaf nodes can be sequentially inspected by
- // recurssively multiplying (0 * 2) and ((0 * 2) + 1):
+ // recursively multiplying (0 * 2) and ((0 * 2) + 1):
// (0 x 2) = 0; ((0 x 2) + 1) = 1
// (1 x 2) = 2; ((1 x 2) + 1) = 3
// (2 x 2) = 4; ((2 x 2) + 1) = 5 ...
@@ -87,46 +132,73 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) {
}
/**
- * Returns an array of random numbers which lenght is a power of 2.
+ * Returns an array of random numbers whose length is a power of 2.
*
- * @param size The power of 2 that will determine the lenght of the array.
- * @param maxScore The maximum possible score.
- * @return An array of random numbers.
+ * @param size The power of 2 that will determine the length of the array
+ * (array length = 2^size).
+ * @param maxScore The maximum possible score (scores will be between 1 and
+ * maxScore inclusive).
+ * @return An array of random numbers with length 2^size.
*/
public static int[] getRandomScores(int size, int maxScore) {
int[] randomScores = new int[(int) Math.pow(2, size)];
- Random rand = new Random();
for (int i = 0; i < randomScores.length; i++) {
- randomScores[i] = rand.nextInt(maxScore) + 1;
+ randomScores[i] = RANDOM.nextInt(maxScore) + 1;
}
return randomScores;
}
- // A utility function to find Log n in base 2
+ /**
+ * Calculates the logarithm base 2 of a number.
+ *
+ * @param n The number to calculate log2 for (must be a power of 2).
+ * @return The log2 of n.
+ */
private int log2(int n) {
return (n == 1) ? 0 : log2(n / 2) + 1;
}
- // A utility function to check if a number is a power of 2
+ /**
+ * Checks if a number is a power of 2.
+ *
+ * @param n The number to check.
+ * @return True if n is a power of 2, false otherwise.
+ */
private boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
+ /**
+ * Sets the scores array for the game tree.
+ *
+ * @param scores The array of scores. Length must be a power of 2.
+ * @throws IllegalArgumentException if the scores array length is not a power of
+ * 2
+ */
public void setScores(int[] scores) {
if (!isPowerOfTwo(scores.length)) {
- System.out.println("The number of scores must be a power of 2.");
- return;
+ throw new IllegalArgumentException("The number of scores must be a power of 2.");
}
- this.scores = scores;
+ this.scores = Arrays.copyOf(scores, scores.length);
height = log2(this.scores.length);
}
+ /**
+ * Returns a copy of the scores array.
+ *
+ * @return A copy of the scores array.
+ */
public int[] getScores() {
- return scores;
+ return Arrays.copyOf(scores, scores.length);
}
+ /**
+ * Returns the height of the game tree.
+ *
+ * @return The height of the game tree (log2 of the number of leaf nodes).
+ */
public int getHeight() {
return height;
}
diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java
index 821eb3f16029..4e81c8b7e34f 100644
--- a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java
+++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java
@@ -1,12 +1,9 @@
package com.thealgorithms.others;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -26,56 +23,82 @@ void setUp() {
System.setOut(new PrintStream(outputStream));
}
+ @AfterEach
+ void tearDown() {
+ System.setOut(originalOut);
+ }
+
@Test
void testConstructorCreatesValidScores() {
// The default constructor should create scores array of length 8 (2^3)
- assertEquals(8, miniMax.getScores().length);
- assertEquals(3, miniMax.getHeight());
+ Assertions.assertEquals(8, miniMax.getScores().length);
+ Assertions.assertEquals(3, miniMax.getHeight());
// All scores should be positive (between 1 and 99)
for (int score : miniMax.getScores()) {
- assertTrue(score >= 1 && score <= 99);
+ Assertions.assertTrue(score >= 1 && score <= 99);
}
}
+ @Test
+ void testConstructorWithValidScores() {
+ int[] validScores = {10, 20, 30, 40};
+ MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(validScores);
+
+ Assertions.assertArrayEquals(validScores, customMiniMax.getScores());
+ Assertions.assertEquals(2, customMiniMax.getHeight()); // log2(4) = 2
+ }
+
+ @Test
+ void testConstructorWithInvalidScoresThrowsException() {
+ int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores));
+ }
+
+ @Test
+ void testConstructorDoesNotModifyOriginalArray() {
+ int[] originalScores = {10, 20, 30, 40};
+ int[] copyOfOriginal = {10, 20, 30, 40};
+ MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(originalScores);
+
+ // Modify the original array
+ originalScores[0] = 999;
+
+ // Constructor should have made a copy, so internal state should be unchanged
+ Assertions.assertArrayEquals(copyOfOriginal, customMiniMax.getScores());
+ }
+
@Test
void testSetScoresWithValidPowerOfTwo() {
int[] validScores = {10, 20, 30, 40};
miniMax.setScores(validScores);
- assertArrayEquals(validScores, miniMax.getScores());
- assertEquals(2, miniMax.getHeight()); // log2(4) = 2
+ Assertions.assertArrayEquals(validScores, miniMax.getScores());
+ Assertions.assertEquals(2, miniMax.getHeight()); // log2(4) = 2
}
@Test
void testSetScoresWithInvalidLength() {
int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2
- miniMax.setScores(invalidScores);
-
- // Should print error message and not change the scores
- String output = outputStream.toString();
- assertTrue(output.contains("The number of scores must be a power of 2."));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores));
// Scores should remain unchanged (original length 8)
- assertEquals(8, miniMax.getScores().length);
+ Assertions.assertEquals(8, miniMax.getScores().length);
}
@Test
void testSetScoresWithZeroLength() {
int[] emptyScores = {}; // Length 0 is not a power of 2
- miniMax.setScores(emptyScores);
-
- // Should print error message and not change the scores
- String output = outputStream.toString();
- assertTrue(output.contains("The number of scores must be a power of 2."));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyScores));
// Scores should remain unchanged (original length 8)
- assertEquals(8, miniMax.getScores().length);
+ Assertions.assertEquals(8, miniMax.getScores().length);
}
@Test
void testSetScoresWithVariousInvalidLengths() {
- // Test multiple invalid lengths to ensure isPowerOfTwo function is fully covered
+ // Test multiple invalid lengths to ensure isPowerOfTwo function is fully
+ // covered
int[][] invalidScoreArrays = {
{1, 2, 3, 4, 5}, // Length 5
{1, 2, 3, 4, 5, 6}, // Length 6
@@ -86,17 +109,11 @@ void testSetScoresWithVariousInvalidLengths() {
};
for (int[] invalidScores : invalidScoreArrays) {
- // Clear the output stream for each test
- outputStream.reset();
- miniMax.setScores(invalidScores);
-
- // Should print error message for each invalid length
- String output = outputStream.toString();
- assertTrue(output.contains("The number of scores must be a power of 2."), "Failed for array length: " + invalidScores.length);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), "Failed for array length: " + invalidScores.length);
}
// Scores should remain unchanged (original length 8)
- assertEquals(8, miniMax.getScores().length);
+ Assertions.assertEquals(8, miniMax.getScores().length);
}
@Test
@@ -104,8 +121,8 @@ void testSetScoresWithSingleElement() {
int[] singleScore = {42};
miniMax.setScores(singleScore);
- assertArrayEquals(singleScore, miniMax.getScores());
- assertEquals(0, miniMax.getHeight()); // log2(1) = 0
+ Assertions.assertArrayEquals(singleScore, miniMax.getScores());
+ Assertions.assertEquals(0, miniMax.getHeight()); // log2(1) = 0
}
@Test
@@ -116,7 +133,7 @@ void testMiniMaxWithKnownScores() {
// Maximizer starts: should choose max(min(3,12), min(8,2)) = max(3, 2) = 3
int result = miniMax.miniMax(0, true, 0, false);
- assertEquals(3, result);
+ Assertions.assertEquals(3, result);
}
@Test
@@ -127,7 +144,7 @@ void testMiniMaxWithMinimizerFirst() {
// Minimizer starts: should choose min(max(3,12), max(8,2)) = min(12, 8) = 8
int result = miniMax.miniMax(0, false, 0, false);
- assertEquals(8, result);
+ Assertions.assertEquals(8, result);
}
@Test
@@ -139,8 +156,8 @@ void testMiniMaxWithLargerTree() {
// Maximizer starts
int result = miniMax.miniMax(0, true, 0, false);
// Expected: max(min(max(5,6), max(7,4)), min(max(5,3), max(6,2)))
- // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6
- assertEquals(6, result);
+ // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6
+ Assertions.assertEquals(6, result);
}
@Test
@@ -151,41 +168,41 @@ void testMiniMaxVerboseOutput() {
miniMax.miniMax(0, true, 0, true);
String output = outputStream.toString();
- assertTrue(output.contains("Maximizer"));
- assertTrue(output.contains("Minimizer"));
- assertTrue(output.contains("chooses"));
+ Assertions.assertTrue(output.contains("Maximizer"));
+ Assertions.assertTrue(output.contains("Minimizer"));
+ Assertions.assertTrue(output.contains("chooses"));
}
@Test
void testGetRandomScoresLength() {
int[] randomScores = MiniMaxAlgorithm.getRandomScores(4, 50);
- assertEquals(16, randomScores.length); // 2^4 = 16
+ Assertions.assertEquals(16, randomScores.length); // 2^4 = 16
// All scores should be between 1 and 50
for (int score : randomScores) {
- assertTrue(score >= 1 && score <= 50);
+ Assertions.assertTrue(score >= 1 && score <= 50);
}
}
@Test
void testGetRandomScoresWithDifferentParameters() {
int[] randomScores = MiniMaxAlgorithm.getRandomScores(2, 10);
- assertEquals(4, randomScores.length); // 2^2 = 4
+ Assertions.assertEquals(4, randomScores.length); // 2^2 = 4
// All scores should be between 1 and 10
for (int score : randomScores) {
- assertTrue(score >= 1 && score <= 10);
+ Assertions.assertTrue(score >= 1 && score <= 10);
}
}
@Test
void testMainMethod() {
// Test that main method runs without errors
- assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {}));
+ Assertions.assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {}));
String output = outputStream.toString();
- assertTrue(output.contains("The best score for"));
- assertTrue(output.contains("Maximizer"));
+ Assertions.assertTrue(output.contains("The best score for"));
+ Assertions.assertTrue(output.contains("Maximizer"));
}
@Test
@@ -193,11 +210,11 @@ void testHeightCalculation() {
// Test height calculation for different array sizes
int[] scores2 = {1, 2};
miniMax.setScores(scores2);
- assertEquals(1, miniMax.getHeight()); // log2(2) = 1
+ Assertions.assertEquals(1, miniMax.getHeight()); // log2(2) = 1
int[] scores16 = new int[16];
miniMax.setScores(scores16);
- assertEquals(4, miniMax.getHeight()); // log2(16) = 4
+ Assertions.assertEquals(4, miniMax.getHeight()); // log2(16) = 4
}
@Test
@@ -206,7 +223,7 @@ void testEdgeCaseWithZeroScores() {
miniMax.setScores(zeroScores);
int result = miniMax.miniMax(0, true, 0, false);
- assertEquals(0, result);
+ Assertions.assertEquals(0, result);
}
@Test
@@ -218,11 +235,7 @@ void testEdgeCaseWithNegativeScores() {
// Level 1 (minimizer): min(-5,-2) = -5, min(-8,-1) = -8
// Level 0 (maximizer): max(-5, -8) = -5
int result = miniMax.miniMax(0, true, 0, false);
- assertEquals(-5, result);
- }
-
- void tearDown() {
- System.setOut(originalOut);
+ Assertions.assertEquals(-5, result);
}
@Test
@@ -233,12 +246,9 @@ void testSetScoresWithNegativeLength() {
// Test with array length 0 (edge case for n > 0 condition)
int[] emptyArray = new int[0];
- outputStream.reset();
- miniMax.setScores(emptyArray);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyArray));
- String output = outputStream.toString();
- assertTrue(output.contains("The number of scores must be a power of 2."));
- assertEquals(8, miniMax.getScores().length); // Should remain unchanged
+ Assertions.assertEquals(8, miniMax.getScores().length); // Should remain unchanged
}
@Test
@@ -250,8 +260,8 @@ void testSetScoresWithLargePowerOfTwo() {
}
miniMax.setScores(largeValidScores);
- assertArrayEquals(largeValidScores, miniMax.getScores());
- assertEquals(5, miniMax.getHeight()); // log2(32) = 5
+ Assertions.assertArrayEquals(largeValidScores, miniMax.getScores());
+ Assertions.assertEquals(5, miniMax.getHeight()); // log2(32) = 5
}
@Test
@@ -270,8 +280,65 @@ void testSetScoresValidEdgeCases() {
for (int i = 0; i < validPowersOf2.length; i++) {
miniMax.setScores(validPowersOf2[i]);
- assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length);
- assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length);
+ Assertions.assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length);
+ Assertions.assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length);
}
}
+
+ @Test
+ void testGetScoresReturnsDefensiveCopy() {
+ int[] originalScores = {10, 20, 30, 40};
+ miniMax.setScores(originalScores);
+
+ // Get the scores and modify them
+ int[] retrievedScores = miniMax.getScores();
+ retrievedScores[0] = 999;
+
+ // Internal state should remain unchanged
+ Assertions.assertEquals(10, miniMax.getScores()[0]);
+ }
+
+ @Test
+ void testSetScoresCreatesDefensiveCopy() {
+ int[] originalScores = {10, 20, 30, 40};
+ miniMax.setScores(originalScores);
+
+ // Modify the original array after setting
+ originalScores[0] = 999;
+
+ // Internal state should remain unchanged
+ Assertions.assertEquals(10, miniMax.getScores()[0]);
+ }
+
+ @Test
+ void testMiniMaxWithAllSameScores() {
+ int[] sameScores = {5, 5, 5, 5};
+ miniMax.setScores(sameScores);
+
+ // When all scores are the same, result should be that score
+ int result = miniMax.miniMax(0, true, 0, false);
+ Assertions.assertEquals(5, result);
+ }
+
+ @Test
+ void testMiniMaxAtDifferentDepths() {
+ int[] testScores = {3, 12, 8, 2, 14, 5, 2, 9};
+ miniMax.setScores(testScores);
+
+ // Test maximizer first
+ int result = miniMax.miniMax(0, true, 0, false);
+ // Expected: max(min(max(3,12), max(8,2)), min(max(14,5), max(2,9)))
+ // = max(min(12, 8), min(14, 9)) = max(8, 9) = 9
+ Assertions.assertEquals(9, result);
+ }
+
+ @Test
+ void testMiniMaxWithMinIntAndMaxInt() {
+ int[] extremeScores = {Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1};
+ miniMax.setScores(extremeScores);
+
+ int result = miniMax.miniMax(0, true, 0, false);
+ // Expected: max(min(MIN, MAX), min(0, 1)) = max(MIN, 0) = 0
+ Assertions.assertEquals(0, result);
+ }
}
From c0ca70498bcfcb9e7f50603f54342eb62cb10a2b Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 12 Oct 2025 11:43:58 +0530
Subject: [PATCH 060/203] refactor: Enhance docs, code, add tests in `PageRank`
(#6642)
---
.../com/thealgorithms/others/PageRank.java | 346 ++++++++++++++----
.../thealgorithms/others/PageRankTest.java | 319 ++++++++++++++++
2 files changed, 598 insertions(+), 67 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/others/PageRankTest.java
diff --git a/src/main/java/com/thealgorithms/others/PageRank.java b/src/main/java/com/thealgorithms/others/PageRank.java
index c7be7a9882bc..2899b80bcee8 100644
--- a/src/main/java/com/thealgorithms/others/PageRank.java
+++ b/src/main/java/com/thealgorithms/others/PageRank.java
@@ -2,94 +2,306 @@
import java.util.Scanner;
-class PageRank {
+/**
+ * PageRank Algorithm Implementation
+ *
+ *
+ * The PageRank algorithm is used by Google Search to rank web pages in their
+ * search engine
+ * results. It was named after Larry Page, one of the founders of Google.
+ * PageRank is a way of
+ * measuring the importance of website pages.
+ *
+ *
+ * Algorithm: 1. Initialize PageRank values for all pages to 1/N (where N is the
+ * total number
+ * of pages) 2. For each iteration: - For each page, calculate the new PageRank
+ * by summing the
+ * contributions from all incoming links - Apply the damping factor: PR(page) =
+ * (1-d) + d *
+ * sum(PR(incoming_page) / outgoing_links(incoming_page)) 3. Repeat until
+ * convergence
+ *
+ * @see PageRank Algorithm
+ */
+public final class PageRank {
+ private static final int MAX_NODES = 10;
+ private static final double DEFAULT_DAMPING_FACTOR = 0.85;
+ private static final int DEFAULT_ITERATIONS = 2;
+
+ private int[][] adjacencyMatrix;
+ private double[] pageRankValues;
+ private int nodeCount;
+
+ /**
+ * Constructor to initialize PageRank with specified number of nodes
+ *
+ * @param numberOfNodes the number of nodes/pages in the graph
+ * @throws IllegalArgumentException if numberOfNodes is less than 1 or greater
+ * than MAX_NODES
+ */
+ public PageRank(int numberOfNodes) {
+ if (numberOfNodes < 1 || numberOfNodes > MAX_NODES) {
+ throw new IllegalArgumentException("Number of nodes must be between 1 and " + MAX_NODES);
+ }
+ this.nodeCount = numberOfNodes;
+ this.adjacencyMatrix = new int[MAX_NODES][MAX_NODES];
+ this.pageRankValues = new double[MAX_NODES];
+ }
+
+ /**
+ * Default constructor for interactive mode
+ */
+ public PageRank() {
+ this.adjacencyMatrix = new int[MAX_NODES][MAX_NODES];
+ this.pageRankValues = new double[MAX_NODES];
+ }
+
+ /**
+ * Main method for interactive PageRank calculation
+ *
+ * @param args command line arguments (not used)
+ */
public static void main(String[] args) {
- int nodes;
- int i;
- int j;
- Scanner in = new Scanner(System.in);
- System.out.print("Enter the Number of WebPages: ");
- nodes = in.nextInt();
- PageRank p = new PageRank();
- System.out.println("Enter the Adjacency Matrix with 1->PATH & 0->NO PATH Between two WebPages: ");
- for (i = 1; i <= nodes; i++) {
- for (j = 1; j <= nodes; j++) {
- p.path[i][j] = in.nextInt();
- if (j == i) {
- p.path[i][j] = 0;
+ try (Scanner scanner = new Scanner(System.in)) {
+ System.out.print("Enter the Number of WebPages: ");
+ int nodes = scanner.nextInt();
+
+ PageRank pageRank = new PageRank(nodes);
+ System.out.println("Enter the Adjacency Matrix with 1->PATH & 0->NO PATH Between two WebPages: ");
+
+ for (int i = 1; i <= nodes; i++) {
+ for (int j = 1; j <= nodes; j++) {
+ int value = scanner.nextInt();
+ pageRank.setEdge(i, j, value);
}
}
+
+ pageRank.calculatePageRank(nodes, DEFAULT_DAMPING_FACTOR, DEFAULT_ITERATIONS, true);
}
- p.calc(nodes);
}
- public int[][] path = new int[10][10];
- public double[] pagerank = new double[10];
+ /**
+ * Sets an edge in the adjacency matrix
+ *
+ * @param from source node (1-indexed)
+ * @param to destination node (1-indexed)
+ * @param value 1 if edge exists, 0 otherwise
+ */
+ public void setEdge(int from, int to, int value) {
+ if (from == to) {
+ adjacencyMatrix[from][to] = 0; // No self-loops
+ } else {
+ adjacencyMatrix[from][to] = value;
+ }
+ }
- public void calc(double totalNodes) {
- double initialPageRank;
- double outgoingLinks = 0;
- double dampingFactor = 0.85;
- double[] tempPageRank = new double[10];
- int externalNodeNumber;
- int internalNodeNumber;
- int k = 1; // For Traversing
- int iterationStep = 1;
- initialPageRank = 1 / totalNodes;
- System.out.printf(" Total Number of Nodes :" + totalNodes + "\t Initial PageRank of All Nodes :" + initialPageRank + "\n");
+ /**
+ * Sets the adjacency matrix for the graph
+ *
+ * @param matrix the adjacency matrix (1-indexed)
+ */
+ public void setAdjacencyMatrix(int[][] matrix) {
+ for (int i = 1; i <= nodeCount; i++) {
+ for (int j = 1; j <= nodeCount; j++) {
+ setEdge(i, j, matrix[i][j]);
+ }
+ }
+ }
- // 0th ITERATION _ OR _ INITIALIZATION PHASE //
- for (k = 1; k <= totalNodes; k++) {
- this.pagerank[k] = initialPageRank;
+ /**
+ * Gets the PageRank value for a specific node
+ *
+ * @param node the node index (1-indexed)
+ * @return the PageRank value
+ */
+ public double getPageRank(int node) {
+ if (node < 1 || node > nodeCount) {
+ throw new IllegalArgumentException("Node index out of bounds");
}
- System.out.print("\n Initial PageRank Values , 0th Step \n");
+ return pageRankValues[node];
+ }
+
+ /**
+ * Gets all PageRank values
+ *
+ * @return array of PageRank values (1-indexed)
+ */
+ public double[] getAllPageRanks() {
+ return pageRankValues.clone();
+ }
+
+ /**
+ * Calculates PageRank using the default damping factor and iterations
+ *
+ * @param totalNodes the total number of nodes
+ * @return array of PageRank values
+ */
+ public double[] calculatePageRank(int totalNodes) {
+ return calculatePageRank(totalNodes, DEFAULT_DAMPING_FACTOR, DEFAULT_ITERATIONS, false);
+ }
+
+ /**
+ * Calculates PageRank with custom parameters
+ *
+ * @param totalNodes the total number of nodes
+ * @param dampingFactor the damping factor (typically 0.85)
+ * @param iterations number of iterations to perform
+ * @param verbose whether to print detailed output
+ * @return array of PageRank values
+ */
+ public double[] calculatePageRank(int totalNodes, double dampingFactor, int iterations, boolean verbose) {
+ validateInputParameters(totalNodes, dampingFactor, iterations);
- for (k = 1; k <= totalNodes; k++) {
- System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n");
+ this.nodeCount = totalNodes;
+ double initialPageRank = 1.0 / totalNodes;
+
+ if (verbose) {
+ System.out.printf("Total Number of Nodes: %d\tInitial PageRank of All Nodes: %.6f%n", totalNodes, initialPageRank);
}
- while (iterationStep <= 2) { // Iterations
- // Store the PageRank for All Nodes in Temporary Array
- for (k = 1; k <= totalNodes; k++) {
- tempPageRank[k] = this.pagerank[k];
- this.pagerank[k] = 0;
- }
+ initializePageRanks(totalNodes, initialPageRank, verbose);
+ performIterations(totalNodes, dampingFactor, iterations, verbose);
- for (internalNodeNumber = 1; internalNodeNumber <= totalNodes; internalNodeNumber++) {
- for (externalNodeNumber = 1; externalNodeNumber <= totalNodes; externalNodeNumber++) {
- if (this.path[externalNodeNumber][internalNodeNumber] == 1) {
- k = 1;
- outgoingLinks = 0; // Count the Number of Outgoing Links for each externalNodeNumber
- while (k <= totalNodes) {
- if (this.path[externalNodeNumber][k] == 1) {
- outgoingLinks = outgoingLinks + 1; // Counter for Outgoing Links
- }
- k = k + 1;
- }
- // Calculate PageRank
- this.pagerank[internalNodeNumber] += tempPageRank[externalNodeNumber] * (1 / outgoingLinks);
- }
- }
- System.out.printf("\n After " + iterationStep + "th Step \n");
+ if (verbose) {
+ System.out.println("\nFinal PageRank:");
+ printPageRanks(totalNodes);
+ }
- for (k = 1; k <= totalNodes; k++) {
- System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n");
- }
+ return pageRankValues.clone();
+ }
+
+ /**
+ * Validates input parameters for PageRank calculation
+ *
+ * @param totalNodes the total number of nodes
+ * @param dampingFactor the damping factor
+ * @param iterations number of iterations
+ * @throws IllegalArgumentException if parameters are invalid
+ */
+ private void validateInputParameters(int totalNodes, double dampingFactor, int iterations) {
+ if (totalNodes < 1 || totalNodes > MAX_NODES) {
+ throw new IllegalArgumentException("Total nodes must be between 1 and " + MAX_NODES);
+ }
+ if (dampingFactor < 0 || dampingFactor > 1) {
+ throw new IllegalArgumentException("Damping factor must be between 0 and 1");
+ }
+ if (iterations < 1) {
+ throw new IllegalArgumentException("Iterations must be at least 1");
+ }
+ }
+
+ /**
+ * Initializes PageRank values for all nodes
+ *
+ * @param totalNodes the total number of nodes
+ * @param initialPageRank the initial PageRank value
+ * @param verbose whether to print output
+ */
+ private void initializePageRanks(int totalNodes, double initialPageRank, boolean verbose) {
+ for (int i = 1; i <= totalNodes; i++) {
+ pageRankValues[i] = initialPageRank;
+ }
+
+ if (verbose) {
+ System.out.println("\nInitial PageRank Values, 0th Step");
+ printPageRanks(totalNodes);
+ }
+ }
- iterationStep = iterationStep + 1;
+ /**
+ * Performs the iterative PageRank calculation
+ *
+ * @param totalNodes the total number of nodes
+ * @param dampingFactor the damping factor
+ * @param iterations number of iterations
+ * @param verbose whether to print output
+ */
+ private void performIterations(int totalNodes, double dampingFactor, int iterations, boolean verbose) {
+ for (int iteration = 1; iteration <= iterations; iteration++) {
+ double[] tempPageRank = storeCurrentPageRanks(totalNodes);
+ calculateNewPageRanks(totalNodes, tempPageRank);
+ applyDampingFactor(totalNodes, dampingFactor);
+
+ if (verbose) {
+ System.out.printf("%nAfter %d iteration(s)%n", iteration);
+ printPageRanks(totalNodes);
}
+ }
+ }
- // Add the Damping Factor to PageRank
- for (k = 1; k <= totalNodes; k++) {
- this.pagerank[k] = (1 - dampingFactor) + dampingFactor * this.pagerank[k];
+ /**
+ * Stores current PageRank values in a temporary array
+ *
+ * @param totalNodes the total number of nodes
+ * @return temporary array with current PageRank values
+ */
+ private double[] storeCurrentPageRanks(int totalNodes) {
+ double[] tempPageRank = new double[MAX_NODES];
+ for (int i = 1; i <= totalNodes; i++) {
+ tempPageRank[i] = pageRankValues[i];
+ pageRankValues[i] = 0;
+ }
+ return tempPageRank;
+ }
+
+ /**
+ * Calculates new PageRank values based on incoming links
+ *
+ * @param totalNodes the total number of nodes
+ * @param tempPageRank temporary array with previous PageRank values
+ */
+ private void calculateNewPageRanks(int totalNodes, double[] tempPageRank) {
+ for (int targetNode = 1; targetNode <= totalNodes; targetNode++) {
+ for (int sourceNode = 1; sourceNode <= totalNodes; sourceNode++) {
+ if (adjacencyMatrix[sourceNode][targetNode] == 1) {
+ int outgoingLinks = countOutgoingLinks(sourceNode, totalNodes);
+ if (outgoingLinks > 0) {
+ pageRankValues[targetNode] += tempPageRank[sourceNode] / outgoingLinks;
+ }
+ }
}
+ }
+ }
- // Display PageRank
- System.out.print("\n Final Page Rank : \n");
- for (k = 1; k <= totalNodes; k++) {
- System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n");
+ /**
+ * Applies the damping factor to all PageRank values
+ *
+ * @param totalNodes the total number of nodes
+ * @param dampingFactor the damping factor
+ */
+ private void applyDampingFactor(int totalNodes, double dampingFactor) {
+ for (int i = 1; i <= totalNodes; i++) {
+ pageRankValues[i] = (1 - dampingFactor) + dampingFactor * pageRankValues[i];
+ }
+ }
+
+ /**
+ * Counts the number of outgoing links from a node
+ *
+ * @param node the source node (1-indexed)
+ * @param totalNodes total number of nodes
+ * @return the count of outgoing links
+ */
+ private int countOutgoingLinks(int node, int totalNodes) {
+ int count = 0;
+ for (int i = 1; i <= totalNodes; i++) {
+ if (adjacencyMatrix[node][i] == 1) {
+ count++;
}
}
+ return count;
+ }
+
+ /**
+ * Prints the PageRank values for all nodes
+ *
+ * @param totalNodes the total number of nodes
+ */
+ private void printPageRanks(int totalNodes) {
+ for (int i = 1; i <= totalNodes; i++) {
+ System.out.printf("PageRank of %d: %.6f%n", i, pageRankValues[i]);
+ }
}
}
diff --git a/src/test/java/com/thealgorithms/others/PageRankTest.java b/src/test/java/com/thealgorithms/others/PageRankTest.java
new file mode 100644
index 000000000000..de96403dd1fc
--- /dev/null
+++ b/src/test/java/com/thealgorithms/others/PageRankTest.java
@@ -0,0 +1,319 @@
+package com.thealgorithms.others;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for PageRank algorithm implementation
+ *
+ * @author Hardvan
+ */
+class PageRankTest {
+
+ private static final double EPSILON = 0.0001; // Tolerance for floating point comparisons
+
+ /**
+ * Test basic PageRank calculation with a simple 3-node graph
+ * Graph: 1 -> 2, 2 -> 3, 3 -> 1
+ */
+ @Test
+ void testSimpleThreeNodeGraph() {
+ PageRank pageRank = new PageRank(3);
+
+ // Create a simple circular graph: 1 -> 2 -> 3 -> 1
+ int[][] adjacencyMatrix = new int[10][10];
+ adjacencyMatrix[1][2] = 1; // Node 1 links to Node 2
+ adjacencyMatrix[2][3] = 1; // Node 2 links to Node 3
+ adjacencyMatrix[3][1] = 1; // Node 3 links to Node 1
+
+ pageRank.setAdjacencyMatrix(adjacencyMatrix);
+ double[] result = pageRank.calculatePageRank(3);
+
+ // All nodes should have equal PageRank in a circular graph
+ assertNotNull(result);
+ assertEquals(result[1], result[2], EPSILON);
+ assertEquals(result[2], result[3], EPSILON);
+ }
+
+ /**
+ * Test PageRank with a two-node graph where one node points to another
+ */
+ @Test
+ void testTwoNodeGraph() {
+ PageRank pageRank = new PageRank(2);
+
+ // Node 1 links to Node 2
+ pageRank.setEdge(1, 2, 1);
+
+ double[] result = pageRank.calculatePageRank(2);
+
+ // Node 2 should have higher PageRank than Node 1 (after 2 iterations)
+ assertNotNull(result);
+ assertEquals(0.2775, result[2], EPSILON);
+ assertEquals(0.15, result[1], EPSILON);
+ }
+
+ /**
+ * Test PageRank with a single node (no links)
+ */
+ @Test
+ void testSingleNode() {
+ PageRank pageRank = new PageRank(1);
+
+ double[] result = pageRank.calculatePageRank(1);
+
+ // Single node should have (1-d) = 0.15 after applying damping
+ assertNotNull(result);
+ assertEquals(0.15, result[1], EPSILON);
+ }
+
+ /**
+ * Test PageRank with a hub-and-spoke configuration
+ * Node 1 is the hub, pointing to nodes 2, 3, and 4
+ */
+ @Test
+ void testHubAndSpokeGraph() {
+ PageRank pageRank = new PageRank(4);
+
+ // Hub node (1) links to all other nodes
+ pageRank.setEdge(1, 2, 1);
+ pageRank.setEdge(1, 3, 1);
+ pageRank.setEdge(1, 4, 1);
+
+ // All spokes link back to hub
+ pageRank.setEdge(2, 1, 1);
+ pageRank.setEdge(3, 1, 1);
+ pageRank.setEdge(4, 1, 1);
+
+ double[] result = pageRank.calculatePageRank(4);
+
+ assertNotNull(result);
+ // Hub should have higher PageRank
+ assertEquals(result[2], result[3], EPSILON);
+ assertEquals(result[3], result[4], EPSILON);
+ }
+
+ /**
+ * Test PageRank with multiple iterations
+ */
+ @Test
+ void testMultipleIterations() {
+ PageRank pageRank = new PageRank(3);
+
+ pageRank.setEdge(1, 2, 1);
+ pageRank.setEdge(2, 3, 1);
+ pageRank.setEdge(3, 1, 1);
+
+ double[] result2Iterations = pageRank.calculatePageRank(3, 0.85, 2, false);
+ double[] result5Iterations = pageRank.calculatePageRank(3, 0.85, 5, false);
+
+ assertNotNull(result2Iterations);
+ assertNotNull(result5Iterations);
+ }
+
+ /**
+ * Test getPageRank method for individual node
+ */
+ @Test
+ void testGetPageRank() {
+ PageRank pageRank = new PageRank(2);
+
+ pageRank.setEdge(1, 2, 1);
+ pageRank.calculatePageRank(2);
+
+ double node1PageRank = pageRank.getPageRank(1);
+ double node2PageRank = pageRank.getPageRank(2);
+
+ assertEquals(0.15, node1PageRank, EPSILON);
+ assertEquals(0.2775, node2PageRank, EPSILON);
+ }
+
+ /**
+ * Test getAllPageRanks method
+ */
+ @Test
+ void testGetAllPageRanks() {
+ PageRank pageRank = new PageRank(2);
+
+ pageRank.setEdge(1, 2, 1);
+ pageRank.calculatePageRank(2);
+
+ double[] allPageRanks = pageRank.getAllPageRanks();
+
+ assertNotNull(allPageRanks);
+ assertEquals(0.15, allPageRanks[1], EPSILON);
+ assertEquals(0.2775, allPageRanks[2], EPSILON);
+ }
+
+ /**
+ * Test that self-loops are not allowed
+ */
+ @Test
+ void testNoSelfLoops() {
+ PageRank pageRank = new PageRank(2);
+
+ // Try to set a self-loop
+ pageRank.setEdge(1, 1, 1);
+ pageRank.setEdge(1, 2, 1);
+
+ double[] result = pageRank.calculatePageRank(2);
+
+ assertNotNull(result);
+ // Self-loop should be ignored
+ }
+
+ /**
+ * Test exception when node count is too small
+ */
+ @Test
+ void testInvalidNodeCountTooSmall() {
+ assertThrows(IllegalArgumentException.class, () -> new PageRank(0));
+ }
+
+ /**
+ * Test exception when node count is too large
+ */
+ @Test
+ void testInvalidNodeCountTooLarge() {
+ assertThrows(IllegalArgumentException.class, () -> new PageRank(11));
+ }
+
+ /**
+ * Test exception for invalid damping factor (negative)
+ */
+ @Test
+ void testInvalidDampingFactorNegative() {
+ PageRank pageRank = new PageRank(2);
+ pageRank.setEdge(1, 2, 1);
+
+ assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, -0.1, 2, false));
+ }
+
+ /**
+ * Test exception for invalid damping factor (greater than 1)
+ */
+ @Test
+ void testInvalidDampingFactorTooLarge() {
+ PageRank pageRank = new PageRank(2);
+ pageRank.setEdge(1, 2, 1);
+
+ assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, 1.5, 2, false));
+ }
+
+ /**
+ * Test exception for invalid iterations (less than 1)
+ */
+ @Test
+ void testInvalidIterations() {
+ PageRank pageRank = new PageRank(2);
+ pageRank.setEdge(1, 2, 1);
+
+ assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, 0.85, 0, false));
+ }
+
+ /**
+ * Test exception when getting PageRank for invalid node
+ */
+ @Test
+ void testGetPageRankInvalidNode() {
+ PageRank pageRank = new PageRank(2);
+ pageRank.calculatePageRank(2);
+
+ assertThrows(IllegalArgumentException.class, () -> pageRank.getPageRank(3));
+ }
+
+ /**
+ * Test exception when getting PageRank for node less than 1
+ */
+ @Test
+ void testGetPageRankNodeLessThanOne() {
+ PageRank pageRank = new PageRank(2);
+ pageRank.calculatePageRank(2);
+
+ assertThrows(IllegalArgumentException.class, () -> pageRank.getPageRank(0));
+ }
+
+ /**
+ * Test complex graph with multiple incoming and outgoing links
+ */
+ @Test
+ void testComplexGraph() {
+ PageRank pageRank = new PageRank(4);
+
+ // Create a more complex graph
+ pageRank.setEdge(1, 2, 1);
+ pageRank.setEdge(1, 3, 1);
+ pageRank.setEdge(2, 3, 1);
+ pageRank.setEdge(3, 4, 1);
+ pageRank.setEdge(4, 1, 1);
+
+ double[] result = pageRank.calculatePageRank(4);
+
+ assertNotNull(result);
+ // Node 3 should have high PageRank (receives links from nodes 1 and 2)
+ // After 2 iterations, the sum will not equal total nodes
+ double sum = result[1] + result[2] + result[3] + result[4];
+ assertEquals(1.8325, sum, EPSILON);
+ }
+
+ /**
+ * Test that PageRank values sum after 2 iterations
+ */
+ @Test
+ void testPageRankSum() {
+ PageRank pageRank = new PageRank(5);
+
+ // Create arbitrary graph
+ pageRank.setEdge(1, 2, 1);
+ pageRank.setEdge(2, 3, 1);
+ pageRank.setEdge(3, 4, 1);
+ pageRank.setEdge(4, 5, 1);
+ pageRank.setEdge(5, 1, 1);
+
+ double[] result = pageRank.calculatePageRank(5);
+
+ double sum = 0;
+ for (int i = 1; i <= 5; i++) {
+ sum += result[i];
+ }
+
+ // Sum after 2 iterations with default damping factor
+ assertEquals(2.11, sum, EPSILON);
+ }
+
+ /**
+ * Test graph with isolated node (no incoming or outgoing links)
+ */
+ @Test
+ void testGraphWithIsolatedNode() {
+ PageRank pageRank = new PageRank(3);
+
+ // Node 1 and 2 are connected, Node 3 is isolated
+ pageRank.setEdge(1, 2, 1);
+ pageRank.setEdge(2, 1, 1);
+
+ double[] result = pageRank.calculatePageRank(3);
+
+ assertNotNull(result);
+ // Isolated node should have some PageRank due to damping factor
+ assertEquals(0.15, result[3], EPSILON);
+ }
+
+ /**
+ * Test verbose mode (should not throw exception)
+ */
+ @Test
+ void testVerboseMode() {
+ PageRank pageRank = new PageRank(2);
+
+ pageRank.setEdge(1, 2, 1);
+
+ // This should execute without throwing an exception
+ double[] result = pageRank.calculatePageRank(2, 0.85, 2, true);
+
+ assertNotNull(result);
+ }
+}
From 14a23b709a27eb47644ebc35e8f386827cf110d3 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 12 Oct 2025 14:35:30 +0530
Subject: [PATCH 061/203] refactor: Enhance docs, code, add tests in
`LowestBasePalindrome` (#6648)
---
.../others/LowestBasePalindrome.java | 97 ++++++++++++++++---
.../others/LowestBasePalindromeTest.java | 64 ++++++++++--
2 files changed, 135 insertions(+), 26 deletions(-)
diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
index 76d1ed4aba1d..a3ca8d6f6db8 100644
--- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
+++ b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
@@ -4,8 +4,26 @@
import java.util.List;
/**
- * @brief Class for finding the lowest base in which a given integer is a palindrome.
- cf. https://oeis.org/A016026
+ * Utility class for finding the lowest base in which a given integer is a
+ * palindrome.
+ *
+ * A number is a palindrome in a given base if its representation in that base
+ * reads the same
+ * forwards and backwards. For example, 15 in base 2 is 1111, which is
+ * palindromic.
+ * This class provides methods to check palindromic properties and find the
+ * smallest base
+ * where a number becomes palindromic.
+ *
+ * Example: The number 15 in base 2 is represented as [1,1,1,1], which is
+ * palindromic.
+ * The number 10 in base 3 is represented as [1,0,1], which is also palindromic.
+ *
+ * The digits are returned in reverse order (least significant digit first).
+ * For example, the number 13 in base 2 produces [1,0,1,1] representing 1101 in
+ * binary.
+ *
+ * A list is palindromic if it reads the same forwards and backwards.
+ * For example, [1,2,1] is palindromic, but [1,2,3] is not.
+ *
+ * This method first validates the input, then applies optimization: if the
+ * number
+ * ends with 0 in the given base (i.e., divisible by the base), it cannot be
+ * palindromic
+ * as palindromes cannot start with 0.
+ *
+ * Examples:
+ * - 101 in base 10 is palindromic (101)
+ * - 15 in base 2 is palindromic (1111)
+ * - 10 in base 3 is palindromic (101)
+ *
+ * This method iteratively checks bases starting from 2 until it finds one where
+ * the number is palindromic. For any number n ≥ 2, the number is always
+ * palindromic
+ * in base n-1 (represented as [1, 1]), so this algorithm is guaranteed to
+ * terminate.
+ *
+ * Time Complexity: O(n * log(n)) in the worst case, where we check each base
+ * and
+ * convert the number to that base.
+ *
+ * Examples:
+ * - lowestBasePalindrome(15) returns 2 (15 in base 2 is 1111)
+ * - lowestBasePalindrome(10) returns 3 (10 in base 3 is 101)
+ * - lowestBasePalindrome(11) returns 10 (11 in base 10 is 11)
+ *
+ * A Krishnamurthy number (also known as a Strong number or Factorion) is a
+ * number
+ * whose sum of the factorials of its digits is equal to the number itself.
+ *
+ * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 +
+ * 120 = 145.
+ *
+ * The only Krishnamurthy numbers in base 10 are: 1, 2, 145, and 40585.
+ *
* Example usage:
+ *
+ * A number is a Krishnamurthy number if the sum of the factorials of its digits
+ * equals the number itself.
+ *
+ * Brief Idea:
+ *
+ * Complexities:
+ *
+ * Usage Example:
+ * Reference
+ * Example: To add (¬x₁ ∨ x₂), call:
+ *
+ * Mapping rule:
+ *
+ * Time Complexity: O(n log n) due to sorting
+ *
+ * Space Complexity: O(1) if sorting is done in-place
+ *
+ * @see Median (Wikipedia)
+ * @see Mean,
+ * Median, and Mode Review
*/
public final class Median {
private Median() {
}
/**
- * Calculate average median
- * @param values sorted numbers to find median of
- * @return median of given {@code values}
- * @throws IllegalArgumentException If the input array is empty or null.
+ * Calculates the median of an array of integers.
+ * The array is sorted internally, so the original order is not preserved.
+ * For arrays with an odd number of elements, returns the middle element.
+ * For arrays with an even number of elements, returns the average of the two
+ * middle elements.
+ *
+ * @param values the array of integers to find the median of (can be unsorted)
+ * @return the median value as a double
+ * @throws IllegalArgumentException if the input array is empty or null
*/
public static double median(int[] values) {
if (values == null || values.length == 0) {
@@ -22,6 +40,10 @@ public static double median(int[] values) {
Arrays.sort(values);
int length = values.length;
- return length % 2 == 0 ? (values[length / 2] + values[length / 2 - 1]) / 2.0 : values[length / 2];
+ if (length % 2 == 0) {
+ return (values[length / 2] + values[length / 2 - 1]) / 2.0;
+ } else {
+ return values[length / 2];
+ }
}
}
diff --git a/src/test/java/com/thealgorithms/maths/MedianTest.java b/src/test/java/com/thealgorithms/maths/MedianTest.java
index 560feb695792..f42fe8bb17ee 100644
--- a/src/test/java/com/thealgorithms/maths/MedianTest.java
+++ b/src/test/java/com/thealgorithms/maths/MedianTest.java
@@ -5,40 +5,153 @@
import org.junit.jupiter.api.Test;
-public class MedianTest {
+/**
+ * Test class for {@link Median}.
+ * Tests various scenarios including edge cases, odd/even length arrays,
+ * negative values, and unsorted inputs.
+ */
+class MedianTest {
+
@Test
- void medianSingleValue() {
+ void testMedianSingleValue() {
int[] arr = {0};
assertEquals(0, Median.median(arr));
}
@Test
- void medianTwoValues() {
+ void testMedianSinglePositiveValue() {
+ int[] arr = {42};
+ assertEquals(42, Median.median(arr));
+ }
+
+ @Test
+ void testMedianSingleNegativeValue() {
+ int[] arr = {-15};
+ assertEquals(-15, Median.median(arr));
+ }
+
+ @Test
+ void testMedianTwoValues() {
int[] arr = {1, 2};
assertEquals(1.5, Median.median(arr));
}
@Test
- void medianThreeValues() {
+ void testMedianTwoIdenticalValues() {
+ int[] arr = {5, 5};
+ assertEquals(5.0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianThreeValues() {
int[] arr = {1, 2, 3};
assertEquals(2, Median.median(arr));
}
@Test
- void medianDecimalValueReturn() {
+ void testMedianThreeUnsortedValues() {
+ int[] arr = {3, 1, 2};
+ assertEquals(2, Median.median(arr));
+ }
+
+ @Test
+ void testMedianDecimalValueReturn() {
int[] arr = {1, 2, 3, 4, 5, 6, 8, 9};
assertEquals(4.5, Median.median(arr));
}
@Test
- void medianNegativeValues() {
+ void testMedianNegativeValues() {
int[] arr = {-27, -16, -7, -4, -2, -1};
assertEquals(-5.5, Median.median(arr));
}
@Test
- void medianEmptyArrayThrows() {
+ void testMedianMixedPositiveAndNegativeValues() {
+ int[] arr = {-10, -5, 0, 5, 10};
+ assertEquals(0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianMixedUnsortedValues() {
+ int[] arr = {10, -5, 0, 5, -10};
+ assertEquals(0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianLargeOddArray() {
+ int[] arr = {9, 7, 5, 3, 1, 2, 4, 6, 8};
+ assertEquals(5, Median.median(arr));
+ }
+
+ @Test
+ void testMedianLargeEvenArray() {
+ int[] arr = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
+ assertEquals(55.0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianAllSameValues() {
+ int[] arr = {7, 7, 7, 7, 7};
+ assertEquals(7.0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianWithZeros() {
+ int[] arr = {0, 0, 0, 0, 0};
+ assertEquals(0.0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianAlreadySorted() {
+ int[] arr = {1, 2, 3, 4, 5};
+ assertEquals(3, Median.median(arr));
+ }
+
+ @Test
+ void testMedianReverseSorted() {
+ int[] arr = {5, 4, 3, 2, 1};
+ assertEquals(3, Median.median(arr));
+ }
+
+ @Test
+ void testMedianWithDuplicates() {
+ int[] arr = {1, 2, 2, 3, 3, 3, 4};
+ assertEquals(3, Median.median(arr));
+ }
+
+ @Test
+ void testMedianEmptyArrayThrows() {
int[] arr = {};
assertThrows(IllegalArgumentException.class, () -> Median.median(arr));
}
+
+ @Test
+ void testMedianNullArrayThrows() {
+ assertThrows(IllegalArgumentException.class, () -> Median.median(null));
+ }
+
+ @Test
+ void testMedianExtremeValues() {
+ int[] arr = {Integer.MAX_VALUE, Integer.MIN_VALUE};
+ assertEquals(-0.5, Median.median(arr));
+ }
+
+ @Test
+ void testMedianTwoNegativeValues() {
+ int[] arr = {-10, -20};
+ assertEquals(-15.0, Median.median(arr));
+ }
+
+ @Test
+ void testMedianFourValuesEven() {
+ int[] arr = {1, 2, 3, 4};
+ assertEquals(2.5, Median.median(arr));
+ }
+
+ @Test
+ void testMedianFiveValuesOdd() {
+ int[] arr = {10, 20, 30, 40, 50};
+ assertEquals(30.0, Median.median(arr));
+ }
}
From 297634d05fdd9b90b58c9060229229c0cae6280a Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 13 Oct 2025 02:43:14 +0530
Subject: [PATCH 066/203] refactor: Enhance docs, code, add tests in
`KaprekarNumbers` (#6747)
Co-authored-by: a
+ * A Kaprekar number is a positive integer with the following property:
+ * If you square it, then split the resulting number into two parts (right part
+ * has same number of
+ * digits as the original number, left part has the remaining digits), and
+ * finally add the two
+ * parts together, you get the original number.
+ *
+ * For example:
+ *
+ * Note: The right part can have leading zeros, but must not be all zeros.
+ *
+ * @see Kaprekar Number
+ * - Wikipedia
+ * @author TheAlgorithms (https://github.com/TheAlgorithms)
+ */
public final class KaprekarNumbers {
private KaprekarNumbers() {
}
- /* This program demonstrates if a given number is Kaprekar Number or not.
- Kaprekar Number: A Kaprekar number is an n-digit number which its square can be split into
- two parts where the right part has n digits and sum of these parts is equal to the original
- number. */
-
- // Provides a list of kaprekarNumber in a range
- public static List
+ * The algorithm works as follows:
+ *
+ * Special handling is required for numbers whose squares contain zeros.
+ *
+ * @param num the number to check
+ * @return true if the number is a Kaprekar number, false otherwise
+ * @throws IllegalArgumentException if num is negative
+ */
public static boolean isKaprekarNumber(long num) {
+ if (num < 0) {
+ throw new IllegalArgumentException("Number must be non-negative. Given: " + num);
+ }
+
+ if (num == 0 || num == 1) {
+ return true;
+ }
+
String number = Long.toString(num);
BigInteger originalNumber = BigInteger.valueOf(num);
BigInteger numberSquared = originalNumber.multiply(originalNumber);
- if (number.length() == numberSquared.toString().length()) {
- return number.equals(numberSquared.toString());
- } else {
- BigInteger leftDigits1 = BigInteger.ZERO;
- BigInteger leftDigits2;
- if (numberSquared.toString().contains("0")) {
- leftDigits1 = new BigInteger(numberSquared.toString().substring(0, numberSquared.toString().indexOf("0")));
- }
- leftDigits2 = new BigInteger(numberSquared.toString().substring(0, (numberSquared.toString().length() - number.length())));
- BigInteger rightDigits = new BigInteger(numberSquared.toString().substring(numberSquared.toString().length() - number.length()));
- String x = leftDigits1.add(rightDigits).toString();
- String y = leftDigits2.add(rightDigits).toString();
- return (number.equals(x)) || (number.equals(y));
+ String squaredStr = numberSquared.toString();
+
+ // Special case: if the squared number has the same length as the original
+ if (number.length() == squaredStr.length()) {
+ return number.equals(squaredStr);
+ }
+
+ // Calculate the split position
+ int splitPos = squaredStr.length() - number.length();
+
+ // Split the squared number into left and right parts
+ String leftPart = squaredStr.substring(0, splitPos);
+ String rightPart = squaredStr.substring(splitPos);
+
+ // Parse the parts as BigInteger (handles empty left part as zero)
+ BigInteger leftNum = leftPart.isEmpty() ? BigInteger.ZERO : new BigInteger(leftPart);
+ BigInteger rightNum = new BigInteger(rightPart);
+
+ // Check if right part is all zeros (invalid for Kaprekar numbers except 1)
+ if (rightNum.equals(BigInteger.ZERO)) {
+ return false;
}
+
+ // Check if the sum equals the original number
+ return leftNum.add(rightNum).equals(originalNumber);
}
}
diff --git a/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java b/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java
index 05e58cf88e22..a3cd7500b30c 100644
--- a/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java
+++ b/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java
@@ -1,85 +1,188 @@
package com.thealgorithms.maths;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
-public class KaprekarNumbersTest {
+/**
+ * Test class for {@link KaprekarNumbers}.
+ * Tests various Kaprekar numbers and edge cases to ensure full coverage.
+ */
+class KaprekarNumbersTest {
@Test
- void testFor1() {
+ void testZeroIsKaprekarNumber() {
+ assertTrue(KaprekarNumbers.isKaprekarNumber(0));
+ }
+
+ @Test
+ void testOneIsKaprekarNumber() {
assertTrue(KaprekarNumbers.isKaprekarNumber(1));
}
@Test
- void testFor45() {
+ void testNineIsKaprekarNumber() {
+ // 9^2 = 81, 8 + 1 = 9
+ assertTrue(KaprekarNumbers.isKaprekarNumber(9));
+ }
+
+ @Test
+ void testFortyFiveIsKaprekarNumber() {
+ // 45^2 = 2025, 20 + 25 = 45
assertTrue(KaprekarNumbers.isKaprekarNumber(45));
}
@Test
- void testFor297() {
+ void testFiftyFiveIsKaprekarNumber() {
+ // 55^2 = 3025, 30 + 25 = 55
+ assertTrue(KaprekarNumbers.isKaprekarNumber(55));
+ }
+
+ @Test
+ void testNinetyNineIsKaprekarNumber() {
+ // 99^2 = 9801, 98 + 01 = 99
+ assertTrue(KaprekarNumbers.isKaprekarNumber(99));
+ }
+
+ @Test
+ void testTwoNinetySevenIsKaprekarNumber() {
+ // 297^2 = 88209, 88 + 209 = 297
assertTrue(KaprekarNumbers.isKaprekarNumber(297));
}
@Test
- void testFor2223() {
+ void testSevenZeroThreeIsKaprekarNumber() {
+ // 703^2 = 494209, 494 + 209 = 703
+ assertTrue(KaprekarNumbers.isKaprekarNumber(703));
+ }
+
+ @Test
+ void testNineNineNineIsKaprekarNumber() {
+ // 999^2 = 998001, 998 + 001 = 999
+ assertTrue(KaprekarNumbers.isKaprekarNumber(999));
+ }
+
+ @Test
+ void testTwoTwoTwoThreeIsKaprekarNumber() {
+ // 2223^2 = 4941729, 4941 + 729 = 5670 (not directly obvious)
+ // Actually: 494 + 1729 = 2223
assertTrue(KaprekarNumbers.isKaprekarNumber(2223));
}
@Test
- void testFor857143() {
+ void testEightFiveSevenOneFortyThreeIsKaprekarNumber() {
assertTrue(KaprekarNumbers.isKaprekarNumber(857143));
}
@Test
- void testFor3() {
+ void testTwoIsNotKaprekarNumber() {
+ assertFalse(KaprekarNumbers.isKaprekarNumber(2));
+ }
+
+ @Test
+ void testThreeIsNotKaprekarNumber() {
assertFalse(KaprekarNumbers.isKaprekarNumber(3));
}
@Test
- void testFor26() {
+ void testTenIsNotKaprekarNumber() {
+ assertFalse(KaprekarNumbers.isKaprekarNumber(10));
+ }
+
+ @Test
+ void testTwentySixIsNotKaprekarNumber() {
assertFalse(KaprekarNumbers.isKaprekarNumber(26));
}
@Test
- void testFor98() {
+ void testNinetyEightIsNotKaprekarNumber() {
assertFalse(KaprekarNumbers.isKaprekarNumber(98));
}
@Test
- void testForRangeOfNumber() {
- try {
- List
+ * For example:
+ *
+ * The algorithm works as follows:
+ * Time complexity: O(E * V^2) in the worst case, but typically faster in practice
+ * and near O(E * sqrt(V)) for unit networks. The graph is represented using a capacity matrix where capacity[u][v] is the
+ * capacity of the directed edge u -> v. Capacities must be non-negative.
+ * The algorithm builds level graphs using BFS and finds blocking flows using DFS
+ * with current-edge optimization. This implementation mirrors the API and validation style of
+ * {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.
+ * Leonardo numbers are a sequence of numbers defined by the recurrence:
+ * L(n) = L(n-1) + L(n-2) + 1, with L(0) = 1 and L(1) = 1
+ *
+ * The sequence begins: 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, ...
+ *
+ * This class provides both a recursive implementation and an optimized
+ * iterative
+ * implementation for calculating Leonardo numbers.
+ *
+ * @see Leonardo Number
+ * - Wikipedia
+ * @see OEIS A001595
*/
public final class LeonardoNumber {
private LeonardoNumber() {
}
/**
- * Calculate nth Leonardo Number (1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, ...)
+ * Calculates the nth Leonardo Number using recursion.
+ *
+ * Time Complexity: O(2^n) - exponential due to repeated calculations
+ * Space Complexity: O(n) - due to recursion stack
+ *
+ * Note: This method is not recommended for large values of n due to exponential
+ * time complexity.
+ * Consider using {@link #leonardoNumberIterative(int)} for better performance.
*
- * @param n the index of Leonardo Number to calculate
- * @return nth number of Leonardo sequences
+ * @param n the index of the Leonardo Number to calculate (must be non-negative)
+ * @return the nth Leonardo Number
+ * @throws IllegalArgumentException if n is negative
*/
public static int leonardoNumber(int n) {
if (n < 0) {
- throw new ArithmeticException();
+ throw new IllegalArgumentException("Input must be non-negative. Received: " + n);
}
if (n == 0 || n == 1) {
return 1;
}
- return (leonardoNumber(n - 1) + leonardoNumber(n - 2) + 1);
+ return leonardoNumber(n - 1) + leonardoNumber(n - 2) + 1;
+ }
+
+ /**
+ * Calculates the nth Leonardo Number using an iterative approach.
+ *
+ * This method provides better performance than the recursive version for large
+ * values of n.
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(1)
+ *
+ * @param n the index of the Leonardo Number to calculate (must be non-negative)
+ * @return the nth Leonardo Number
+ * @throws IllegalArgumentException if n is negative
+ */
+ public static int leonardoNumberIterative(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative. Received: " + n);
+ }
+ if (n == 0 || n == 1) {
+ return 1;
+ }
+
+ int previous = 1;
+ int current = 1;
+
+ for (int i = 2; i <= n; i++) {
+ int next = current + previous + 1;
+ previous = current;
+ current = next;
+ }
+
+ return current;
}
}
diff --git a/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java b/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java
index 705f1a1006fa..baf4540cf239 100644
--- a/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java
+++ b/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java
@@ -1,29 +1,171 @@
package com.thealgorithms.maths;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-public class LeonardoNumberTest {
+/**
+ * Test cases for {@link LeonardoNumber} class.
+ *
+ * Tests both recursive and iterative implementations with various input values
+ * including edge cases and boundary conditions.
+ */
+class LeonardoNumberTest {
+
+ // Tests for recursive implementation
+
+ @Test
+ void testLeonardoNumberNegative() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumber(-1));
+ }
+
+ @Test
+ void testLeonardoNumberNegativeLarge() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumber(-100));
+ }
+
+ @Test
+ void testLeonardoNumberZero() {
+ Assertions.assertEquals(1, LeonardoNumber.leonardoNumber(0));
+ }
+
+ @Test
+ void testLeonardoNumberOne() {
+ Assertions.assertEquals(1, LeonardoNumber.leonardoNumber(1));
+ }
+
+ @Test
+ void testLeonardoNumberTwo() {
+ Assertions.assertEquals(3, LeonardoNumber.leonardoNumber(2));
+ }
+
+ @Test
+ void testLeonardoNumberThree() {
+ Assertions.assertEquals(5, LeonardoNumber.leonardoNumber(3));
+ }
+
+ @Test
+ void testLeonardoNumberFour() {
+ Assertions.assertEquals(9, LeonardoNumber.leonardoNumber(4));
+ }
+
+ @Test
+ void testLeonardoNumberFive() {
+ Assertions.assertEquals(15, LeonardoNumber.leonardoNumber(5));
+ }
+
+ @Test
+ void testLeonardoNumberSix() {
+ Assertions.assertEquals(25, LeonardoNumber.leonardoNumber(6));
+ }
+
+ @Test
+ void testLeonardoNumberSeven() {
+ Assertions.assertEquals(41, LeonardoNumber.leonardoNumber(7));
+ }
+
+ @Test
+ void testLeonardoNumberEight() {
+ Assertions.assertEquals(67, LeonardoNumber.leonardoNumber(8));
+ }
+
+ @Test
+ void testLeonardoNumberTen() {
+ Assertions.assertEquals(177, LeonardoNumber.leonardoNumber(10));
+ }
+
+ @Test
+ void testLeonardoNumberFifteen() {
+ Assertions.assertEquals(1973, LeonardoNumber.leonardoNumber(15));
+ }
+
@Test
- void leonardoNumberNegative() {
- assertThrows(ArithmeticException.class, () -> LeonardoNumber.leonardoNumber(-1));
+ void testLeonardoNumberTwenty() {
+ Assertions.assertEquals(21891, LeonardoNumber.leonardoNumber(20));
}
+
+ // Tests for iterative implementation
+
+ @Test
+ void testLeonardoNumberIterativeNegative() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumberIterative(-1));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeNegativeLarge() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumberIterative(-50));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeZero() {
+ Assertions.assertEquals(1, LeonardoNumber.leonardoNumberIterative(0));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeOne() {
+ Assertions.assertEquals(1, LeonardoNumber.leonardoNumberIterative(1));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeTwo() {
+ Assertions.assertEquals(3, LeonardoNumber.leonardoNumberIterative(2));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeThree() {
+ Assertions.assertEquals(5, LeonardoNumber.leonardoNumberIterative(3));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeFour() {
+ Assertions.assertEquals(9, LeonardoNumber.leonardoNumberIterative(4));
+ }
+
@Test
- void leonardoNumberZero() {
- assertEquals(1, LeonardoNumber.leonardoNumber(0));
+ void testLeonardoNumberIterativeFive() {
+ Assertions.assertEquals(15, LeonardoNumber.leonardoNumberIterative(5));
}
+
@Test
- void leonardoNumberOne() {
- assertEquals(1, LeonardoNumber.leonardoNumber(1));
+ void testLeonardoNumberIterativeSix() {
+ Assertions.assertEquals(25, LeonardoNumber.leonardoNumberIterative(6));
}
+
@Test
- void leonardoNumberFive() {
- assertEquals(15, LeonardoNumber.leonardoNumber(5));
+ void testLeonardoNumberIterativeSeven() {
+ Assertions.assertEquals(41, LeonardoNumber.leonardoNumberIterative(7));
}
+
+ @Test
+ void testLeonardoNumberIterativeEight() {
+ Assertions.assertEquals(67, LeonardoNumber.leonardoNumberIterative(8));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeTen() {
+ Assertions.assertEquals(177, LeonardoNumber.leonardoNumberIterative(10));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeFifteen() {
+ Assertions.assertEquals(1973, LeonardoNumber.leonardoNumberIterative(15));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeTwenty() {
+ Assertions.assertEquals(21891, LeonardoNumber.leonardoNumberIterative(20));
+ }
+
+ @Test
+ void testLeonardoNumberIterativeTwentyFive() {
+ Assertions.assertEquals(242785, LeonardoNumber.leonardoNumberIterative(25));
+ }
+
+ // Consistency tests between recursive and iterative implementations
+
@Test
- void leonardoNumberTwenty() {
- assertEquals(21891, LeonardoNumber.leonardoNumber(20));
+ void testConsistencyBetweenImplementations() {
+ for (int i = 0; i <= 15; i++) {
+ Assertions.assertEquals(LeonardoNumber.leonardoNumber(i), LeonardoNumber.leonardoNumberIterative(i), "Mismatch at index " + i);
+ }
}
}
From 8b8434cb51bc6a81317760ff18b28c42797fee85 Mon Sep 17 00:00:00 2001
From: Microindole <1513979779@qq.com>
Date: Mon, 13 Oct 2025 20:47:50 +0800
Subject: [PATCH 070/203] feat(graph): Add Edmonds's algorithm for minimum
spanning arborescence (#6771)
* feat(graph): Add Edmonds's algorithm for minimum spanning arborescence
* test: Add test cases to achieve 100% coverage
---
.../java/com/thealgorithms/graph/Edmonds.java | 201 ++++++++++++++++++
.../com/thealgorithms/graph/EdmondsTest.java | 172 +++++++++++++++
2 files changed, 373 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/graph/Edmonds.java
create mode 100644 src/test/java/com/thealgorithms/graph/EdmondsTest.java
diff --git a/src/main/java/com/thealgorithms/graph/Edmonds.java b/src/main/java/com/thealgorithms/graph/Edmonds.java
new file mode 100644
index 000000000000..4ddb8f9ff544
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/Edmonds.java
@@ -0,0 +1,201 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An implementation of Edmonds's algorithm (also known as the Chu–Liu/Edmonds algorithm)
+ * for finding a Minimum Spanning Arborescence (MSA).
+ *
+ * An MSA is a directed graph equivalent of a Minimum Spanning Tree. It is a tree rooted
+ * at a specific vertex 'r' that reaches all other vertices, such that the sum of the
+ * weights of its edges is minimized.
+ *
+ * The algorithm works recursively:
+ * Time Complexity: O(E * V) where E is the number of edges and V is the number of vertices.
+ *
+ * References:
+ * Input is an adjacency matrix of edge weights. A value of -1 indicates no edge.
+ * All existing edge weights must be non-negative. Zero-weight edges are allowed. References:
+ * - Wikipedia: Yen's algorithm (https://en.wikipedia.org/wiki/Yen%27s_algorithm)
+ * - Dijkstra's algorithm for the base shortest path computation.
+ * The algorithm works by:
+ * 1. Storing the last element of the array
+ * 2. Placing the search key at the last position (sentinel)
+ * 3. Searching from the beginning without bound checking
+ * 4. If found before the last position, return the index
+ * 5. If found at the last position, check if it was originally there
+ *
+ *
+ * Time Complexity:
+ * - Best case: O(1) - when the element is at the first position
+ * - Average case: O(n) - when the element is in the middle
+ * - Worst case: O(n) - when the element is not present
+ *
+ *
+ * Space Complexity: O(1) - only uses constant extra space
+ *
+ *
+ * Advantages over regular linear search:
+ * - Reduces the number of comparisons by eliminating bound checking
+ * - Slightly more efficient in practice due to fewer conditional checks
+ *
+ * @author TheAlgorithms Contributors
+ * @see LinearSearch
+ * @see SearchAlgorithm
+ */
+public class SentinelLinearSearch implements SearchAlgorithm {
+ /**
+ * Performs sentinel linear search on the given array.
+ *
+ * @param array the array to search in
+ * @param key the element to search for
+ * @param Run-Length Encoding is a simple form of lossless data compression in which
+ * runs of data (sequences in which the same data value occurs in many
+ * consecutive data elements) are stored as a single data value and count,
+ * rather than as the original run.
+ *
+ * This implementation provides methods for both compressing and decompressing
+ * a string. For example:
+ * Time Complexity: O(n) for both compression and decompression, where n is the
+ * length of the input string.
+ *
+ * References:
+ * Shannon-Fano coding is an entropy encoding technique for lossless data
+ * compression. It assigns variable-length codes to symbols based on their
+ * frequencies of occurrence. It is a precursor to Huffman coding and works by
+ * recursively partitioning a sorted list of symbols into two sub-lists with
+ * nearly equal total frequencies.
+ *
+ * The algorithm works as follows:
+ * Time Complexity: O(n^2) in this implementation due to the partitioning logic,
+ * or O(n log n) if a more optimized partitioning strategy is used.
+ * Sorting takes O(n log n), where n is the number of unique symbols.
+ *
+ * References:
+ *
+ * A linear Diophantine equation is an equation in which only integer solutions
+ * are allowed.
+ * This solver uses the Extended Euclidean Algorithm to find integer solutions
+ * (x, y)
+ * for equations of the form ax + by = c, where a, b, and c are integers.
+ *
+ * The equation has solutions if and only if gcd(a, b) divides c.
+ * If solutions exist, this solver finds one particular solution.
+ *
+ * The method returns one of three types of solutions:
+ *
+ * This method also finds coefficients x and y such that ax + by = gcd(a, b).
+ * The coefficients are stored in the 'previous' wrapper object.
+ *
+ * Special instances:
+ *
+ * This class is used internally to pass results between recursive calls
+ * of the GCD computation.
+ *
+ * Heron's Formula states that the area of a triangle whose sides have lengths
+ * a, b, and c is:
+ * Area = √(s(s - a)(s - b)(s - c))
+ * where s is the semi-perimeter of the triangle: s = (a + b + c) / 2
+ *
+ * The triangle inequality theorem states that the sum of any two sides
+ * of a triangle must be greater than the third side.
+ *
+ * Given three side lengths a, b, and c, the area is computed as:
+ * Area = √(s(s - a)(s - b)(s - c))
+ * where s is the semi-perimeter: s = (a + b + c) / 2
+ *
- * by: Punit Patel
+ * This class provides static methods to calculate different types of means
+ * (averages)
+ * from a collection of numbers. All methods accept any {@link Iterable}
+ * collection of
+ * {@link Double} values and return the computed mean as a {@link Double}.
+ *
+ * Supported means:
+ *
+ * The arithmetic mean is calculated as: (x₁ + x₂ + ... + xₙ) / n
+ *
+ * Example: For numbers [2, 4, 6], the arithmetic mean is (2+4+6)/3 = 4.0
+ *
+ * The geometric mean is calculated as: ⁿ√(x₁ × x₂ × ... × xₙ)
+ *
+ * Example: For numbers [2, 8], the geometric mean is √(2×8) = √16 = 4.0
+ *
+ * Note: This method may produce unexpected results for negative numbers,
+ * as it computes the real-valued nth root which may not exist for negative
+ * products.
+ *
+ * The harmonic mean is calculated as: n / (1/x₁ + 1/x₂ + ... + 1/xₙ)
+ *
+ * Example: For numbers [1, 2, 4], the harmonic mean is 3/(1/1 + 1/2 + 1/4) =
+ * 3/1.75 ≈ 1.714
+ *
+ * Note: This method will produce unexpected results if any input number is
+ * zero,
+ * as it involves computing reciprocals.
+ *
+ * This class provides comprehensive test coverage for all mean calculation
+ * methods,
+ * including edge cases, various collection types, and error conditions.
+ * Input graph is a capacity matrix where {@code capacity[u][v]} is the capacity of the edge
+ * {@code u -> v}. Capacities must be non-negative. Vertices are indexed in {@code [0, n)}.
+ *
+ * Time complexity: O(V^3) in the worst case for the array-based variant; typically fast in
+ * practice. This implementation uses a residual network over an adjacency-matrix representation.
+ *
+ * The API mirrors {@link EdmondsKarp#maxFlow(int[][], int, int)} and {@link Dinic#maxFlow(int[][], int, int)}.
+ *
+ * @see Wikipedia: Push–Relabel maximum flow algorithm
+ */
+public final class PushRelabel {
+
+ private PushRelabel() {
+ }
+
+ /**
+ * Computes the maximum flow from {@code source} to {@code sink} using Push–Relabel.
+ *
+ * @param capacity square capacity matrix (n x n); entries must be >= 0
+ * @param source source vertex index in [0, n)
+ * @param sink sink vertex index in [0, n)
+ * @return the maximum flow value
+ * @throws IllegalArgumentException if inputs are invalid
+ */
+ public static int maxFlow(int[][] capacity, int source, int sink) {
+ validate(capacity, source, sink);
+ final int n = capacity.length;
+ if (source == sink) {
+ return 0;
+ }
+
+ int[][] residual = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ residual[i] = Arrays.copyOf(capacity[i], n);
+ }
+
+ int[] height = new int[n];
+ int[] excess = new int[n];
+ int[] nextNeighbor = new int[n];
+
+ // Preflow initialization
+ height[source] = n;
+ for (int v = 0; v < n; v++) {
+ int cap = residual[source][v];
+ if (cap > 0) {
+ residual[source][v] -= cap;
+ residual[v][source] += cap;
+ excess[v] += cap;
+ excess[source] -= cap;
+ }
+ }
+
+ // Active queue contains vertices (except source/sink) with positive excess
+ Queue Given an n x m cost matrix (n tasks, m workers), finds a minimum-cost
+ * one-to-one assignment. If the matrix is rectangular, the algorithm pads to a
+ * square internally. Costs must be finite non-negative integers.
+ *
+ * Time complexity: O(n^3) with n = max(rows, cols).
+ *
+ * API returns the assignment as an array where {@code assignment[i]} is the
+ * column chosen for row i (or -1 if unassigned when rows != cols), and a total
+ * minimal cost.
+ *
+ * @see Wikipedia: Hungarian algorithm
+ */
+public final class HungarianAlgorithm {
+
+ private HungarianAlgorithm() {
+ }
+
+ /** Result holder for the Hungarian algorithm. */
+ public static final class Result {
+ public final int[] assignment; // assignment[row] = col or -1
+ public final int minCost;
+
+ public Result(int[] assignment, int minCost) {
+ this.assignment = assignment;
+ this.minCost = minCost;
+ }
+ }
+
+ /**
+ * Solves the assignment problem for a non-negative cost matrix.
+ *
+ * @param cost an r x c matrix of non-negative costs
+ * @return Result with row-to-column assignment and minimal total cost
+ * @throws IllegalArgumentException for null/empty or negative costs
+ */
+ public static Result solve(int[][] cost) {
+ validate(cost);
+ int rows = cost.length;
+ int cols = cost[0].length;
+ int n = Math.max(rows, cols);
+
+ // Build square matrix with padding 0 for missing cells
+ int[][] a = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ if (i < rows) {
+ for (int j = 0; j < n; j++) {
+ a[i][j] = (j < cols) ? cost[i][j] : 0;
+ }
+ } else {
+ Arrays.fill(a[i], 0);
+ }
+ }
+
+ // Potentials and matching arrays
+ int[] u = new int[n + 1];
+ int[] v = new int[n + 1];
+ int[] p = new int[n + 1];
+ int[] way = new int[n + 1];
+
+ for (int i = 1; i <= n; i++) {
+ p[0] = i;
+ int j0 = 0;
+ int[] minv = new int[n + 1];
+ boolean[] used = new boolean[n + 1];
+ Arrays.fill(minv, Integer.MAX_VALUE);
+ Arrays.fill(used, false);
+ do {
+ used[j0] = true;
+ int i0 = p[j0];
+ int delta = Integer.MAX_VALUE;
+ int j1 = 0;
+ for (int j = 1; j <= n; j++) {
+ if (!used[j]) {
+ int cur = a[i0 - 1][j - 1] - u[i0] - v[j];
+ if (cur < minv[j]) {
+ minv[j] = cur;
+ way[j] = j0;
+ }
+ if (minv[j] < delta) {
+ delta = minv[j];
+ j1 = j;
+ }
+ }
+ }
+ for (int j = 0; j <= n; j++) {
+ if (used[j]) {
+ u[p[j]] += delta;
+ v[j] -= delta;
+ } else {
+ minv[j] -= delta;
+ }
+ }
+ j0 = j1;
+ } while (p[j0] != 0);
+ do {
+ int j1 = way[j0];
+ p[j0] = p[j1];
+ j0 = j1;
+ } while (j0 != 0);
+ }
+
+ int[] matchColForRow = new int[n];
+ Arrays.fill(matchColForRow, -1);
+ for (int j = 1; j <= n; j++) {
+ if (p[j] != 0) {
+ matchColForRow[p[j] - 1] = j - 1;
+ }
+ }
+
+ // Build assignment for original rows only, ignore padded rows
+ int[] assignment = new int[rows];
+ Arrays.fill(assignment, -1);
+ int total = 0;
+ for (int i = 0; i < rows; i++) {
+ int j = matchColForRow[i];
+ if (j >= 0 && j < cols) {
+ assignment[i] = j;
+ total += cost[i][j];
+ }
+ }
+ return new Result(assignment, total);
+ }
+
+ private static void validate(int[][] cost) {
+ if (cost == null || cost.length == 0) {
+ throw new IllegalArgumentException("Cost matrix must not be null or empty");
+ }
+ int c = cost[0].length;
+ if (c == 0) {
+ throw new IllegalArgumentException("Cost matrix must have at least 1 column");
+ }
+ for (int i = 0; i < cost.length; i++) {
+ if (cost[i] == null || cost[i].length != c) {
+ throw new IllegalArgumentException("Cost matrix must be rectangular with equal row lengths");
+ }
+ for (int j = 0; j < c; j++) {
+ if (cost[i][j] < 0) {
+ throw new IllegalArgumentException("Costs must be non-negative");
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java
new file mode 100644
index 000000000000..1344934b62e4
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class HungarianAlgorithmTest {
+
+ @Test
+ @DisplayName("Classic 3x3 example: minimal cost 5 with assignment [1,0,2]")
+ void classicSquareExample() {
+ int[][] cost = {{4, 1, 3}, {2, 0, 5}, {3, 2, 2}};
+ HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost);
+ assertEquals(5, res.minCost);
+ assertArrayEquals(new int[] {1, 0, 2}, res.assignment);
+ }
+
+ @Test
+ @DisplayName("Rectangular (more rows than cols): pads to square and returns -1 for unassigned rows")
+ void rectangularMoreRows() {
+ int[][] cost = {{7, 3}, {2, 8}, {5, 1}};
+ // Optimal selects any 2 rows: choose row1->col0 (2) and row2->col1 (1) => total 3
+ HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost);
+ assertEquals(3, res.minCost);
+ // Two rows assigned to 2 columns; one row remains -1.
+ int assigned = 0;
+ for (int a : res.assignment) {
+ if (a >= 0) {
+ assigned++;
+ }
+ }
+ assertEquals(2, assigned);
+ }
+
+ @Test
+ @DisplayName("Zero diagonal yields zero total cost")
+ void zeroDiagonal() {
+ int[][] cost = {{0, 5, 9}, {4, 0, 7}, {3, 6, 0}};
+ HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost);
+ assertEquals(0, res.minCost);
+ }
+}
From e1773e9165da4496c598a9a6e4516cb3d0833304 Mon Sep 17 00:00:00 2001
From: Yash Rajput <52812070+the-yash-rajput@users.noreply.github.com>
Date: Fri, 17 Oct 2025 02:49:09 +0530
Subject: [PATCH 087/203] Adding DampedOscillator code (#6801)
* Adding DampedOscillator code
* Adding one more test case
* Adding one more test case
* Adding one more test case
* Fixing build issues.
* Fixing build issues.
---
.../physics/DampedOscillator.java | 109 +++++++++++++
.../physics/DampedOscillatorTest.java | 143 ++++++++++++++++++
2 files changed, 252 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/physics/DampedOscillator.java
create mode 100644 src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java
diff --git a/src/main/java/com/thealgorithms/physics/DampedOscillator.java b/src/main/java/com/thealgorithms/physics/DampedOscillator.java
new file mode 100644
index 000000000000..84028b628e77
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/DampedOscillator.java
@@ -0,0 +1,109 @@
+package com.thealgorithms.physics;
+
+/**
+ * Models a damped harmonic oscillator, capturing the behavior of a mass-spring-damper system.
+ *
+ * The system is defined by the second-order differential equation:
+ * x'' + 2 * gamma * x' + omega₀² * x = 0
+ * where:
+ * This implementation provides:
+ * Usage Example:
+ * Tests focus on:
+ *
+ * An Eulerian Circuit is a path that starts and ends at the same vertex
+ * and visits every edge exactly once.
+ *
+ * An Eulerian Path visits every edge exactly once but may start and end
+ * at different vertices.
+ *
+ * Algorithm Summary:
+ * Time Complexity: O(E + V). Coverage includes:
+ *
+ * Arithmetic coding is a form of entropy encoding used in lossless data
+ * compression. It encodes an entire message into a single number, a fraction n
+ * where (0.0 <= n < 1.0). Unlike Huffman coding, which assigns a specific
+ * bit sequence to each symbol, arithmetic coding represents the message as a
+ * sub-interval of the [0, 1) interval.
+ *
+ * This implementation uses BigDecimal for precision to handle the shrinking
+ * intervals, making it suitable for educational purposes to demonstrate the
+ * core logic.
+ *
+ * Time Complexity: O(n*m) for compression and decompression where n is the
+ * length of the input and m is the number of unique symbols, due to the need
+ * to calculate symbol probabilities.
+ *
+ * References:
+ *
+ * LZW is a universal lossless data compression algorithm created by Abraham
+ * Lempel, Jacob Ziv, and Terry Welch. It works by building a dictionary of
+ * strings encountered during compression and replacing occurrences of those
+ * strings with a shorter code.
+ *
+ * This implementation handles standard ASCII characters and provides methods for
+ * both compression and decompression.
+ * > graph, int source, int maxEdgeWeight) {
+ int numVertices = graph.size();
+ if (source < 0 || source >= numVertices) {
+ throw new IllegalArgumentException("Source vertex is out of bounds.");
+ }
+
+ // Initialize distances array
+ int[] distances = new int[numVertices];
+ Arrays.fill(distances, Integer.MAX_VALUE);
+ distances[source] = 0;
+
+ // The bucket queue. Size is determined by the max possible path length.
+ int maxPathWeight = maxEdgeWeight * (numVertices > 0 ? numVertices - 1 : 0);
+ List
> graph;
+ private static final int NUM_VERTICES = 6;
+ private static final int MAX_EDGE_WEIGHT = 10;
+
+ @BeforeEach
+ void setUp() {
+ graph = new ArrayList<>();
+ for (int i = 0; i < NUM_VERTICES; i++) {
+ graph.add(new ArrayList<>());
+ }
+ }
+
+ private void addEdge(int u, int v, int weight) {
+ graph.get(u).add(new DialsAlgorithm.Edge(v, weight));
+ }
+
+ @Test
+ @DisplayName("Test with a simple connected graph")
+ void testSimpleGraph() {
+ // Build graph from a standard example
+ addEdge(0, 1, 2);
+ addEdge(0, 2, 4);
+ addEdge(1, 2, 1);
+ addEdge(1, 3, 7);
+ addEdge(2, 4, 3);
+ addEdge(3, 5, 1);
+ addEdge(4, 3, 2);
+ addEdge(4, 5, 5);
+
+ int[] expectedDistances = {0, 2, 3, 8, 6, 9};
+ int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT);
+ assertArrayEquals(expectedDistances, actualDistances);
+ }
+
+ @Test
+ @DisplayName("Test with a disconnected node")
+ void testDisconnectedNode() {
+ addEdge(0, 1, 5);
+ addEdge(1, 2, 5);
+ // Node 3, 4, 5 are disconnected
+
+ int[] expectedDistances = {0, 5, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
+ int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT);
+ assertArrayEquals(expectedDistances, actualDistances);
+ }
+
+ @Test
+ @DisplayName("Test with source as destination")
+ void testSourceIsDestination() {
+ addEdge(0, 1, 10);
+ int[] expectedDistances = {0, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
+ // Run with source 0
+ int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT);
+ assertArrayEquals(expectedDistances, actualDistances);
+ }
+
+ @Test
+ @DisplayName("Test graph with multiple paths to a node")
+ void testMultiplePaths() {
+ addEdge(0, 1, 10);
+ addEdge(0, 2, 3);
+ addEdge(2, 1, 2); // Shorter path to 1 is via 2 (3+2=5)
+
+ int[] expectedDistances = {0, 5, 3, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
+ int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT);
+ assertArrayEquals(expectedDistances, actualDistances);
+ }
+
+ @Test
+ @DisplayName("Test with an invalid source vertex")
+ void testInvalidSource() {
+ assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, -1, MAX_EDGE_WEIGHT));
+ assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, NUM_VERTICES, MAX_EDGE_WEIGHT));
+ }
+}
From 74ddea6747808f563189c175c47611286a09ae1e Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 13 Oct 2025 01:34:32 +0530
Subject: [PATCH 063/203] refactor: Enhance docs, code, add tests in
`KrishnaMurthyNumber` (#6742)
* refactor: Enhance docs, code, add tests in `KrishnaMurthyNumber`
* Lint
---
.../maths/KrishnamurthyNumber.java | 60 ++++++++-----
.../thealgorithms/others/Krishnamurthy.java | 38 --------
.../maths/KrishnamurthyNumberTest.java | 86 ++++++++++++++++---
3 files changed, 111 insertions(+), 73 deletions(-)
delete mode 100644 src/main/java/com/thealgorithms/others/Krishnamurthy.java
diff --git a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
index 03f18aca786f..a00ef7e3b15d 100644
--- a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
+++ b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
@@ -3,10 +3,25 @@
/**
* Utility class for checking if a number is a Krishnamurthy number.
*
- * A Krishnamurthy number (also known as a Strong number) is a number whose sum of the factorials of its digits is equal to the number itself.
+ *
* boolean isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(145);
* System.out.println(isKrishnamurthy); // Output: true
@@ -14,40 +29,43 @@
* isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(123);
* System.out.println(isKrishnamurthy); // Output: false
*
+ *
+ * @see Factorion
+ * (Wikipedia)
*/
public final class KrishnamurthyNumber {
+ // Pre-computed factorials for digits 0-9 to improve performance
+ private static final int[] FACTORIALS = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
+
private KrishnamurthyNumber() {
}
/**
* Checks if a number is a Krishnamurthy number.
*
- * @param n The number to check
+ *
+ * 1. From each clause (a ∨ b), we can derive implications:
+ * (¬a → b) and (¬b → a)
+ *
+ * 2. We construct an implication graph using these implications.
+ *
+ * 3. For each variable x, its negation ¬x is also represented as a node.
+ * If x and ¬x belong to the same SCC, the expression is unsatisfiable.
+ *
+ * 4. Otherwise, we assign truth values based on the SCC order:
+ * If SCC(x) > SCC(¬x), then x = true; otherwise, x = false.
+ *
+ *
+ *
+ *
+ * where {@code n} is the number of variables and {@code m} is the number of
+ * clauses.
+ *
+ *
+ * TwoSat twoSat = new TwoSat(5); // Initialize with 5 variables: x1, x2, x3, x4, x5
+ *
+ * // Add clauses
+ * twoSat.addClause(1, false, 2, false); // (x1 ∨ x2)
+ * twoSat.addClause(3, true, 2, false); // (¬x3 ∨ x2)
+ * twoSat.addClause(4, false, 5, true); // (x4 ∨ ¬x5)
+ *
+ * twoSat.solve(); // Solve the problem
+ *
+ * if (twoSat.isSolutionExists()) {
+ * boolean[] solution = twoSat.getSolutions();
+ * for (int i = 1; i <= 5; i++) {
+ * System.out.println("x" + i + " = " + solution[i]);
+ * }
+ * }
+ *
+ *
+ * Wikipedia - 2 SAT
+ * @author Shoyeb Ansari
+ *
+ * @see Kosaraju
+ */
+class TwoSat {
+
+ /** Number of variables in the boolean expression. */
+ private final int numberOfVariables;
+
+ /** Implication graph built from the boolean clauses. */
+ private final ArrayList{@code
+ * addClause(1, true, 2, false);
+ * }
+ *
+ * @param a the first variable (1 ≤ a ≤ numberOfVariables)
+ * @param isNegateA {@code true} if variable {@code a} is negated
+ * @param b the second variable (1 ≤ b ≤ numberOfVariables)
+ * @param isNegateB {@code true} if variable {@code b} is negated
+ * @throws IllegalArgumentException if {@code a} or {@code b} are out of range
+ */
+ void addClause(int a, boolean isNegateA, int b, boolean isNegateB) {
+ if (a <= 0 || a > numberOfVariables) {
+ throw new IllegalArgumentException("Variable number must be between 1 and " + numberOfVariables);
+ }
+ if (b <= 0 || b > numberOfVariables) {
+ throw new IllegalArgumentException("Variable number must be between 1 and " + numberOfVariables);
+ }
+
+ a = isNegateA ? negate(a) : a;
+ b = isNegateB ? negate(b) : b;
+ int notA = negate(a);
+ int notB = negate(b);
+
+ // Add implications: (¬a → b) and (¬b → a)
+ graph[notA].add(b);
+ graph[notB].add(a);
+
+ // Build transpose graph
+ graphTranspose[b].add(notA);
+ graphTranspose[a].add(notB);
+ }
+
+ /**
+ * Solves the 2-SAT problem using Kosaraju's algorithm to find SCCs
+ * and determines whether a satisfying assignment exists.
+ */
+ void solve() {
+ isSolved = true;
+ int n = 2 * numberOfVariables + 1;
+
+ boolean[] visited = new boolean[n];
+ int[] component = new int[n];
+ Stack
+ * For a variable i:
+ * negate(i) = i + n
+ * For a negated variable (i + n):
+ * negate(i + n) = i
+ * where n = numberOfVariables
+ *
+ *
+ * @param a the variable index
+ * @return the index representing its negation
+ */
+ private int negate(int a) {
+ return a <= numberOfVariables ? a + numberOfVariables : a - numberOfVariables;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java
new file mode 100644
index 000000000000..15e77b357f83
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java
@@ -0,0 +1,125 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Testcases for 2-SAT.
+ * Please note thea whlie checking for boolean assignments always keep n + 1 elements and the first element should be always false.
+ */
+public class TwoSatTest {
+ private TwoSat twoSat;
+
+ /**
+ * Case 1: Basic satisfiable case.
+ * Simple 3 clauses with consistent assignments.
+ */
+ @Test
+ public void testSatisfiableBasicCase() {
+ twoSat = new TwoSat(5);
+
+ twoSat.addClause(1, false, 2, false); // (x1 ∨ x2)
+ twoSat.addClause(3, true, 2, false); // (¬x3 ∨ x2)
+ twoSat.addClause(4, false, 5, true); // (x4 ∨ ¬x5)
+
+ twoSat.solve();
+
+ assertTrue(twoSat.isSolutionExists(), "Expected solution to exist");
+ boolean[] expected = {false, true, true, true, true, true};
+ assertArrayEquals(expected, twoSat.getSolutions());
+ }
+
+ /**
+ * Case 2: Unsatisfiable due to direct contradiction.
+ * (x1 ∨ x1) ∧ (¬x1 ∨ ¬x1) makes x1 and ¬x1 both required.
+ */
+ @Test
+ public void testUnsatisfiableContradiction() {
+ twoSat = new TwoSat(1);
+
+ twoSat.addClause(1, false, 1, false); // (x1 ∨ x1)
+ twoSat.addClause(1, true, 1, true); // (¬x1 ∨ ¬x1)
+
+ twoSat.solve();
+
+ assertFalse(twoSat.isSolutionExists(), "Expected no solution (contradiction)");
+ }
+
+ /**
+ * Case 3: Single variable, trivially satisfiable.
+ * Only (x1 ∨ x1) exists.
+ */
+ @Test
+ public void testSingleVariableTrivialSatisfiable() {
+ twoSat = new TwoSat(1);
+
+ twoSat.addClause(1, false, 1, false); // (x1 ∨ x1)
+
+ twoSat.solve();
+
+ assertTrue(twoSat.isSolutionExists(), "Expected solution to exist");
+ boolean[] expected = {false, true};
+ assertArrayEquals(expected, twoSat.getSolutions());
+ }
+
+ /**
+ * Case 4: Larger satisfiable system with dependencies.
+ * (x1 ∨ x2), (¬x2 ∨ x3), (¬x3 ∨ x4), (¬x4 ∨ x5)
+ */
+ @Test
+ public void testChainedDependenciesSatisfiable() {
+ twoSat = new TwoSat(5);
+
+ twoSat.addClause(1, false, 2, false);
+ twoSat.addClause(2, true, 3, false);
+ twoSat.addClause(3, true, 4, false);
+ twoSat.addClause(4, true, 5, false);
+
+ twoSat.solve();
+
+ assertTrue(twoSat.isSolutionExists(), "Expected solution to exist");
+ boolean[] solution = twoSat.getSolutions();
+ for (int i = 1; i <= 5; i++) {
+ assertTrue(solution[i], "Expected x" + i + " to be true");
+ }
+ }
+
+ /**
+ * Case 5: Contradiction due to dependency cycle.
+ * (x1 ∨ x2), (¬x1 ∨ ¬x2), (x1 ∨ ¬x2), (¬x1 ∨ x2)
+ * These clauses form a circular dependency making it impossible.
+ */
+ @Test
+ public void testUnsatisfiableCycle() {
+ twoSat = new TwoSat(2);
+
+ twoSat.addClause(1, false, 2, false);
+ twoSat.addClause(1, true, 2, true);
+ twoSat.addClause(1, false, 2, true);
+ twoSat.addClause(1, true, 2, false);
+
+ twoSat.solve();
+
+ assertFalse(twoSat.isSolutionExists(), "Expected no solution due to contradictory cycle");
+ }
+
+ /**
+ * Testcase from CSES
+ */
+ @Test
+ public void test6() {
+ twoSat = new TwoSat(2);
+
+ twoSat.addClause(1, true, 2, false);
+ twoSat.addClause(2, true, 1, false);
+ twoSat.addClause(1, true, 1, true);
+ twoSat.addClause(2, false, 2, false);
+
+ twoSat.solve();
+
+ assertFalse(twoSat.isSolutionExists(), "Expected no solution.");
+ }
+}
From ff9fd2e2e4d7ecdb5c0eb62e15c151b9b346eca0 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 13 Oct 2025 02:21:54 +0530
Subject: [PATCH 065/203] refactor: Enhance docs, code, add tests in `Median`
(#6751)
Co-authored-by: a
+ *
+ *
+ *
+ *
+ *
+ *
+ * @see Keith Number -
+ * Wikipedia
+ * @see Keith Number -
+ * MathWorld
+ */
+public final class KeithNumber {
private KeithNumber() {
}
- // user-defined function that checks if the given number is Keith or not
- static boolean isKeith(int x) {
- // List stores all the digits of the X
+ /**
+ * Checks if a given number is a Keith number.
+ *
+ *
+ *
+ *
+ * @param number the number to check (must be positive)
+ * @return {@code true} if the number is a Keith number, {@code false} otherwise
+ * @throws IllegalArgumentException if the number is not positive
+ */
+ public static boolean isKeith(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive");
+ }
+
+ // Extract digits and store them in the list
ArrayList
+ *
+ *
+ *
+ *
+ */
+public final class Edmonds {
+
+ private Edmonds() {
+ }
+
+ /**
+ * Represents a directed weighted edge in the graph.
+ */
+ public static class Edge {
+ final int from;
+ final int to;
+ final long weight;
+
+ /**
+ * Constructs a directed edge.
+ *
+ * @param from source vertex
+ * @param to destination vertex
+ * @param weight edge weight
+ */
+ public Edge(int from, int to, long weight) {
+ this.from = from;
+ this.to = to;
+ this.weight = weight;
+ }
+ }
+
+ /**
+ * Computes the total weight of the Minimum Spanning Arborescence of a directed,
+ * weighted graph from a given root.
+ *
+ * @param numVertices the number of vertices, labeled {@code 0..numVertices-1}
+ * @param edges list of directed edges in the graph
+ * @param root the root vertex
+ * @return the total weight of the MSA. Returns -1 if not all vertices are reachable
+ * from the root or if a valid arborescence cannot be formed.
+ * @throws IllegalArgumentException if {@code numVertices <= 0} or {@code root} is out of range.
+ */
+ public static long findMinimumSpanningArborescence(int numVertices, List> kShortestPaths(int[][] weights, int src, int dst, int k) {
+ validate(weights, src, dst, k);
+ final int n = weights.length;
+ // Make a defensive copy to avoid mutating caller's matrix
+ int[][] weightsCopy = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ weightsCopy[i] = Arrays.copyOf(weights[i], n);
+ }
+
+ List
> result = new ArrayList<>(shortestPaths.size());
+ for (Path p : shortestPaths) {
+ result.add(new ArrayList<>(p.nodes));
+ }
+ return result;
+ }
+
+ private static void validate(int[][] weights, int src, int dst, int k) {
+ if (weights == null || weights.length == 0) {
+ throw new IllegalArgumentException("Weights matrix must not be null or empty");
+ }
+ int n = weights.length;
+ for (int i = 0; i < n; i++) {
+ if (weights[i] == null || weights[i].length != n) {
+ throw new IllegalArgumentException("Weights matrix must be square");
+ }
+ for (int j = 0; j < n; j++) {
+ int val = weights[i][j];
+ if (val < NO_EDGE) {
+ throw new IllegalArgumentException("Weights must be -1 (no edge) or >= 0");
+ }
+ }
+ }
+ if (src < 0 || dst < 0 || src >= n || dst >= n) {
+ throw new IllegalArgumentException("Invalid src/dst indices");
+ }
+ if (k < 1) {
+ throw new IllegalArgumentException("k must be >= 1");
+ }
+ }
+
+ private static boolean startsWith(List
> paths = YensKShortestPaths.kShortestPaths(w, 0, 3, 3);
+ // Expected K=3 loopless shortest paths from 0 to 3, ordered by total cost:
+ // 1) 0-1-3 (cost 2)
+ // 2) 0-2-3 (cost 3)
+ // 3) 0-1-2-3 (cost 3) -> tie with 0-2-3; tie-broken lexicographically by nodes
+ assertEquals(3, paths.size());
+ assertEquals(List.of(0, 1, 3), paths.get(0));
+ assertEquals(List.of(0, 1, 2, 3), paths.get(1)); // lexicographically before [0,2,3]
+ assertEquals(List.of(0, 2, 3), paths.get(2));
+ }
+
+ @Test
+ @DisplayName("K larger than available paths returns only existing ones")
+ void kLargerThanAvailable() {
+ int[][] w = {{-1, 1, -1}, {-1, -1, 1}, {-1, -1, -1}};
+ // Only one simple path 0->1->2
+ List
> paths = YensKShortestPaths.kShortestPaths(w, 0, 2, 5);
+ assertEquals(1, paths.size());
+ assertEquals(List.of(0, 1, 2), paths.get(0));
+ }
+
+ @Test
+ @DisplayName("No path returns empty list")
+ void noPath() {
+ int[][] w = {{-1, -1}, {-1, -1}};
+ List
> paths = YensKShortestPaths.kShortestPaths(w, 0, 1, 3);
+ assertEquals(0, paths.size());
+ }
+
+ @Test
+ @DisplayName("Source equals destination returns trivial path")
+ void sourceEqualsDestination() {
+ int[][] w = {{-1, 1}, {-1, -1}};
+ List
> paths = YensKShortestPaths.kShortestPaths(w, 0, 0, 2);
+ // First path is [0]
+ assertEquals(1, paths.size());
+ assertEquals(List.of(0), paths.get(0));
+ }
+
+ @Test
+ @DisplayName("Negative weight entries (other than -1) are rejected")
+ void negativeWeightsRejected() {
+ int[][] w = {{-1, -2}, {-1, -1}};
+ assertThrows(IllegalArgumentException.class, () -> YensKShortestPaths.kShortestPaths(w, 0, 1, 2));
+ }
+}
From 3c61271ff74e8b24214dc38077f595124e57cff7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 14 Oct 2025 10:43:12 +0200
Subject: [PATCH 072/203] chore(deps): bump com.puppycrawl.tools:checkstyle
from 12.0.0 to 12.0.1 (#6775)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 12.0.0 to 12.0.1.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-12.0.0...checkstyle-12.0.1)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 12.0.1
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
+ *
+ *
+ *
+ *
+ */
+public final class RunLengthEncoding {
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ */
+ private RunLengthEncoding() {
+ }
+
+ /**
+ * Compresses a string using the Run-Length Encoding algorithm.
+ *
+ * @param text The string to be compressed. Must not be null.
+ * @return The compressed string. Returns an empty string if the input is empty.
+ */
+ public static String compress(String text) {
+ if (text == null || text.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder compressed = new StringBuilder();
+ int count = 1;
+
+ for (int i = 0; i < text.length(); i++) {
+ // Check if it's the last character or if the next character is different
+ if (i == text.length() - 1 || text.charAt(i) != text.charAt(i + 1)) {
+ compressed.append(count);
+ compressed.append(text.charAt(i));
+ count = 1; // Reset count for the new character
+ } else {
+ count++;
+ }
+ }
+ return compressed.toString();
+ }
+
+ /**
+ * Decompresses a string that was compressed using the Run-Length Encoding algorithm.
+ *
+ * @param compressedText The compressed string. Must not be null.
+ * @return The original, uncompressed string.
+ */
+ public static String decompress(String compressedText) {
+ if (compressedText == null || compressedText.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder decompressed = new StringBuilder();
+ int count = 0;
+
+ for (char ch : compressedText.toCharArray()) {
+ if (Character.isDigit(ch)) {
+ // Build the number for runs of 10 or more (e.g., "12A")
+ count = count * 10 + ch - '0';
+ } else {
+ // Append the character 'count' times
+ decompressed.append(String.valueOf(ch).repeat(Math.max(0, count)));
+ count = 0; // Reset count for the next sequence
+ }
+ }
+ return decompressed.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/compression/ShannonFano.java b/src/main/java/com/thealgorithms/compression/ShannonFano.java
new file mode 100644
index 000000000000..aa5d7ad91b2f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/compression/ShannonFano.java
@@ -0,0 +1,159 @@
+package com.thealgorithms.compression;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * An implementation of the Shannon-Fano algorithm for generating prefix codes.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * DampedOscillator oscillator = new DampedOscillator(10.0, 0.5);
+ * double displacement = oscillator.displacementAnalytical(1.0, 0.0, 0.1);
+ * double[] nextState = oscillator.stepEuler(new double[]{1.0, 0.0}, 0.001);
+ * }
+ *
+ * @author [Yash Rajput](https://github.com/the-yash-rajput)
+ */
+public final class DampedOscillator {
+
+ /** Natural (undamped) angular frequency (rad/s). */
+ private final double omega0;
+
+ /** Damping coefficient (s⁻¹). */
+ private final double gamma;
+
+ private DampedOscillator() {
+ throw new AssertionError("No instances.");
+ }
+
+ /**
+ * Constructs a damped oscillator model.
+ *
+ * @param omega0 the natural frequency (rad/s), must be positive
+ * @param gamma the damping coefficient (s⁻¹), must be non-negative
+ * @throws IllegalArgumentException if parameters are invalid
+ */
+ public DampedOscillator(double omega0, double gamma) {
+ if (omega0 <= 0) {
+ throw new IllegalArgumentException("Natural frequency must be positive.");
+ }
+ if (gamma < 0) {
+ throw new IllegalArgumentException("Damping coefficient must be non-negative.");
+ }
+ this.omega0 = omega0;
+ this.gamma = gamma;
+ }
+
+ /**
+ * Computes the analytical displacement of an underdamped oscillator.
+ * Formula: x(t) = A * exp(-γt) * cos(ω_d t + φ)
+ *
+ * @param amplitude the initial amplitude A
+ * @param phase the initial phase φ (radians)
+ * @param time the time t (seconds)
+ * @return the displacement x(t)
+ */
+ public double displacementAnalytical(double amplitude, double phase, double time) {
+ double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
+ return amplitude * Math.exp(-gamma * time) * Math.cos(omegaD * time + phase);
+ }
+
+ /**
+ * Performs a single integration step using the explicit Euler method.
+ * State vector format: [x, v], where v = dx/dt.
+ *
+ * @param state the current state [x, v]
+ * @param dt the time step (seconds)
+ * @return the next state [x_next, v_next]
+ * @throws IllegalArgumentException if the state array is invalid or dt is non-positive
+ */
+ public double[] stepEuler(double[] state, double dt) {
+ if (state == null || state.length != 2) {
+ throw new IllegalArgumentException("State must be a non-null array of length 2.");
+ }
+ if (dt <= 0) {
+ throw new IllegalArgumentException("Time step must be positive.");
+ }
+
+ double x = state[0];
+ double v = state[1];
+ double acceleration = -2.0 * gamma * v - omega0 * omega0 * x;
+
+ double xNext = x + dt * v;
+ double vNext = v + dt * acceleration;
+
+ return new double[] {xNext, vNext};
+ }
+
+ /** @return the natural (undamped) angular frequency (rad/s). */
+ public double getOmega0() {
+ return omega0;
+ }
+
+ /** @return the damping coefficient (s⁻¹). */
+ public double getGamma() {
+ return gamma;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java
new file mode 100644
index 000000000000..4b3e9fafe063
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java
@@ -0,0 +1,143 @@
+package com.thealgorithms.physics;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link DampedOscillator}.
+ *
+ *
+ *
+ */
+@DisplayName("DampedOscillator — unit tests")
+public class DampedOscillatorTest {
+
+ private static final double TOLERANCE = 1e-3;
+
+ @Test
+ @DisplayName("Constructor rejects invalid parameters")
+ void constructorValidation() {
+ assertAll("invalid-constructor-params",
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(0.0, 0.1), "omega0 == 0 should throw"),
+ () -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(-1.0, 0.1), "negative omega0 should throw"), () -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(1.0, -0.1), "negative gamma should throw"));
+ }
+
+ @Test
+ @DisplayName("Analytical displacement matches expected formula for underdamped case")
+ void analyticalUnderdamped() {
+ double omega0 = 10.0;
+ double gamma = 0.5;
+ DampedOscillator d = new DampedOscillator(omega0, gamma);
+
+ double a = 1.0;
+ double phi = 0.2;
+ double t = 0.123;
+
+ // expected: a * exp(-gamma * t) * cos(omega_d * t + phi)
+ double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
+ double expected = a * Math.exp(-gamma * t) * Math.cos(omegaD * t + phi);
+
+ double actual = d.displacementAnalytical(a, phi, t);
+ assertEquals(expected, actual, 1e-12, "Analytical underdamped displacement should match closed-form value");
+ }
+
+ @Test
+ @DisplayName("Analytical displacement gracefully handles overdamped parameters (omegaD -> 0)")
+ void analyticalOverdamped() {
+ double omega0 = 1.0;
+ double gamma = 2.0; // gamma > omega0 => omega_d = 0 in our implementation (Math.max)
+ DampedOscillator d = new DampedOscillator(omega0, gamma);
+
+ double a = 2.0;
+ double phi = Math.PI / 4.0;
+ double t = 0.5;
+
+ // With omegaD forced to 0 by implementation, expected simplifies to:
+ double expected = a * Math.exp(-gamma * t) * Math.cos(phi);
+ double actual = d.displacementAnalytical(a, phi, t);
+
+ assertEquals(expected, actual, 1e-12, "Overdamped handling should reduce to exponential * cos(phase)");
+ }
+
+ @Test
+ @DisplayName("Explicit Euler step approximates analytical solution for small dt over short time")
+ void eulerApproximatesAnalyticalSmallDt() {
+ double omega0 = 10.0;
+ double gamma = 0.5;
+ DampedOscillator d = new DampedOscillator(omega0, gamma);
+
+ double a = 1.0;
+ double phi = 0.0;
+
+ // initial conditions consistent with amplitude a and zero phase:
+ // x(0) = a, v(0) = -a * gamma * cos(phi) + a * omegaD * sin(phi)
+ double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
+ double x0 = a * Math.cos(phi);
+ double v0 = -a * gamma * Math.cos(phi) - a * omegaD * Math.sin(phi); // small general form
+
+ double dt = 1e-4;
+ int steps = 1000; // simulate to t = 0.1s
+ double tFinal = steps * dt;
+
+ double[] state = new double[] {x0, v0};
+ for (int i = 0; i < steps; i++) {
+ state = d.stepEuler(state, dt);
+ }
+
+ double analyticAtT = d.displacementAnalytical(a, phi, tFinal);
+ double numericAtT = state[0];
+
+ // Euler is low-order — allow a small tolerance but assert it remains close for small dt + short time.
+ assertEquals(analyticAtT, numericAtT, TOLERANCE, String.format("Numeric Euler should approximate analytical solution at t=%.6f (tolerance=%g)", tFinal, TOLERANCE));
+ }
+
+ @Test
+ @DisplayName("stepEuler validates inputs and throws on null/invalid dt/state")
+ void eulerInputValidation() {
+ DampedOscillator d = new DampedOscillator(5.0, 0.1);
+
+ assertAll("invalid-stepEuler-args",
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(null, 0.01), "null state should throw"),
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0}, 0.01), "state array with invalid length should throw"),
+ () -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, 0.0), "non-positive dt should throw"), () -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, -1e-3), "negative dt should throw"));
+ }
+
+ @Test
+ @DisplayName("Getter methods return configured parameters")
+ void gettersReturnConfiguration() {
+ double omega0 = Math.PI;
+ double gamma = 0.01;
+ DampedOscillator d = new DampedOscillator(omega0, gamma);
+
+ assertAll("getters", () -> assertEquals(omega0, d.getOmega0(), 0.0, "getOmega0 should return configured omega0"), () -> assertEquals(gamma, d.getGamma(), 0.0, "getGamma should return configured gamma"));
+ }
+
+ @Test
+ @DisplayName("Analytical displacement at t=0 returns initial amplitude * cos(phase)")
+ void analyticalAtZeroTime() {
+ double omega0 = 5.0;
+ double gamma = 0.2;
+ DampedOscillator d = new DampedOscillator(omega0, gamma);
+
+ double a = 2.0;
+ double phi = Math.PI / 3.0;
+ double t = 0.0;
+
+ double expected = a * Math.cos(phi);
+ double actual = d.displacementAnalytical(a, phi, t);
+
+ assertEquals(expected, actual, 1e-12, "Displacement at t=0 should be a * cos(phase)");
+ }
+}
From 4858ec9af08ef4092e06a0e0e9d6aa3a87b84102 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Fri, 17 Oct 2025 13:23:39 +0530
Subject: [PATCH 088/203] =?UTF-8?q?refactor:=20Enhance=20docs,=20code,=20a?=
=?UTF-8?q?dd=20tests=20in=20`MaximumSumOfDistinctSubar=E2=80=A6=20(#6649)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: Enhance docs, code, add tests in `MaximumSumOfDistinctSubarraysWithLengthK`
* Fix
* Fix spotbug
* Fix
* Fix
* Fix
* Fix
---
...imumSumOfDistinctSubarraysWithLengthK.java | 98 +++++++-----
...SumOfDistinctSubarraysWithLengthKTest.java | 145 +++++++++++++++++-
2 files changed, 200 insertions(+), 43 deletions(-)
diff --git a/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java b/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java
index c05f1af4e327..dec813dd3213 100644
--- a/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java
+++ b/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java
@@ -1,14 +1,26 @@
package com.thealgorithms.others;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.Map;
/**
- * References: https://en.wikipedia.org/wiki/Streaming_algorithm
+ * Algorithm to find the maximum sum of a subarray of size K with all distinct
+ * elements.
*
- * This model involves computing the maximum sum of subarrays of a fixed size \( K \) from a stream of integers.
- * As the stream progresses, elements from the end of the window are removed, and new elements from the stream are added.
+ * This implementation uses a sliding window approach with a hash map to
+ * efficiently
+ * track element frequencies within the current window. The algorithm maintains
+ * a window
+ * of size K and slides it across the array, ensuring all elements in the window
+ * are distinct.
*
+ * Time Complexity: O(n) where n is the length of the input array
+ * Space Complexity: O(k) for storing elements in the hash map
+ *
+ * @see Streaming
+ * Algorithm
+ * @see Sliding
+ * Window
* @author Swarga-codes (https://github.com/Swarga-codes)
*/
public final class MaximumSumOfDistinctSubarraysWithLengthK {
@@ -16,54 +28,62 @@ private MaximumSumOfDistinctSubarraysWithLengthK() {
}
/**
- * Finds the maximum sum of a subarray of size K consisting of distinct elements.
+ * Finds the maximum sum of a subarray of size K consisting of distinct
+ * elements.
*
- * @param k The size of the subarray.
- * @param nums The array from which subarrays will be considered.
+ * The algorithm uses a sliding window technique with a frequency map to track
+ * the count of each element in the current window. A window is valid only if
+ * all K elements are distinct (frequency map size equals K).
*
- * @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0.
+ * @param k The size of the subarray. Must be non-negative.
+ * @param nums The array from which subarrays will be considered.
+ * @return The maximum sum of any distinct-element subarray of size K.
+ * Returns 0 if no such subarray exists or if k is 0 or negative.
+ * @throws IllegalArgumentException if k is negative
*/
public static long maximumSubarraySum(int k, int... nums) {
- if (nums.length < k) {
+ if (k <= 0 || nums == null || nums.length < k) {
return 0;
}
- long masSum = 0; // Variable to store the maximum sum of distinct subarrays
- long currentSum = 0; // Variable to store the sum of the current subarray
- Set
+ * 1. Compute indegree and outdegree for all vertices.
+ * 2. Check if the graph satisfies Eulerian path or circuit conditions.
+ * 3. Verify that all vertices with non-zero degree are weakly connected (undirected connectivity).
+ * 4. Use Hierholzer’s algorithm to build the path by exploring unused edges iteratively.
+ *
+ * Space Complexity: O(V + E).
+ * > adjacencyList;
+
+ /**
+ * Constructs a graph with a given number of vertices.
+ *
+ * @param numNodes number of vertices
+ */
+ public Graph(int numNodes) {
+ adjacencyList = new ArrayList<>();
+ for (int i = 0; i < numNodes; i++) {
+ adjacencyList.add(new ArrayList<>());
+ }
+ }
+
+ /**
+ * Adds a directed edge from vertex {@code from} to vertex {@code to}.
+ *
+ * @param from source vertex
+ * @param to destination vertex
+ */
+ public void addEdge(int from, int to) {
+ adjacencyList.get(from).add(to);
+ }
+
+ /**
+ * Returns a list of outgoing edges from the given vertex.
+ *
+ * @param node vertex index
+ * @return list of destination vertices
+ */
+ public List
+ *
+ *
+ *
+ *
+ *
+ *
+ * Time Complexity: O(n) for both compression and decompression, where n is the + * length of the input string. + *
+ * + *+ * References: + *
- * Bloom filters are space-efficient data structures that provide a fast way to test whether an - * element is a member of a set. They may produce false positives, indicating an element is + * Bloom filters are space-efficient data structures that provide a fast way to + * test whether an + * element is a member of a set. They may produce false positives, indicating an + * element is * in the set when it is not, but they will never produce false negatives. *
* @@ -21,11 +23,14 @@ public class BloomFilter- * This method hashes the element using all defined hash functions and sets the corresponding + * This method hashes the element using all defined hash functions and sets the + * corresponding * bits in the bit array. *
* @@ -66,13 +73,16 @@ public void insert(T key) { /** * Checks if an element might be in the Bloom filter. *- * This method checks the bits at the positions computed by each hash function. If any of these - * bits are not set, the element is definitely not in the filter. If all bits are set, the element + * This method checks the bits at the positions computed by each hash function. + * If any of these + * bits are not set, the element is definitely not in the filter. If all bits + * are set, the element * might be in the filter. *
* * @param key the element to check for membership in the Bloom filter - * @return {@code true} if the element might be in the Bloom filter, {@code false} if it is definitely not + * @return {@code true} if the element might be in the Bloom filter, + * {@code false} if it is definitely not */ public boolean contains(T key) { for (Hash- * Each instance of this class represents a different hash function based on its index. + * Each instance of this class represents a different hash function based on its + * index. *
* * @param+ * Given a 2D array (matrix), this class provides a method to return the + * elements + * of the matrix in spiral order, starting from the top-left corner and moving + * clockwise. + *
+ * + * @author Sadiul Hakim (https://github.com/sadiul-hakim) + */ public class PrintAMatrixInSpiralOrder { + /** - * Search a key in row and column wise sorted matrix + * Returns the elements of the given matrix in spiral order. + * + * @param matrix the 2D array to traverse in spiral order + * @param row the number of rows in the matrix + * @param col the number of columns in the matrix + * @return a list containing the elements of the matrix in spiral order * - * @param matrix matrix to be searched - * @param row number of rows matrix has - * @param col number of columns matrix has - * @author Sadiul Hakim : https://github.com/sadiul-hakim + *+ * Example: + * + *
+ * int[][] matrix = {
+ * {1, 2, 3},
+ * {4, 5, 6},
+ * {7, 8, 9}
+ * };
+ * print(matrix, 3, 3) returns [1, 2, 3, 6, 9, 8, 7, 4, 5]
+ *
+ *
*/
public List+ * LZ77 is a lossless data compression algorithm that works by finding repeated + * occurrences of data in a sliding window. It replaces subsequent occurrences + * with references (offset, length) to the first occurrence within the window. + *
+ *+ * This implementation uses a simple sliding window and lookahead buffer approach. + * Output format is a sequence of tuples (offset, length, next_character). + *
+ *+ * Time Complexity: O(n*W) in this naive implementation, where n is the input length + * and W is the window size, due to the search for the longest match. More advanced + * data structures (like suffix trees) can improve this. + *
+ *+ * References: + *
+ * LZ78 is a dictionary-based lossless data compression algorithm. It processes + * input data sequentially, building a dictionary of phrases encountered so far. + * It outputs pairs (dictionary_index, next_character), representing + * the longest match found in the dictionary plus the character that follows it. + *
+ *+ * This implementation builds the dictionary dynamically during compression. + * The dictionary index 0 represents the empty string (no prefix). + *
+ *+ * Time Complexity: O(n) on average for compression and decompression, assuming + * efficient dictionary lookups (using a HashMap), where n is the + * length of the input string. + *
+ *+ * References: + *
+ * BWT is a reversible data transformation algorithm that rearranges a string into runs of + * similar characters. While not a compression algorithm itself, it significantly improves + * the compressibility of data for subsequent algorithms like Move-to-Front encoding and + * Run-Length Encoding. + *
+ * + *The transform works by: + *
Important: The input string should end with a unique end-of-string marker + * (typically '$') that: + *
Time Complexity: + *
Example:
+ *
+ * Input: "banana$"
+ * Output: BWTResult("annb$aa", 4)
+ * - "annb$aa" is the transformed string (groups similar characters)
+ * - 4 is the index of the original string in the sorted rotations
+ *
+ *
+ * @see Burrows–Wheeler transform (Wikipedia)
+ */
+public final class BurrowsWheelerTransform {
+
+ private BurrowsWheelerTransform() {
+ }
+
+ /**
+ * A container for the result of the forward BWT.
+ * + * Contains the transformed string and the index of the original string + * in the sorted rotations matrix, both of which are required for the + * inverse transformation. + *
+ */ + public static class BWTResult { + /** The transformed string (last column of the sorted rotation matrix) */ + public final String transformed; + + /** The index of the original string in the sorted rotations matrix */ + public final int originalIndex; + + /** + * Constructs a BWTResult with the transformed string and original index. + * + * @param transformed the transformed string (L-column) + * @param originalIndex the index of the original string in sorted rotations + */ + public BWTResult(String transformed, int originalIndex) { + this.transformed = transformed; + this.originalIndex = originalIndex; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BWTResult bwtResult = (BWTResult) obj; + return originalIndex == bwtResult.originalIndex && transformed.equals(bwtResult.transformed); + } + + @Override + public int hashCode() { + return 31 * transformed.hashCode() + originalIndex; + } + + @Override + public String toString() { + return "BWTResult[transformed=" + transformed + ", originalIndex=" + originalIndex + "]"; + } + } + + /** + * Performs the forward Burrows-Wheeler Transform on the input string. + *+ * The algorithm generates all cyclic rotations of the input, sorts them + * lexicographically, and returns the last column of this sorted matrix + * along with the position of the original string. + *
+ * + *Note: It is strongly recommended that the input string ends with + * a unique end-of-string marker (e.g., '$') that is lexicographically smaller + * than any other character in the string. This ensures correct inversion.
+ * + * @param text the input string to transform; must not be {@code null} + * @return a {@link BWTResult} object containing the transformed string (L-column) + * and the index of the original string in the sorted rotations matrix; + * returns {@code BWTResult("", -1)} for empty input + * @throws NullPointerException if {@code text} is {@code null} + */ + public static BWTResult transform(String text) { + if (text == null || text.isEmpty()) { + return new BWTResult("", -1); + } + + int n = text.length(); + + // Generate all rotations of the input string + String[] rotations = new String[n]; + for (int i = 0; i < n; i++) { + rotations[i] = text.substring(i) + text.substring(0, i); + } + + // Sort rotations lexicographically + Arrays.sort(rotations); + int originalIndex = Arrays.binarySearch(rotations, text); + StringBuilder lastColumn = new StringBuilder(n); + for (int i = 0; i < n; i++) { + lastColumn.append(rotations[i].charAt(n - 1)); + } + + return new BWTResult(lastColumn.toString(), originalIndex); + } + + /** + * Performs the inverse Burrows-Wheeler Transform using the LF-mapping technique. + *+ * The LF-mapping (Last-First mapping) is an efficient method to reconstruct + * the original string from the BWT output without explicitly reconstructing + * the entire sorted rotations matrix. + *
+ * + *The algorithm works by: + *
+ * MTF is a data transformation algorithm that encodes each symbol in the input + * as its current position in a dynamically-maintained list, then moves that symbol + * to the front of the list. This transformation is particularly effective when used + * after the Burrows-Wheeler Transform (BWT), as BWT groups similar characters together. + *
+ * + *The transform converts runs of repeated characters into sequences of small integers + * (often zeros), which are highly compressible by subsequent entropy encoding algorithms + * like Run-Length Encoding (RLE) or Huffman coding. This technique is used in the + * bzip2 compression algorithm. + *
+ * + *How it works: + *
Time Complexity: + *
Example:
+ *+ * Input: "annb$aa" + * Alphabet: "$abn" (initial order) + * Output: [1, 3, 0, 3, 3, 3, 0] + * + * Step-by-step: + * - 'a': index 1 in [$,a,b,n] → output 1, list becomes [a,$,b,n] + * - 'n': index 3 in [a,$,b,n] → output 3, list becomes [n,a,$,b] + * - 'n': index 0 in [n,a,$,b] → output 0, list stays [n,a,$,b] + * - 'b': index 3 in [n,a,$,b] → output 3, list becomes [b,n,a,$] + * - etc. + * + * Notice how repeated 'n' characters produce zeros after the first occurrence! + *+ * + * @see Move-to-front transform (Wikipedia) + */ +public final class MoveToFront { + + private MoveToFront() { + } + + /** + * Performs the forward Move-to-Front transform. + *
+ * Converts the input string into a list of integers, where each integer represents + * the position of the corresponding character in a dynamically-maintained alphabet list. + *
+ * + *Note: All characters in the input text must exist in the provided alphabet, + * otherwise an {@link IllegalArgumentException} is thrown. The alphabet should contain + * all unique characters that may appear in the input.
+ * + * @param text the input string to transform; if empty, returns an empty list + * @param initialAlphabet a string containing the initial ordered set of symbols + * (e.g., "$abn" or the full ASCII set); must not be empty + * when {@code text} is non-empty + * @return a list of integers representing the transformed data, where each integer + * is the index of the corresponding input character in the current alphabet state + * @throws IllegalArgumentException if {@code text} is non-empty and {@code initialAlphabet} + * is {@code null} or empty + * @throws IllegalArgumentException if any character in {@code text} is not found in + * {@code initialAlphabet} + */ + public static List+ * Reconstructs the original string from the list of indices produced by the + * forward transform. This requires the exact same initial alphabet that was + * used in the forward transform. + *
+ * + *Important: The {@code initialAlphabet} parameter must be identical + * to the one used in the forward transform, including character order, or the + * output will be incorrect.
+ * + * @param indices The list of integers from the forward transform. + * @param initialAlphabet the exact same initial alphabet string used for the forward transform; + * if {@code null} or empty, returns an empty string + * @return the original, untransformed string + * @throws IllegalArgumentException if any index in {@code indices} is negative or + * exceeds the current alphabet size + */ + public static String inverseTransform(CollectionUses a sweep-line approach with an event queue and status structure to + * efficiently detect intersections in 2D plane geometry.
+ * + * @see + * Bentley–Ottmann algorithm + */ +public final class BentleyOttmann { + + private BentleyOttmann() { + } + + private static final double EPS = 1e-9; + private static double currentSweepX; + + /** + * Represents a line segment with two endpoints. + */ + public static class Segment { + final Point2D.Double p1; + final Point2D.Double p2; + final int id; // Unique identifier for each segment + + Segment(Point2D.Double p1, Point2D.Double p2) { + this.p1 = p1; + this.p2 = p2; + this.id = segmentCounter++; + } + + private static int segmentCounter = 0; + + /** + * Computes the y-coordinate of this segment at a given x value. + */ + double getY(double x) { + if (Math.abs(p2.x - p1.x) < EPS) { + // Vertical segment: return midpoint y + return (p1.y + p2.y) / 2.0; + } + double t = (x - p1.x) / (p2.x - p1.x); + return p1.y + t * (p2.y - p1.y); + } + + Point2D.Double leftPoint() { + return p1.x < p2.x ? p1 : p1.x > p2.x ? p2 : p1.y < p2.y ? p1 : p2; + } + + Point2D.Double rightPoint() { + return p1.x > p2.x ? p1 : p1.x < p2.x ? p2 : p1.y > p2.y ? p1 : p2; + } + + @Override + public String toString() { + return String.format("S%d[(%.2f, %.2f), (%.2f, %.2f)]", id, p1.x, p1.y, p2.x, p2.y); + } + } + + /** + * Event types for the sweep line algorithm. + */ + private enum EventType { START, END, INTERSECTION } + + /** + * Represents an event in the event queue. + */ + private static class Event implements ComparableAn intersection point is reported when two or more segments cross or touch. + * For overlapping segments, only actual crossing/touching points are reported, + * not all points along the overlap.
+ * + * @param segments list of line segments represented as pairs of points + * @return a set of intersection points where segments meet or cross + * @throws IllegalArgumentException if the list is null or contains null points + */ + public static SetThis test suite validates the correctness of the Bentley–Ottmann algorithm + * implementation by checking intersection points between multiple line segment configurations.
+ * + *Test cases include typical, edge, degenerate geometrical setups, and performance tests.
+ */ +public class BentleyOttmannTest { + + private static final double EPS = 1e-6; + + @Test + void testSingleIntersection() { + List
+ * Wikipedia: https://en.wikipedia.org/wiki/Neville%27s_algorithm
+ *
+ * @author Mitrajit Ghorui(KeyKyrios)
+ */
+public final class Neville {
+
+ private Neville() {
+ }
+
+ /**
+ * Evaluates the polynomial that passes through the given points at a
+ * specific x-coordinate.
+ *
+ * @param x The x-coordinates of the points. Must be the same length as y.
+ * @param y The y-coordinates of the points. Must be the same length as x.
+ * @param target The x-coordinate at which to evaluate the polynomial.
+ * @return The interpolated y-value at the target x-coordinate.
+ * @throws IllegalArgumentException if the lengths of x and y arrays are
+ * different, if the arrays are empty, or if x-coordinates are not unique.
+ */
+ public static double interpolate(double[] x, double[] y, double target) {
+ if (x.length != y.length) {
+ throw new IllegalArgumentException("x and y arrays must have the same length.");
+ }
+ if (x.length == 0) {
+ throw new IllegalArgumentException("Input arrays cannot be empty.");
+ }
+
+ // Check for duplicate x-coordinates to prevent division by zero
+ Set
+ * This method is a "polynomial acceleration" method, meaning it finds the
+ * optimal polynomial to apply to the residual to accelerate convergence.
+ *
+ *
+ * It requires knowledge of the bounds of the eigenvalues of the matrix A:
+ * m(A) (smallest eigenvalue) and M(A) (largest eigenvalue).
+ *
+ *
+ * Wikipedia: https://en.wikipedia.org/wiki/Chebyshev_iteration
+ *
+ * @author Mitrajit Ghorui(KeyKyrios)
+ */
+public final class ChebyshevIteration {
+
+ private ChebyshevIteration() {
+ }
+
+ /**
+ * Solves the linear system Ax = b using the Chebyshev iteration method.
+ *
+ *
+ * NOTE: The matrix A *must* be symmetric positive-definite (SPD) for this
+ * algorithm to converge.
+ *
+ * @param a The matrix A (must be square, SPD).
+ * @param b The vector b.
+ * @param x0 The initial guess vector.
+ * @param minEigenvalue The smallest eigenvalue of A (m(A)).
+ * @param maxEigenvalue The largest eigenvalue of A (M(A)).
+ * @param maxIterations The maximum number of iterations to perform.
+ * @param tolerance The desired tolerance for the residual norm.
+ * @return The solution vector x.
+ * @throws IllegalArgumentException if matrix/vector dimensions are
+ * incompatible,
+ * if maxIterations <= 0, or if eigenvalues are invalid (e.g., minEigenvalue
+ * <= 0, maxEigenvalue <= minEigenvalue).
+ */
+ public static double[] solve(double[][] a, double[] b, double[] x0, double minEigenvalue, double maxEigenvalue, int maxIterations, double tolerance) {
+ validateInputs(a, b, x0, minEigenvalue, maxEigenvalue, maxIterations, tolerance);
+
+ int n = b.length;
+ double[] x = x0.clone();
+ double[] r = vectorSubtract(b, matrixVectorMultiply(a, x));
+ double[] p = new double[n];
+
+ double d = (maxEigenvalue + minEigenvalue) / 2.0;
+ double c = (maxEigenvalue - minEigenvalue) / 2.0;
+
+ double alpha = 0.0;
+ double alphaPrev = 0.0;
+
+ for (int k = 0; k < maxIterations; k++) {
+ double residualNorm = vectorNorm(r);
+ if (residualNorm < tolerance) {
+ return x; // Solution converged
+ }
+
+ if (k == 0) {
+ alpha = 1.0 / d;
+ System.arraycopy(r, 0, p, 0, n); // p = r
+ } else {
+ double beta = c * alphaPrev / 2.0 * (c * alphaPrev / 2.0);
+ alpha = 1.0 / (d - beta / alphaPrev);
+ double[] pUpdate = scalarMultiply(beta / alphaPrev, p);
+ p = vectorAdd(r, pUpdate); // p = r + (beta / alphaPrev) * p
+ }
+
+ double[] xUpdate = scalarMultiply(alpha, p);
+ x = vectorAdd(x, xUpdate); // x = x + alpha * p
+
+ // Recompute residual for accuracy
+ r = vectorSubtract(b, matrixVectorMultiply(a, x));
+ alphaPrev = alpha;
+ }
+
+ return x; // Return best guess after maxIterations
+ }
+
+ /**
+ * Validates the inputs for the Chebyshev solver.
+ */
+ private static void validateInputs(double[][] a, double[] b, double[] x0, double minEigenvalue, double maxEigenvalue, int maxIterations, double tolerance) {
+ int n = a.length;
+ if (n == 0) {
+ throw new IllegalArgumentException("Matrix A cannot be empty.");
+ }
+ if (n != a[0].length) {
+ throw new IllegalArgumentException("Matrix A must be square.");
+ }
+ if (n != b.length) {
+ throw new IllegalArgumentException("Matrix A and vector b dimensions do not match.");
+ }
+ if (n != x0.length) {
+ throw new IllegalArgumentException("Matrix A and vector x0 dimensions do not match.");
+ }
+ if (minEigenvalue <= 0) {
+ throw new IllegalArgumentException("Smallest eigenvalue must be positive (matrix must be positive-definite).");
+ }
+ if (maxEigenvalue <= minEigenvalue) {
+ throw new IllegalArgumentException("Max eigenvalue must be strictly greater than min eigenvalue.");
+ }
+ if (maxIterations <= 0) {
+ throw new IllegalArgumentException("Max iterations must be positive.");
+ }
+ if (tolerance <= 0) {
+ throw new IllegalArgumentException("Tolerance must be positive.");
+ }
+ }
+
+ // --- Vector/Matrix Helper Methods ---
+ /**
+ * Computes the product of a matrix A and a vector v (Av).
+ */
+ private static double[] matrixVectorMultiply(double[][] a, double[] v) {
+ int n = a.length;
+ double[] result = new double[n];
+ for (int i = 0; i < n; i++) {
+ double sum = 0;
+ for (int j = 0; j < n; j++) {
+ sum += a[i][j] * v[j];
+ }
+ result[i] = sum;
+ }
+ return result;
+ }
+
+ /**
+ * Computes the subtraction of two vectors (v1 - v2).
+ */
+ private static double[] vectorSubtract(double[] v1, double[] v2) {
+ int n = v1.length;
+ double[] result = new double[n];
+ for (int i = 0; i < n; i++) {
+ result[i] = v1[i] - v2[i];
+ }
+ return result;
+ }
+
+ /**
+ * Computes the addition of two vectors (v1 + v2).
+ */
+ private static double[] vectorAdd(double[] v1, double[] v2) {
+ int n = v1.length;
+ double[] result = new double[n];
+ for (int i = 0; i < n; i++) {
+ result[i] = v1[i] + v2[i];
+ }
+ return result;
+ }
+
+ /**
+ * Computes the product of a scalar and a vector (s * v).
+ */
+ private static double[] scalarMultiply(double scalar, double[] v) {
+ int n = v.length;
+ double[] result = new double[n];
+ for (int i = 0; i < n; i++) {
+ result[i] = scalar * v[i];
+ }
+ return result;
+ }
+
+ /**
+ * Computes the L2 norm (Euclidean norm) of a vector.
+ */
+ private static double vectorNorm(double[] v) {
+ double sumOfSquares = 0;
+ for (double val : v) {
+ sumOfSquares += val * val;
+ }
+ return Math.sqrt(sumOfSquares);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/ChebyshevIterationTest.java b/src/test/java/com/thealgorithms/maths/ChebyshevIterationTest.java
new file mode 100644
index 000000000000..d5cf83818fa4
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/ChebyshevIterationTest.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class ChebyshevIterationTest {
+
+ @Test
+ public void testSolveSimple2x2Diagonal() {
+ double[][] a = {{2, 0}, {0, 1}};
+ double[] b = {2, 2};
+ double[] x0 = {0, 0};
+ double minEig = 1.0;
+ double maxEig = 2.0;
+ int maxIter = 50;
+ double tol = 1e-9;
+ double[] expected = {1.0, 2.0};
+
+ double[] result = ChebyshevIteration.solve(a, b, x0, minEig, maxEig, maxIter, tol);
+ assertArrayEquals(expected, result, 1e-9);
+ }
+
+ @Test
+ public void testSolve2x2Symmetric() {
+ double[][] a = {{4, 1}, {1, 3}};
+ double[] b = {1, 2};
+ double[] x0 = {0, 0};
+ double minEig = (7.0 - Math.sqrt(5.0)) / 2.0;
+ double maxEig = (7.0 + Math.sqrt(5.0)) / 2.0;
+ int maxIter = 100;
+ double tol = 1e-10;
+ double[] expected = {1.0 / 11.0, 7.0 / 11.0};
+
+ double[] result = ChebyshevIteration.solve(a, b, x0, minEig, maxEig, maxIter, tol);
+ assertArrayEquals(expected, result, 1e-9);
+ }
+
+ @Test
+ public void testAlreadyAtSolution() {
+ double[][] a = {{2, 0}, {0, 1}};
+ double[] b = {2, 2};
+ double[] x0 = {1, 2};
+ double minEig = 1.0;
+ double maxEig = 2.0;
+ int maxIter = 10;
+ double tol = 1e-5;
+ double[] expected = {1.0, 2.0};
+
+ double[] result = ChebyshevIteration.solve(a, b, x0, minEig, maxEig, maxIter, tol);
+ assertArrayEquals(expected, result, 0.0);
+ }
+
+ @Test
+ public void testMismatchedDimensionsAB() {
+ double[][] a = {{1, 0}, {0, 1}};
+ double[] b = {1};
+ double[] x0 = {0, 0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 2, 10, 1e-5));
+ }
+
+ @Test
+ public void testMismatchedDimensionsAX() {
+ double[][] a = {{1, 0}, {0, 1}};
+ double[] b = {1, 1};
+ double[] x0 = {0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 2, 10, 1e-5));
+ }
+
+ @Test
+ public void testNonSquareMatrix() {
+ double[][] a = {{1, 0, 0}, {0, 1, 0}};
+ double[] b = {1, 1};
+ double[] x0 = {0, 0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 2, 10, 1e-5));
+ }
+
+ @Test
+ public void testInvalidEigenvalues() {
+ double[][] a = {{1, 0}, {0, 1}};
+ double[] b = {1, 1};
+ double[] x0 = {0, 0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 2, 1, 10, 1e-5));
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 1, 10, 1e-5));
+ }
+
+ @Test
+ public void testNonPositiveDefinite() {
+ double[][] a = {{1, 0}, {0, 1}};
+ double[] b = {1, 1};
+ double[] x0 = {0, 0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 0, 1, 10, 1e-5));
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, -1, 1, 10, 1e-5));
+ }
+
+ @Test
+ public void testInvalidIterationCount() {
+ double[][] a = {{1, 0}, {0, 1}};
+ double[] b = {1, 1};
+ double[] x0 = {0, 0};
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 2, 0, 1e-5));
+ assertThrows(IllegalArgumentException.class, () -> ChebyshevIteration.solve(a, b, x0, 1, 2, -1, 1e-5));
+ }
+}
From bb6385e756a0159a29655c745682e95ca7b41ada Mon Sep 17 00:00:00 2001
From: sairamsharan <143504336+sairamsharan@users.noreply.github.com>
Date: Wed, 29 Oct 2025 14:38:05 +0530
Subject: [PATCH 111/203] feat: Add Stoer-Wagner Algorithm for Minimum Cut
(#6752)
* feat: Add Stoer-Wagner Algorithm for Minimum Cut
* fix: Correct Stoer-Wagner implementation
* fix: Remove unused import
* fix: Apply clang-format
---
.../com/thealgorithms/graph/StoerWagner.java | 78 +++++++++++++++++++
.../thealgorithms/graph/StoerWagnerTest.java | 64 +++++++++++++++
2 files changed, 142 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/graph/StoerWagner.java
create mode 100644 src/test/java/com/thealgorithms/graph/StoerWagnerTest.java
diff --git a/src/main/java/com/thealgorithms/graph/StoerWagner.java b/src/main/java/com/thealgorithms/graph/StoerWagner.java
new file mode 100644
index 000000000000..b204834c431a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/StoerWagner.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.graph;
+
+/**
+ * An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph.
+ * A minimum cut is a partition of the graph's vertices into two disjoint sets with the minimum possible edge weight
+ * sum connecting the two sets.
+ *
+ * Wikipedia: https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm
+ * Time Complexity: O(V^3) where V is the number of vertices.
+ */
+public class StoerWagner {
+
+ /**
+ * Finds the minimum cut in the given undirected, weighted graph.
+ *
+ * @param graph An adjacency matrix representing the graph. graph[i][j] is the weight of the edge between i and j.
+ * @return The weight of the minimum cut.
+ */
+ public int findMinCut(int[][] graph) {
+ int n = graph.length;
+ if (n < 2) {
+ return 0;
+ }
+
+ int[][] currentGraph = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ System.arraycopy(graph[i], 0, currentGraph[i], 0, n);
+ }
+
+ int minCut = Integer.MAX_VALUE;
+ boolean[] merged = new boolean[n];
+
+ for (int phase = 0; phase < n - 1; phase++) {
+ boolean[] inSetA = new boolean[n];
+ int[] weights = new int[n];
+ int prev = -1;
+ int last = -1;
+
+ for (int i = 0; i < n - phase; i++) {
+ int maxWeight = -1;
+ int currentVertex = -1;
+
+ for (int j = 0; j < n; j++) {
+ if (!merged[j] && !inSetA[j] && weights[j] > maxWeight) {
+ maxWeight = weights[j];
+ currentVertex = j;
+ }
+ }
+
+ if (currentVertex == -1) {
+ // This can happen if the graph is disconnected.
+ return 0;
+ }
+
+ prev = last;
+ last = currentVertex;
+ inSetA[last] = true;
+
+ for (int j = 0; j < n; j++) {
+ if (!merged[j] && !inSetA[j]) {
+ weights[j] += currentGraph[last][j];
+ }
+ }
+ }
+
+ minCut = Math.min(minCut, weights[last]);
+
+ // Merge 'last' vertex into 'prev' vertex
+ for (int i = 0; i < n; i++) {
+ currentGraph[prev][i] += currentGraph[last][i];
+ currentGraph[i][prev] = currentGraph[prev][i];
+ }
+ merged[last] = true;
+ }
+
+ return minCut;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java
new file mode 100644
index 000000000000..894d99687d1d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the StoerWagner global minimum cut algorithm.
+ *
+ * These tests verify correctness of the implementation across
+ * several graph configurations: simple, complete, disconnected,
+ * and small edge cases.
+ */
+public class StoerWagnerTest {
+
+ @Test
+ public void testSimpleGraph() {
+ int[][] graph = {{0, 3, 2, 0}, {3, 0, 1, 4}, {2, 1, 0, 5}, {0, 4, 5, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(5, algo.findMinCut(graph)); // Correct minimum cut = 5
+ }
+
+ @Test
+ public void testTriangleGraph() {
+ int[][] graph = {{0, 2, 3}, {2, 0, 4}, {3, 4, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(5, algo.findMinCut(graph)); // min cut = 5
+ }
+
+ @Test
+ public void testDisconnectedGraph() {
+ int[][] graph = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(0, algo.findMinCut(graph)); // Disconnected graph => cut = 0
+ }
+
+ @Test
+ public void testCompleteGraph() {
+ int[][] graph = {{0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 0, 1}, {1, 1, 1, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(3, algo.findMinCut(graph)); // Each vertex connected to all others
+ }
+
+ @Test
+ public void testSingleVertex() {
+ int[][] graph = {{0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(0, algo.findMinCut(graph)); // Only one vertex
+ }
+
+ @Test
+ public void testTwoVertices() {
+ int[][] graph = {{0, 7}, {7, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(7, algo.findMinCut(graph)); // Only one edge, cut weight = 7
+ }
+
+ @Test
+ public void testSquareGraphWithDiagonal() {
+ int[][] graph = {{0, 2, 0, 2}, {2, 0, 3, 0}, {0, 3, 0, 4}, {2, 0, 4, 0}};
+ StoerWagner algo = new StoerWagner();
+ assertEquals(4, algo.findMinCut(graph)); // verified manually
+ }
+}
From dfd8d6993fe85b9913eb314c031c85fddb816ae5 Mon Sep 17 00:00:00 2001
From: JonathanButterworth
<149101933+JonathanButterworth@users.noreply.github.com>
Date: Thu, 30 Oct 2025 17:26:22 -0400
Subject: [PATCH 112/203] Added surface area calculation for pyramid (#6853)
Co-authored-by: JonathanButterworth
+ * An Eulerian circuit is a trail in a graph that visits every edge exactly once,
+ * starting and ending at the same vertex. This algorithm finds such a circuit if one exists.
+ *
+ * This implementation is designed for an undirected graph. For a valid Eulerian
+ * circuit to exist, the graph must satisfy two conditions:
+ *
+ * The algorithm runs in O(E + V) time, where E is the number of edges and V is the number of vertices.
+ * The graph is represented by a Map where keys are vertices and values are a LinkedList of adjacent vertices.
+ * This class provides methods for both left and right circular rotations,
+ * supporting only 32-bit integer operations with proper shift normalization
+ * and error handling.
- * Additional contibutions made by: PuneetTri(https://github.com/PuneetTri)
+ * Additional contributions made by: PuneetTri(https://github.com/PuneetTri)
*/
class PriorityQueue {
@@ -32,8 +32,8 @@ class PriorityQueue {
PriorityQueue() {
/* If capacity is not defined, default size of 11 would be used
- * capacity=max+1 because we cant access 0th element of PQ, and to
- * accomodate (max)th elements we need capacity to be max+1.
+ * capacity=max+1 because we can't access 0th element of PQ, and to
+ * accommodate (max)th elements we need capacity to be max+1.
* Parent is at position k, child at position (k*2,k*2+1), if we
* use position 0 in our queue, its child would be at:
* (0*2, 0*2+1) -> (0,0). This is why we start at position 1
@@ -127,7 +127,7 @@ public int remove() {
if (isEmpty()) {
throw new RuntimeException("Queue is Empty");
} else {
- int max = queueArray[1]; // By defintion of our max-heap, value at queueArray[1] pos is
+ int max = queueArray[1]; // By definition of our max-heap, value at queueArray[1] pos is
// the greatest
// Swap max and last element
From 19f0f0bd83fb350004f375df180d3f65089013d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 17:40:08 +0700
Subject: [PATCH 132/203] chore: fix typos in
src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
(#7024)
Fix typos in src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
---
.../com/thealgorithms/conversions/TurkishToLatinConversion.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
index 30030de6c1bd..50726380621a 100644
--- a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
+++ b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
@@ -16,7 +16,7 @@ private TurkishToLatinConversion() {
* 2. Replace all turkish characters with their corresponding latin characters
* 3. Return the converted string
*
- * @param param String paramter
+ * @param param String parameter
* @return String
*/
public static String convertTurkishToLatin(String param) {
From dd01b35d973a7021b3d8a6a56cf9b21f61c49016 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 17:44:45 +0700
Subject: [PATCH 133/203] chore: fix typos in
src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java
(#7025)
Fix typos in src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java
Co-authored-by: a <19151554+alxkm@users.noreply.github.com>
---
.../com/thealgorithms/datastructures/graphs/BellmanFord.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java b/src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java
index 47c5f0d0b98e..5184dae58d28 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java
@@ -160,7 +160,7 @@ public void show(int source, int end,
break;
}
}
- if (neg == 0) { // Go ahead and show results of computaion
+ if (neg == 0) { // Go ahead and show results of computation
System.out.println("Distance is: " + dist[end]);
System.out.println("Path followed:");
System.out.print(source + " ");
From fab09e7da17325a99ef9033d585f978e26867435 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 17:48:33 +0700
Subject: [PATCH 134/203] chore: fix typos in
src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
(#7026)
Fix typos in src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
Co-authored-by: a <19151554+alxkm@users.noreply.github.com>
---
.../com/thealgorithms/datastructures/graphs/MatrixGraphs.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
index c1d47df457da..a54b0b75e4dc 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
@@ -141,7 +141,7 @@ private int[][] adjacency() {
*
* @param from the parent vertex to check for adjacency
* @param to the child vertex to check for adjacency
- * @return whether or not the vertices are adjancent
+ * @return whether or not the vertices are adjacent
*/
private boolean adjacencyOfEdgeDoesExist(int from, int to) {
return (this.adjacency()[from][to] != AdjacencyMatrixGraph.EDGE_NONE);
@@ -162,7 +162,7 @@ public boolean vertexDoesExist(int aVertex) {
*
* @param from the parent vertex to check for adjacency
* @param to the child vertex to check for adjacency
- * @return whether or not the vertices are adjancent
+ * @return whether or not the vertices are adjacent
*/
public boolean edgeDoesExist(int from, int to) {
if (this.vertexDoesExist(from) && this.vertexDoesExist(to)) {
From 8ae57476b1298601c37da6742bb85902f2ac9b95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 17:53:11 +0700
Subject: [PATCH 135/203] chore: fix typos in
src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java (#7033)
Fix typos in src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java
---
src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java b/src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java
index 13c9212306c1..1639bec6e5a0 100644
--- a/src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java
+++ b/src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java
@@ -12,7 +12,7 @@
public abstract class TreeNode
*
@@ -29,7 +29,7 @@
* into an array.Since array is sorted do a binary search over the array to get
* an element equal to or greater than current key. Time Complexity: O(n) for
* traversal of tree and O(lg(n)) for binary search in array. Total = O(n) Space
- * Complexity: O(n) for auxillary array to save inorder representation of tree.
+ * Complexity: O(n) for auxiliary array to save inorder representation of tree.
*
*
* Solution 3: Optimal We can do a DFS search on given tree in following
From d28fee96650f9a295708d813b2c135081bbfc6b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 22:53:18 +0700
Subject: [PATCH 145/203] chore: fix typos in
src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java (#7049)
Fix typos in src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
---
.../dynamicprogramming/BoundaryFill.java | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
index 8494492f293f..ccd54ee4349a 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
@@ -12,8 +12,8 @@ private BoundaryFill() {
* Get the color at the given co-odrinates of a 2D image
*
* @param image The image to be filled
- * @param xCoordinate The x co-ordinate of which color is to be obtained
- * @param yCoordinate The y co-ordinate of which color is to be obtained
+ * @param xCoordinate The x coordinate of which color is to be obtained
+ * @param yCoordinate The y coordinate of which color is to be obtained
*/
public static int getPixel(int[][] image, int xCoordinate, int yCoordinate) {
return image[xCoordinate][yCoordinate];
@@ -23,8 +23,8 @@ public static int getPixel(int[][] image, int xCoordinate, int yCoordinate) {
* Put the color at the given co-odrinates of a 2D image
*
* @param image The image to be filed
- * @param xCoordinate The x co-ordinate at which color is to be filled
- * @param yCoordinate The y co-ordinate at which color is to be filled
+ * @param xCoordinate The x coordinate at which color is to be filled
+ * @param yCoordinate The y coordinate at which color is to be filled
*/
public static void putPixel(int[][] image, int xCoordinate, int yCoordinate, int newColor) {
image[xCoordinate][yCoordinate] = newColor;
@@ -34,8 +34,8 @@ public static void putPixel(int[][] image, int xCoordinate, int yCoordinate, int
* Fill the 2D image with new color
*
* @param image The image to be filed
- * @param xCoordinate The x co-ordinate at which color is to be filled
- * @param yCoordinate The y co-ordinate at which color is to be filled
+ * @param xCoordinate The x coordinate at which color is to be filled
+ * @param yCoordinate The y coordinate at which color is to be filled
* @param newColor The new color which to be filled in the image
* @param boundaryColor The old color which is to be replaced in the image
*/
From d126fd59f7c4f9aac8de23db2766bc6f6121394d Mon Sep 17 00:00:00 2001
From: Arzoo1701 <122432635+Arzoo1701@users.noreply.github.com>
Date: Wed, 5 Nov 2025 23:11:08 +0530
Subject: [PATCH 146/203] Add Trapping Rainwater problem implementation (Two
Pointer Approach) (#6990)
* Add Trapping Rainwater problem implementation (Two Pointer Approach)
* Add Wikipedia reference link for Trapping Rainwater problem
* fix: format TrappingRainwater.java for CI check
* fix: add package and resolve checkstyle errors for TrappingRainwater.java
* fix: declare TrappingRainwater as final to pass Checkstyle
* Add test cases for TrappingRainwater algorithm
* Add test cases for TrappingRainwater algorithm
* Move TrappingRainwater algorithm to stacks package
* Fix: Move TrappingRainwater to stacks and normalize newline in JugglerSequence
* Fix: Normalize newline in JugglerSequence to ensure test consistency
* Fix: Normalize newline in JugglerSequence and remove return statement
* Add JaCoCo plugin for code coverage reporting
* Revert pom.xml to original version from master
* Fix: Revert the pom.xml file
---------
Co-authored-by: Deniz Altunkapan This class supports conversions between the following units:
+ * This class is final and cannot be instantiated.
+ *
+ * @author krishna-medapati (https://github.com/krishna-medapati)
+ * @see Wikipedia: Temperature Conversion
+ */
+public final class TemperatureConverter {
+
+ private TemperatureConverter() {
+ }
+
+ public static double celsiusToFahrenheit(double celsius) {
+ return celsius * 9.0 / 5.0 + 32.0;
+ }
+
+ public static double celsiusToKelvin(double celsius) {
+ return celsius + 273.15;
+ }
+
+ public static double fahrenheitToCelsius(double fahrenheit) {
+ return (fahrenheit - 32.0) * 5.0 / 9.0;
+ }
+
+ public static double fahrenheitToKelvin(double fahrenheit) {
+ return (fahrenheit - 32.0) * 5.0 / 9.0 + 273.15;
+ }
+
+ public static double kelvinToCelsius(double kelvin) {
+ return kelvin - 273.15;
+ }
+
+ public static double kelvinToFahrenheit(double kelvin) {
+ return (kelvin - 273.15) * 9.0 / 5.0 + 32.0;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java b/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java
new file mode 100644
index 000000000000..24d55b706f36
--- /dev/null
+++ b/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.conversions;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class TemperatureConverterTest {
+
+ private static final double DELTA = 0.01;
+
+ @Test
+ void testCelsiusToFahrenheit() {
+ assertEquals(32.0, TemperatureConverter.celsiusToFahrenheit(0.0), DELTA);
+ assertEquals(212.0, TemperatureConverter.celsiusToFahrenheit(100.0), DELTA);
+ assertEquals(-40.0, TemperatureConverter.celsiusToFahrenheit(-40.0), DELTA);
+ assertEquals(98.6, TemperatureConverter.celsiusToFahrenheit(37.0), DELTA);
+ }
+
+ @Test
+ void testCelsiusToKelvin() {
+ assertEquals(273.15, TemperatureConverter.celsiusToKelvin(0.0), DELTA);
+ assertEquals(373.15, TemperatureConverter.celsiusToKelvin(100.0), DELTA);
+ assertEquals(233.15, TemperatureConverter.celsiusToKelvin(-40.0), DELTA);
+ }
+
+ @Test
+ void testFahrenheitToCelsius() {
+ assertEquals(0.0, TemperatureConverter.fahrenheitToCelsius(32.0), DELTA);
+ assertEquals(100.0, TemperatureConverter.fahrenheitToCelsius(212.0), DELTA);
+ assertEquals(-40.0, TemperatureConverter.fahrenheitToCelsius(-40.0), DELTA);
+ assertEquals(37.0, TemperatureConverter.fahrenheitToCelsius(98.6), DELTA);
+ }
+
+ @Test
+ void testFahrenheitToKelvin() {
+ assertEquals(273.15, TemperatureConverter.fahrenheitToKelvin(32.0), DELTA);
+ assertEquals(373.15, TemperatureConverter.fahrenheitToKelvin(212.0), DELTA);
+ assertEquals(233.15, TemperatureConverter.fahrenheitToKelvin(-40.0), DELTA);
+ }
+
+ @Test
+ void testKelvinToCelsius() {
+ assertEquals(0.0, TemperatureConverter.kelvinToCelsius(273.15), DELTA);
+ assertEquals(100.0, TemperatureConverter.kelvinToCelsius(373.15), DELTA);
+ assertEquals(-273.15, TemperatureConverter.kelvinToCelsius(0.0), DELTA);
+ }
+
+ @Test
+ void testKelvinToFahrenheit() {
+ assertEquals(32.0, TemperatureConverter.kelvinToFahrenheit(273.15), DELTA);
+ assertEquals(212.0, TemperatureConverter.kelvinToFahrenheit(373.15), DELTA);
+ assertEquals(-40.0, TemperatureConverter.kelvinToFahrenheit(233.15), DELTA);
+ }
+}
From 98eecb9f16003e612ad8d84ed6f3c78ae10c7005 Mon Sep 17 00:00:00 2001
From: Taranjeet Singh Kalsi Key features:
+ * Complexities:
+ * {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
+ * {@code peek, isEmpty, size, contains} are O(1).
+ */
+public class IndexedPriorityQueue We use IdentityHashMap by default to:
+ * IMPORTANT: The mutator must not change {@code equals/hashCode} of {@code e}
+ * if you migrate this implementation to value-based indexing (HashMap).
+ *
+ * @throws IllegalArgumentException if {@code e} is not in the queue
+ */
+ public void changeKey(E e, Consumer Returns nothing; the standard {@code PriorityQueue} returns a displaced
+ * element in a rare case to help its iterator. We don't need that here, so
+ * we keep the API simple.
+ */
+ @SuppressWarnings("unchecked")
+ private void removeAt(int i) {
+ int n = --size; // last index after removal
+ E moved = (E) heap[n];
+ E removed = (E) heap[i];
+ heap[n] = null; // help GC
+ index.remove(removed); // drop mapping for removed element
+
+ if (i == n) {
+ return; // removed last element; done
+ }
+
+ heap[i] = moved;
+ index.put(moved, i);
+
+ // Try sift-up first (cheap if key decreased); if no movement, sift-down.
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
new file mode 100644
index 000000000000..8d8c4e1db6bd
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
@@ -0,0 +1,350 @@
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.Comparator;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link IndexedPriorityQueue}.
+ *
+ * Notes:
+ * - We mainly use a Node class with a mutable "prio" field to test changeKey/decreaseKey/increaseKey.
+ * - The queue is a min-heap, so smaller "prio" means higher priority.
+ * - By default the implementation uses IdentityHashMap so duplicate-equals objects are allowed.
+ */
+public class IndexedPriorityQueueTest {
+
+ // ------------------------
+ // Helpers
+ // ------------------------
+
+ /** Simple payload with mutable priority. */
+ static class Node {
+ final String id;
+ int prio; // lower is better (min-heap)
+
+ Node(String id, int prio) {
+ this.id = id;
+ this.prio = prio;
+ }
+
+ @Override
+ public String toString() {
+ return id + "(" + prio + ")";
+ }
+ }
+
+ /** Same as Node but overrides equals/hashCode to simulate "duplicate-equals" scenario. */
+ static class NodeWithEquals {
+ final String id;
+ int prio;
+
+ NodeWithEquals(String id, int prio) {
+ this.id = id;
+ this.prio = prio;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof NodeWithEquals)) {
+ return false;
+ }
+ NodeWithEquals other = (NodeWithEquals) o;
+ // Intentionally naive equality: equal if priority is equal
+ return this.prio == other.prio;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(prio);
+ }
+
+ @Override
+ public String toString() {
+ return id + "(" + prio + ")";
+ }
+ }
+
+ private static IndexedPriorityQueue
+ * Time Complexity: O(N log N) for construction
+ * Space Complexity: O(N)
+ *
+ *
+ * Applications:
+ * - Distance queries on trees
+ * - Path counting problems
+ * - Nearest neighbor searches
+ *
+ * @see Centroid Decomposition
+ * @see Centroid Decomposition Tutorial
+ * @author lens161
+ */
+public final class CentroidDecomposition {
+
+ private CentroidDecomposition() {
+ }
+
+ /**
+ * Represents the centroid tree structure.
+ */
+ public static final class CentroidTree {
+ private final int n;
+ private final List API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
+ *
+ * @see Wikipedia: Gomory–Hu tree
+ */
+
+public final class GomoryHuTree {
+ private GomoryHuTree() {
+ }
+
+ public static int[][] buildTree(int[][] cap) {
+ validateCapacityMatrix(cap);
+ final int n = cap.length;
+ if (n == 1) {
+ return new int[][] {new int[] {-1}, new int[] {0}};
+ }
+
+ int[] parent = new int[n];
+ int[] weight = new int[n];
+ Arrays.fill(parent, 0);
+ parent[0] = -1;
+ weight[0] = 0;
+
+ for (int s = 1; s < n; s++) {
+ int t = parent[s];
+ MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t);
+ int f = res.flow;
+ weight[s] = f;
+
+ for (int v = 0; v < n; v++) {
+ if (v != s && parent[v] == t && res.reachable[v]) {
+ parent[v] = s;
+ }
+ }
+
+ if (t != 0 && res.reachable[parent[t]]) {
+ parent[s] = parent[t];
+ parent[t] = s;
+ weight[s] = weight[t];
+ weight[t] = f;
+ }
+ }
+ return new int[][] {parent, weight};
+ }
+
+ private static void validateCapacityMatrix(int[][] cap) {
+ if (cap == null || cap.length == 0) {
+ throw new IllegalArgumentException("Capacity matrix must not be null or empty");
+ }
+ final int n = cap.length;
+ for (int i = 0; i < n; i++) {
+ if (cap[i] == null || cap[i].length != n) {
+ throw new IllegalArgumentException("Capacity matrix must be square");
+ }
+ for (int j = 0; j < n; j++) {
+ if (cap[i][j] < 0) {
+ throw new IllegalArgumentException("Capacities must be non-negative");
+ }
+ }
+ }
+ }
+
+ private static final class MaxFlowResult {
+ final int flow;
+ final boolean[] reachable;
+ MaxFlowResult(int flow, boolean[] reachable) {
+ this.flow = flow;
+ this.reachable = reachable;
+ }
+ }
+
+ private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) {
+ final int n = capacity.length;
+ int[][] residual = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ residual[i] = Arrays.copyOf(capacity[i], n);
+ }
+
+ int[] parent = new int[n];
+ int maxFlow = 0;
+
+ while (bfs(residual, source, sink, parent)) {
+ int pathFlow = Integer.MAX_VALUE;
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ pathFlow = Math.min(pathFlow, residual[u][v]);
+ }
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ residual[u][v] -= pathFlow;
+ residual[v][u] += pathFlow;
+ }
+ maxFlow += pathFlow;
+ }
+
+ boolean[] reachable = new boolean[n];
+ markReachable(residual, source, reachable);
+ return new MaxFlowResult(maxFlow, reachable);
+ }
+
+ private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
+ Arrays.fill(parent, -1);
+ parent[source] = source;
+ Queue In this implementation, a node's null left/right pointers are used
+ * to point to the in-order predecessor/successor respectively. Two flags
+ * indicate whether left/right pointers are real children or threads.
+ *
+ * @see Wikipedia:
+ * Threaded binary tree
+ */
+public final class ThreadedBinaryTree {
+
+ private Node root;
+
+ private static final class Node {
+ int value;
+ Node left;
+ Node right;
+ boolean leftIsThread;
+ boolean rightIsThread;
+
+ Node(int value) {
+ this.value = value;
+ this.left = null;
+ this.right = null;
+ this.leftIsThread = false;
+ this.rightIsThread = false;
+ }
+ }
+
+ public ThreadedBinaryTree() {
+ this.root = null;
+ }
+
+ /**
+ * Inserts a value into the threaded binary tree. Duplicate values are inserted
+ * to the right subtree (consistent deterministic rule).
+ *
+ * @param value the integer value to insert
+ */
+ public void insert(int value) {
+ Node newNode = new Node(value);
+ if (root == null) {
+ root = newNode;
+ return;
+ }
+
+ Node current = root;
+ Node parent = null;
+
+ while (true) {
+ parent = current;
+ if (value < current.value) {
+ if (!current.leftIsThread && current.left != null) {
+ current = current.left;
+ } else {
+ break;
+ }
+ } else { // value >= current.value
+ if (!current.rightIsThread && current.right != null) {
+ current = current.right;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (value < parent.value) {
+ // attach newNode as left child
+ newNode.left = parent.left;
+ newNode.leftIsThread = parent.leftIsThread;
+ newNode.right = parent;
+ newNode.rightIsThread = true;
+
+ parent.left = newNode;
+ parent.leftIsThread = false;
+ } else {
+ // attach newNode as right child
+ newNode.right = parent.right;
+ newNode.rightIsThread = parent.rightIsThread;
+ newNode.left = parent;
+ newNode.leftIsThread = true;
+
+ parent.right = newNode;
+ parent.rightIsThread = false;
+ }
+ }
+
+ /**
+ * Returns the in-order traversal of the tree as a list of integers.
+ * Traversal is done without recursion or an explicit stack by following threads.
+ *
+ * @return list containing the in-order sequence of node values
+ */
+ public List The One-Time Pad is information-theoretically secure if:
+ * This implementation is for educational purposes only and should not be
+ * used in production systems.
+ */
+public final class OneTimePadCipher {
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ private OneTimePadCipher() {
+ // utility class
+ }
+
+ /**
+ * Generates a random key of the given length in bytes.
+ *
+ * @param length the length of the key in bytes, must be non-negative
+ * @return a new random key
+ * @throws IllegalArgumentException if length is negative
+ */
+ public static byte[] generateKey(int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length must be non-negative");
+ }
+ byte[] key = new byte[length];
+ RANDOM.nextBytes(key);
+ return key;
+ }
+
+ /**
+ * Encrypts the given plaintext bytes using the provided key.
+ * The key length must be exactly the same as the plaintext length.
+ *
+ * @param plaintext the plaintext bytes, must not be {@code null}
+ * @param key the one-time pad key bytes, must not be {@code null}
+ * @return the ciphertext bytes
+ * @throws IllegalArgumentException if the key length does not match plaintext length
+ * @throws NullPointerException if plaintext or key is {@code null}
+ */
+ public static byte[] encrypt(byte[] plaintext, byte[] key) {
+ validateInputs(plaintext, key);
+ return xor(plaintext, key);
+ }
+
+ /**
+ * Decrypts the given ciphertext bytes using the provided key.
+ * For a One-Time Pad, decryption is identical to encryption:
+ * {@code plaintext = ciphertext XOR key}.
+ *
+ * @param ciphertext the ciphertext bytes, must not be {@code null}
+ * @param key the one-time pad key bytes, must not be {@code null}
+ * @return the decrypted plaintext bytes
+ * @throws IllegalArgumentException if the key length does not match ciphertext length
+ * @throws NullPointerException if ciphertext or key is {@code null}
+ */
+ public static byte[] decrypt(byte[] ciphertext, byte[] key) {
+ validateInputs(ciphertext, key);
+ return xor(ciphertext, key);
+ }
+
+ private static void validateInputs(byte[] input, byte[] key) {
+ Objects.requireNonNull(input, "input must not be null");
+ Objects.requireNonNull(key, "key must not be null");
+ if (input.length != key.length) {
+ throw new IllegalArgumentException("Key length must match input length");
+ }
+ }
+
+ private static byte[] xor(byte[] data, byte[] key) {
+ byte[] result = new byte[data.length];
+ for (int i = 0; i < data.length; i++) {
+ result[i] = (byte) (data[i] ^ key[i]);
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java
new file mode 100644
index 000000000000..837c56c603d4
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java
@@ -0,0 +1,49 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Test;
+
+class OneTimePadCipherTest {
+
+ @Test
+ void encryptAndDecryptWithRandomKeyRestoresPlaintext() {
+ String plaintext = "The quick brown fox jumps over the lazy dog.";
+ byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
+
+ byte[] key = OneTimePadCipher.generateKey(plaintextBytes.length);
+
+ byte[] ciphertext = OneTimePadCipher.encrypt(plaintextBytes, key);
+ byte[] decrypted = OneTimePadCipher.decrypt(ciphertext, key);
+
+ assertArrayEquals(plaintextBytes, decrypted);
+ assertEquals(plaintext, new String(decrypted, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void generateKeyWithNegativeLengthThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.generateKey(-1));
+ }
+
+ @Test
+ void encryptWithMismatchedKeyLengthThrowsException() {
+ byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
+ byte[] shortKey = OneTimePadCipher.generateKey(2);
+
+ assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.encrypt(data, shortKey));
+ }
+
+ @Test
+ void decryptWithMismatchedKeyLengthThrowsException() {
+ byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
+ byte[] key = OneTimePadCipher.generateKey(data.length);
+ byte[] ciphertext = OneTimePadCipher.encrypt(data, key);
+
+ byte[] wrongSizedKey = OneTimePadCipher.generateKey(data.length + 1);
+
+ assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.decrypt(ciphertext, wrongSizedKey));
+ }
+}
From 927fe1f87cdfe2ea57051812427e78c9cf6f4775 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 2 Dec 2025 09:54:59 +0100
Subject: [PATCH 178/203] chore(deps): bump com.puppycrawl.tools:checkstyle
from 12.1.2 to 12.2.0 (#7147)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 12.1.2 to 12.2.0.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-12.1.2...checkstyle-12.2.0)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 12.2.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
+ * The conversion follows a preorder traversal pattern (root → left → right)
+ * and uses parentheses to denote the tree structure.
+ * Empty parentheses "()" are used to explicitly represent missing left children
+ * when a right child exists, ensuring the structure is unambiguous.
+ *
+ * This implementation matches the logic from LeetCode problem 606:
+ * Construct String from Binary Tree.
+ *
+ * For more details, see
+ * https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
+ */
+public final class ExtendedEuclideanAlgorithm {
+
+ private ExtendedEuclideanAlgorithm() {
+ }
+
+ /**
+ * This method implements the extended Euclidean algorithm.
+ *
+ * @param a The first number.
+ * @param b The second number.
+ * @return An array of three integers:
+ * A "word" is defined as a maximal substring consisting of non-space
+ * characters only. Trailing spaces at the end of the string are ignored.
+ *
+ * Example:
+ * This implementation runs in O(n) time complexity, where n is the length
+ * of the input string, and uses O(1) additional space.
+ */
+public class LengthOfLastWord {
+
+ /**
+ * Returns the length of the last word in the specified string.
+ *
+ * The method iterates from the end of the string, skipping trailing
+ * spaces first, and then counts the number of consecutive non-space characters
+ * characters until another space (or the beginning of the string) is reached.
+ *
+ * @param s the input string to analyze
+ * @return the length of the last word in {@code s}; returns 0 if there is no word
+ * @throws NullPointerException if {@code s} is {@code null}
+ */
+ public int lengthOfLastWord(String s) {
+ int sizeOfString = s.length() - 1;
+ int lastWordLength = 0;
+
+ // Skip trailing spaces from the end of the string
+ while (sizeOfString >= 0 && s.charAt(sizeOfString) == ' ') {
+ sizeOfString--;
+ }
+
+ // Count the characters of the last word
+ while (sizeOfString >= 0 && s.charAt(sizeOfString) != ' ') {
+ lastWordLength++;
+ sizeOfString--;
+ }
+
+ return lastWordLength;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java b/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java
new file mode 100644
index 000000000000..46a0a6eb0008
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java
@@ -0,0 +1,18 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class LengthOfLastWordTest {
+ @Test
+ public void testLengthOfLastWord() {
+ assertEquals(5, new LengthOfLastWord().lengthOfLastWord("Hello World"));
+ assertEquals(4, new LengthOfLastWord().lengthOfLastWord(" fly me to the moon "));
+ assertEquals(6, new LengthOfLastWord().lengthOfLastWord("luffy is still joyboy"));
+ assertEquals(5, new LengthOfLastWord().lengthOfLastWord("Hello"));
+ assertEquals(0, new LengthOfLastWord().lengthOfLastWord(" "));
+ assertEquals(0, new LengthOfLastWord().lengthOfLastWord(""));
+ assertEquals(3, new LengthOfLastWord().lengthOfLastWord("JUST LIE "));
+ }
+}
From 1eb0d61730e033bbc37bab67f795f9fa3a8fc2b1 Mon Sep 17 00:00:00 2001
From: HxCodes The algorithm selects a pivot element and partitions the array into two
+ * subarrays such that:
+ * The subarrays are then recursively sorted until the entire array is ordered.
+ *
+ * This implementation uses randomization to reduce the probability of
+ * encountering worst-case performance on already sorted inputs.
+ *
+ * Time Complexity:
+ * Space Complexity: O(log n) due to recursion stack (in-place sorting).
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see SortAlgorithm
*/
+
class QuickSort implements SortAlgorithm {
- /**
- * Generic Quick Sort algorithm.
- *
- * Time Complexity:
- * - Best case: O(n log n) – pivot splits array roughly in half each time.
- * - Average case: O(n log n)
- * - Worst case: O(n^2) – occurs when pivot consistently produces unbalanced splits.
- *
- * Space Complexity: O(log n) – recursion stack, in-place sorting.
- *
- * @see SortAlgorithm
- */
@Override
public This HashMap does not allow modification of existing instances.
+ * Any update operation returns a new ImmutableHashMap.
+ *
+ * @param
+ *
+ * > combinationSum(int[] candidates, int target) {
+ List
> results = new ArrayList<>();
+ if (candidates == null || candidates.length == 0) {
+ return results;
+ }
+
+ // Sort to help with pruning duplicates and early termination
+ Arrays.sort(candidates);
+ backtrack(candidates, target, 0, new ArrayList<>(), results);
+ return results;
+ }
+
+ private static void backtrack(int[] candidates, int remaining, int start, List
> results) {
+ if (remaining == 0) {
+ // Found valid combination; add a copy
+ results.add(new ArrayList<>(combination));
+ return;
+ }
+
+ for (int i = start; i < candidates.length; i++) {
+ int candidate = candidates[i];
+
+ // If candidate is greater than remaining target, further candidates (sorted) will also be too big
+ if (candidate > remaining) {
+ break;
+ }
+
+ // include candidate
+ combination.add(candidate);
+ // Because we can reuse the same element, we pass i (not i + 1)
+ backtrack(candidates, remaining - candidate, i, combination, results);
+ // backtrack: remove last
+ combination.remove(combination.size() - 1);
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java b/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java
new file mode 100644
index 000000000000..986c71acebe8
--- /dev/null
+++ b/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java
@@ -0,0 +1,29 @@
+package com.thealgorithms.backtracking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class CombinationSumTest {
+ private static List
> norm(Iterable
> x) {
+ List
> y = new ArrayList<>();
+ for (var p : x) {
+ var q = new ArrayList<>(p);
+ q.sort(Integer::compare);
+ y.add(q);
+ }
+ y.sort(Comparator.
>comparingInt(List::size).thenComparing(Object::toString));
+ return y;
+ }
+
+ @Test
+ void sample() {
+ int[] candidates = {2, 3, 6, 7};
+ int target = 7;
+ var expected = List.of(List.of(2, 2, 3), List.of(7));
+ assertEquals(norm(expected), norm(CombinationSum.combinationSum(candidates, target)));
+ }
+}
From 702664116d73dd8283c307bec606b6a31bdb70c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 22:41:33 +0700
Subject: [PATCH 142/203] chore: fix typos in
src/main/java/com/thealgorithms/backtracking/FloodFill.java (#7046)
Fix typos in src/main/java/com/thealgorithms/backtracking/FloodFill.java
---
.../com/thealgorithms/backtracking/FloodFill.java | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/main/java/com/thealgorithms/backtracking/FloodFill.java b/src/main/java/com/thealgorithms/backtracking/FloodFill.java
index c8219ca8ba7e..0f31a9c5a30e 100644
--- a/src/main/java/com/thealgorithms/backtracking/FloodFill.java
+++ b/src/main/java/com/thealgorithms/backtracking/FloodFill.java
@@ -12,8 +12,8 @@ private FloodFill() {
* Get the color at the given coordinates of a 2D image
*
* @param image The image to be filled
- * @param x The x co-ordinate of which color is to be obtained
- * @param y The y co-ordinate of which color is to be obtained
+ * @param x The x coordinate of which color is to be obtained
+ * @param y The y coordinate of which color is to be obtained
*/
public static int getPixel(final int[][] image, final int x, final int y) {
@@ -24,8 +24,8 @@ public static int getPixel(final int[][] image, final int x, final int y) {
* Put the color at the given coordinates of a 2D image
*
* @param image The image to be filled
- * @param x The x co-ordinate at which color is to be filled
- * @param y The y co-ordinate at which color is to be filled
+ * @param x The x coordinate at which color is to be filled
+ * @param y The y coordinate at which color is to be filled
*/
public static void putPixel(final int[][] image, final int x, final int y, final int newColor) {
image[x][y] = newColor;
@@ -35,8 +35,8 @@ public static void putPixel(final int[][] image, final int x, final int y, final
* Fill the 2D image with new color
*
* @param image The image to be filled
- * @param x The x co-ordinate at which color is to be filled
- * @param y The y co-ordinate at which color is to be filled
+ * @param x The x coordinate at which color is to be filled
+ * @param y The y coordinate at which color is to be filled
* @param newColor The new color which to be filled in the image
* @param oldColor The old color which is to be replaced in the image
*/
From 147da38888303b195c8d95a6f86dacfec9c5f665 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?=
<55955273+khanhkhanhlele@users.noreply.github.com>
Date: Wed, 5 Nov 2025 22:45:27 +0700
Subject: [PATCH 143/203] chore: fix typos in
src/main/java/com/thealgorithms/ciphers/AES.java (#7047)
Fix typos in src/main/java/com/thealgorithms/ciphers/AES.java
Co-authored-by: Deniz Altunkapan
+ *
+ *
+ *
+ *
+ *
+ * IMPORTANT contracts
+ *
+ *
+ *
+ *
+ *
+ * If you prefer value-based semantics, replace with HashMap> adj;
+ private final int[] parent;
+ private final int[] subtreeSize;
+ private final boolean[] removed;
+ private int root;
+
+ /**
+ * Constructs a centroid tree from an adjacency list.
+ *
+ * @param adj adjacency list representation of the tree (0-indexed)
+ * @throws IllegalArgumentException if tree is empty or null
+ */
+ public CentroidTree(List
> adj) {
+ if (adj == null || adj.isEmpty()) {
+ throw new IllegalArgumentException("Tree cannot be empty or null");
+ }
+
+ this.n = adj.size();
+ this.adj = adj;
+ this.parent = new int[n];
+ this.subtreeSize = new int[n];
+ this.removed = new boolean[n];
+ Arrays.fill(parent, -1);
+
+ // Build centroid tree starting from node 0
+ this.root = decompose(0, -1);
+ }
+
+ /**
+ * Recursively builds the centroid tree.
+ *
+ * @param u current node
+ * @param p parent in centroid tree
+ * @return centroid of current component
+ */
+ private int decompose(int u, int p) {
+ int size = getSubtreeSize(u, -1);
+ int centroid = findCentroid(u, -1, size);
+
+ removed[centroid] = true;
+ parent[centroid] = p;
+
+ // Recursively decompose each subtree
+ for (int v : adj.get(centroid)) {
+ if (!removed[v]) {
+ decompose(v, centroid);
+ }
+ }
+
+ return centroid;
+ }
+
+ /**
+ * Calculates subtree size from node u.
+ *
+ * @param u current node
+ * @param p parent node (-1 for root)
+ * @return size of subtree rooted at u
+ */
+ private int getSubtreeSize(int u, int p) {
+ subtreeSize[u] = 1;
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v]) {
+ subtreeSize[u] += getSubtreeSize(v, u);
+ }
+ }
+ return subtreeSize[u];
+ }
+
+ /**
+ * Finds the centroid of a subtree.
+ * A centroid is a node whose removal creates components with size <= totalSize/2.
+ *
+ * @param u current node
+ * @param p parent node
+ * @param totalSize total size of current component
+ * @return centroid node
+ */
+ private int findCentroid(int u, int p, int totalSize) {
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v] && subtreeSize[v] > totalSize / 2) {
+ return findCentroid(v, u, totalSize);
+ }
+ }
+ return u;
+ }
+
+ /**
+ * Gets the parent of a node in the centroid tree.
+ *
+ * @param node the node
+ * @return parent node in centroid tree, or -1 if root
+ */
+ public int getParent(int node) {
+ if (node < 0 || node >= n) {
+ throw new IllegalArgumentException("Invalid node: " + node);
+ }
+ return parent[node];
+ }
+
+ /**
+ * Gets the root of the centroid tree.
+ *
+ * @return root node
+ */
+ public int getRoot() {
+ return root;
+ }
+
+ /**
+ * Gets the number of nodes in the tree.
+ *
+ * @return number of nodes
+ */
+ public int size() {
+ return n;
+ }
+
+ /**
+ * Returns the centroid tree structure as a string.
+ * Format: node -> parent (or ROOT for root node)
+ *
+ * @return string representation
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Centroid Tree:\n");
+ for (int i = 0; i < n; i++) {
+ sb.append("Node ").append(i).append(" -> ");
+ if (parent[i] == -1) {
+ sb.append("ROOT");
+ } else {
+ sb.append("Parent ").append(parent[i]);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Creates a centroid tree from an edge list.
+ *
+ * @param n number of nodes (0-indexed: 0 to n-1)
+ * @param edges list of edges where each edge is [u, v]
+ * @return CentroidTree object
+ * @throws IllegalArgumentException if n <= 0 or edges is invalid
+ */
+ public static CentroidTree buildFromEdges(int n, int[][] edges) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("Number of nodes must be positive");
+ }
+ if (edges == null) {
+ throw new IllegalArgumentException("Edges cannot be null");
+ }
+ if (edges.length != n - 1) {
+ throw new IllegalArgumentException("Tree must have exactly n-1 edges");
+ }
+
+ List
> adj = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ adj.add(new ArrayList<>());
+ }
+
+ for (int[] edge : edges) {
+ if (edge.length != 2) {
+ throw new IllegalArgumentException("Each edge must have exactly 2 nodes");
+ }
+ int u = edge[0];
+ int v = edge[1];
+
+ if (u < 0 || u >= n || v < 0 || v >= n) {
+ throw new IllegalArgumentException("Invalid node in edge: [" + u + ", " + v + "]");
+ }
+
+ adj.get(u).add(v);
+ adj.get(v).add(u);
+ }
+
+ return new CentroidTree(adj);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java
new file mode 100644
index 000000000000..43d732e54f34
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java
@@ -0,0 +1,236 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test cases for CentroidDecomposition
+ *
+ * @author lens161
+ */
+class CentroidDecompositionTest {
+
+ @Test
+ void testSingleNode() {
+ // Tree with just one node
+ int[][] edges = {};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(1, edges);
+
+ assertEquals(1, tree.size());
+ assertEquals(0, tree.getRoot());
+ assertEquals(-1, tree.getParent(0));
+ }
+
+ @Test
+ void testTwoNodes() {
+ // Simple tree: 0 - 1
+ int[][] edges = {{0, 1}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(2, edges);
+
+ assertEquals(2, tree.size());
+ int root = tree.getRoot();
+ assertTrue(root == 0 || root == 1, "Root should be either node 0 or 1");
+
+ // One node should be root, other should have the root as parent
+ int nonRoot = (root == 0) ? 1 : 0;
+ assertEquals(-1, tree.getParent(root));
+ assertEquals(root, tree.getParent(nonRoot));
+ }
+
+ @Test
+ void testLinearTree() {
+ // Linear tree: 0 - 1 - 2 - 3 - 4
+ int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // For a linear tree of 5 nodes, the centroid should be the middle node (node 2)
+ assertEquals(2, tree.getRoot());
+ assertEquals(-1, tree.getParent(2));
+ }
+
+ @Test
+ void testBalancedBinaryTree() {
+ // Balanced binary tree:
+ // 0
+ // / \
+ // 1 2
+ // / \
+ // 3 4
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // Root should be 0 or 1 (both are valid centroids)
+ int root = tree.getRoot();
+ assertTrue(root == 0 || root == 1);
+ assertEquals(-1, tree.getParent(root));
+
+ // All nodes should have a parent in centroid tree except root
+ for (int i = 0; i < 5; i++) {
+ if (i != root) {
+ assertTrue(tree.getParent(i) >= 0 && tree.getParent(i) < 5);
+ }
+ }
+ }
+
+ @Test
+ void testStarTree() {
+ // Star tree: center node 0 connected to 1, 2, 3, 4
+ int[][] edges = {{0, 1}, {0, 2}, {0, 3}, {0, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // Center node (0) should be the root
+ assertEquals(0, tree.getRoot());
+
+ // All other nodes should have 0 as parent
+ for (int i = 1; i < 5; i++) {
+ assertEquals(0, tree.getParent(i));
+ }
+ }
+
+ @Test
+ void testCompleteTree() {
+ // Complete binary tree of 7 nodes:
+ // 0
+ // / \
+ // 1 2
+ // / \ / \
+ // 3 4 5 6
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(7, edges);
+
+ assertEquals(7, tree.size());
+ assertEquals(0, tree.getRoot()); // Root should be the center
+
+ // Verify all nodes are reachable in centroid tree
+ boolean[] visited = new boolean[7];
+ visited[0] = true;
+ for (int i = 1; i < 7; i++) {
+ int parent = tree.getParent(i);
+ assertTrue(parent >= 0 && parent < 7);
+ assertTrue(visited[parent], "Parent should be processed before child");
+ visited[i] = true;
+ }
+ }
+
+ @Test
+ void testLargerTree() {
+ // Tree with 10 nodes
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}, {3, 7}, {4, 8}, {5, 9}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(10, edges);
+
+ assertEquals(10, tree.size());
+ int root = tree.getRoot();
+ assertTrue(root >= 0 && root < 10);
+ assertEquals(-1, tree.getParent(root));
+
+ // Verify centroid tree structure is valid
+ for (int i = 0; i < 10; i++) {
+ if (i != root) {
+ assertTrue(tree.getParent(i) >= -1 && tree.getParent(i) < 10);
+ }
+ }
+ }
+
+ @Test
+ void testPathGraph() {
+ // Path graph with 8 nodes: 0-1-2-3-4-5-6-7
+ int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}, {6, 7}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(8, edges);
+
+ assertEquals(8, tree.size());
+ // For path of 8 nodes, centroid should be around middle
+ int root = tree.getRoot();
+ assertTrue(root >= 2 && root <= 5, "Root should be near the middle of path");
+ }
+
+ @Test
+ void testInvalidEmptyTree() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(0, new int[][] {}); });
+ }
+
+ @Test
+ void testInvalidNegativeNodes() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(-1, new int[][] {}); });
+ }
+
+ @Test
+ void testInvalidNullEdges() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, null); });
+ }
+
+ @Test
+ void testInvalidEdgeCount() {
+ // Tree with n nodes must have n-1 edges
+ int[][] edges = {{0, 1}, {1, 2}}; // 2 edges for 5 nodes (should be 4)
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, edges); });
+ }
+
+ @Test
+ void testInvalidEdgeFormat() {
+ int[][] edges = {{0, 1, 2}}; // Edge with 3 elements instead of 2
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); });
+ }
+
+ @Test
+ void testInvalidNodeInEdge() {
+ int[][] edges = {{0, 5}}; // Node 5 doesn't exist in tree of size 3
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); });
+ }
+
+ @Test
+ void testInvalidNodeQuery() {
+ int[][] edges = {{0, 1}, {1, 2}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges);
+
+ assertThrows(IllegalArgumentException.class, () -> { tree.getParent(-1); });
+
+ assertThrows(IllegalArgumentException.class, () -> { tree.getParent(5); });
+ }
+
+ @Test
+ void testToString() {
+ int[][] edges = {{0, 1}, {1, 2}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges);
+
+ String result = tree.toString();
+ assertNotNull(result);
+ assertTrue(result.contains("Centroid Tree"));
+ assertTrue(result.contains("Node"));
+ assertTrue(result.contains("ROOT"));
+ }
+
+ @Test
+ void testAdjacencyListConstructor() {
+ List
> adj = new ArrayList<>();
+ for (int i = 0; i < 3; i++) {
+ adj.add(new ArrayList<>());
+ }
+ adj.get(0).add(1);
+ adj.get(1).add(0);
+ adj.get(1).add(2);
+ adj.get(2).add(1);
+
+ CentroidDecomposition.CentroidTree tree = new CentroidDecomposition.CentroidTree(adj);
+ assertEquals(3, tree.size());
+ assertEquals(1, tree.getRoot());
+ }
+
+ @Test
+ void testNullAdjacencyList() {
+ assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(null); });
+ }
+
+ @Test
+ void testEmptyAdjacencyList() {
+ assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(new ArrayList<>()); });
+ }
+}
From f693c44b539508cd262374a6d8e9d7329301d6cc Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan
> g = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ g.add(new ArrayList<>());
+ }
+ for (int v = 1; v < n; v++) {
+ int u = parent[v];
+ int w = weight[v];
+ g.get(u).add(new int[] {v, w});
+ g.get(v).add(new int[] {u, w});
+ }
+
+ for (int s = 0; s < n; s++) {
+ for (int t = s + 1; t < n; t++) {
+ int treeVal = minEdgeOnPath(g, s, t);
+ int flowVal = EdmondsKarp.maxFlow(cap, s, t);
+ assertEquals(flowVal, treeVal, "pair (" + s + "," + t + ")");
+ }
+ }
+ }
+
+ private static int minEdgeOnPath(List
> g, int s, int t) {
+ // BFS to record parent and edge weight along the path, since it's a tree, unique path exists
+ int n = g.size();
+ int[] parent = new int[n];
+ int[] edgeW = new int[n];
+ Arrays.fill(parent, -1);
+ Queue
+ *
+ *
+ * Rules:
+ *
+ *
+ *
+ * Example:
+ *
+ *
+ * Input tree:
+ * 1
+ * / \
+ * 2 3
+ * \
+ * 4
+ *
+ * Output string:
+ * "1(2()(4))(3)"
+ *
+ *
+ *
+ * 1/f = 1/v + 1/u
+ *
+ *
+ * where:
+ *
+ *
+ *
+ * Uses the Cartesian sign convention.
+ *
+ * @see Thin Lens
+ */
+public final class ThinLens {
+
+ private ThinLens() {
+ throw new AssertionError("No instances.");
+ }
+
+ /**
+ * Computes the image distance using the thin lens formula.
+ *
+ * @param focalLength focal length of the lens (f)
+ * @param objectDistance object distance (u)
+ * @return image distance (v)
+ * @throws IllegalArgumentException if focal length or object distance is zero
+ */
+ public static double imageDistance(double focalLength, double objectDistance) {
+
+ if (focalLength == 0 || objectDistance == 0) {
+ throw new IllegalArgumentException("Focal length and object distance must be non-zero.");
+ }
+
+ return 1.0 / ((1.0 / focalLength) - (1.0 / objectDistance));
+ }
+
+ /**
+ * Computes magnification of the image.
+ *
+ *
+ * m = v / u
+ *
+ *
+ * @param imageDistance image distance (v)
+ * @param objectDistance object distance (u)
+ * @return magnification
+ * @throws IllegalArgumentException if object distance is zero
+ */
+ public static double magnification(double imageDistance, double objectDistance) {
+
+ if (objectDistance == 0) {
+ throw new IllegalArgumentException("Object distance must be non-zero.");
+ }
+
+ return imageDistance / objectDistance;
+ }
+
+ /**
+ * Determines whether the image formed is real or virtual.
+ *
+ * @param imageDistance image distance (v)
+ * @return {@code true} if image is real, {@code false} if virtual
+ */
+ public static boolean isRealImage(double imageDistance) {
+ return imageDistance > 0;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/physics/ThinLensTest.java b/src/test/java/com/thealgorithms/physics/ThinLensTest.java
new file mode 100644
index 000000000000..cf7e94676819
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/ThinLensTest.java
@@ -0,0 +1,19 @@
+package com.thealgorithms.physics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ThinLensTest {
+
+ @Test
+ void testConvexLensRealImage() {
+ double v = ThinLens.imageDistance(10, 20);
+ assertEquals(20, v, 1e-6);
+ }
+
+ @Test
+ void testMagnification() {
+ assertEquals(2.0, ThinLens.magnification(20, 10), 1e-6);
+ }
+}
From 2911a7b52c1032591d2aa23feef5669fa43c7961 Mon Sep 17 00:00:00 2001
From: Rajuri Likhitha
+ *
+ */
+ public static long[] extendedGCD(long a, long b) {
+ if (b == 0) {
+ // Base case: gcd(a, 0) = a. The equation is a*1 + 0*0 = a.
+ return new long[] {a, 1, 0};
+ }
+
+ // Recursive call
+ long[] result = extendedGCD(b, a % b);
+ long gcd = result[0];
+ long x1 = result[1];
+ long y1 = result[2];
+
+ // Update coefficients using the results from the recursive call
+ long x = y1;
+ long y = x1 - a / b * y1;
+
+ return new long[] {gcd, x, y};
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java
new file mode 100644
index 000000000000..56c005fd51ae
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java
@@ -0,0 +1,47 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class ExtendedEuclideanAlgorithmTest {
+
+ /**
+ * Verifies that the returned values satisfy Bézout's identity: a*x + b*y =
+ * gcd(a, b)
+ */
+ private void verifyBezoutIdentity(long a, long b, long[] result) {
+ long gcd = result[0];
+ long x = result[1];
+ long y = result[2];
+ assertEquals(a * x + b * y, gcd, "Bézout's identity failed for gcd(" + a + ", " + b + ")");
+ }
+
+ @Test
+ public void testExtendedGCD() {
+ // Test case 1: General case gcd(30, 50) = 10
+ long[] result1 = ExtendedEuclideanAlgorithm.extendedGCD(30, 50);
+ assertEquals(10, result1[0], "Test Case 1 Failed: gcd(30, 50) should be 10");
+ verifyBezoutIdentity(30, 50, result1);
+
+ // Test case 2: Another general case gcd(240, 46) = 2
+ long[] result2 = ExtendedEuclideanAlgorithm.extendedGCD(240, 46);
+ assertEquals(2, result2[0], "Test Case 2 Failed: gcd(240, 46) should be 2");
+ verifyBezoutIdentity(240, 46, result2);
+
+ // Test case 3: Base case where b is 0, gcd(10, 0) = 10
+ long[] result3 = ExtendedEuclideanAlgorithm.extendedGCD(10, 0);
+ assertEquals(10, result3[0], "Test Case 3 Failed: gcd(10, 0) should be 10");
+ verifyBezoutIdentity(10, 0, result3);
+
+ // Test case 4: Numbers are co-prime gcd(17, 13) = 1
+ long[] result4 = ExtendedEuclideanAlgorithm.extendedGCD(17, 13);
+ assertEquals(1, result4[0], "Test Case 4 Failed: gcd(17, 13) should be 1");
+ verifyBezoutIdentity(17, 13, result4);
+
+ // Test case 5: One number is a multiple of the other gcd(100, 20) = 20
+ long[] result5 = ExtendedEuclideanAlgorithm.extendedGCD(100, 20);
+ assertEquals(20, result5[0], "Test Case 5 Failed: gcd(100, 20) should be 20");
+ verifyBezoutIdentity(100, 20, result5);
+ }
+}
From 7d51c7f32097b488bab330f858391387ce4ce72c Mon Sep 17 00:00:00 2001
From: Saniya Mane <185913688+Saniya2701@users.noreply.github.com>
Date: Thu, 18 Dec 2025 13:25:03 +0530
Subject: [PATCH 193/203] Add additional edge cases to GCD unit tests (#7174)
* Add additional edge cases to GCD unit tests
* Rename GCD test methods and add descriptive names
* Fix formatting in GCD tests
---------
Co-authored-by: Deniz Altunkapan {@code
+ * LengthOfLastWord obj = new LengthOfLastWord();
+ * System.out.println(obj.lengthOfLastWord("Hello World")); // Output: 5
+ * System.out.println(obj.lengthOfLastWord(" fly me to the moon ")); // Output: 4
+ * System.out.println(obj.lengthOfLastWord("luffy is still joyboy")); // Output: 6
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *