result) {
+
+ if (current.length() == chars.length) {
+ result.add(current.toString());
+ return;
+ }
+
+ for (int i = 0; i < chars.length; i++) {
+
+ // skip duplicates
+ if (i > 0 && chars[i] == chars[i - 1] && !used[i - 1]) {
+ continue;
+ }
+
+ if (!used[i]) {
+ used[i] = true;
+ current.append(chars[i]);
+
+ backtrack(chars, used, current, result);
+
+ // undo changes
+ used[i] = false;
+ current.deleteCharAt(current.length() - 1);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
index 242f35fc35f2..7df522ca8f69 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
@@ -1,79 +1,79 @@
package com.thealgorithms.bitmanipulation;
-public class CountSetBits {
+/**
+ * Utility class to count total set bits from 1 to N
+ * A set bit is a bit in binary representation that is 1
+ *
+ * @author navadeep
+ */
+public final class CountSetBits {
+
+ private CountSetBits() {
+ // Utility class, prevent instantiation
+ }
/**
- * The below algorithm is called as Brian Kernighan's algorithm
- * We can use Brian Kernighanβs algorithm to improve the above naive algorithmβs performance.
- The idea is to only consider the set bits of an integer by turning off its rightmost set bit
- (after counting it), so the next iteration of the loop considers the next rightmost bit.
-
- The expression n & (n-1) can be used to turn off the rightmost set bit of a number n. This
- works as the expression n-1 flips all the bits after the rightmost set bit of n, including the
- rightmost set bit itself. Therefore, n & (n-1) results in the last bit flipped of n.
-
- For example, consider number 52, which is 00110100 in binary, and has a total 3 bits set.
-
- 1st iteration of the loop: n = 52
-
- 00110100 & (n)
- 00110011 (n-1)
- ~~~~~~~~
- 00110000
+ * Counts total number of set bits in all numbers from 1 to n
+ * Time Complexity: O(log n)
+ *
+ * @param n the upper limit (inclusive)
+ * @return total count of set bits from 1 to n
+ * @throws IllegalArgumentException if n is negative
+ */
+ public static int countSetBits(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative");
+ }
+ if (n == 0) {
+ return 0;
+ }
- 2nd iteration of the loop: n = 48
+ // Find the largest power of 2 <= n
+ int x = largestPowerOf2InNumber(n);
- 00110000 & (n)
- 00101111 (n-1)
- ~~~~~~~~
- 00100000
+ // Total bits at position x: x * 2^(x-1)
+ int bitsAtPositionX = x * (1 << (x - 1));
+ // Remaining numbers after 2^x
+ int remainingNumbers = n - (1 << x) + 1;
- 3rd iteration of the loop: n = 32
+ // Recursively count for the rest
+ int rest = countSetBits(n - (1 << x));
- 00100000 & (n)
- 00011111 (n-1)
- ~~~~~~~~
- 00000000 (n = 0)
+ return bitsAtPositionX + remainingNumbers + rest;
+ }
- * @param num takes Long number whose number of set bit is to be found
- * @return the count of set bits in the binary equivalent
- */
- public long countSetBits(long num) {
- long cnt = 0;
- while (num > 0) {
- cnt++;
- num &= (num - 1);
+ /**
+ * Finds the position of the most significant bit in n
+ *
+ * @param n the number
+ * @return position of MSB (0-indexed from right)
+ */
+ private static int largestPowerOf2InNumber(int n) {
+ int position = 0;
+ while ((1 << position) <= n) {
+ position++;
}
- return cnt;
+ return position - 1;
}
/**
- * This approach takes O(1) running time to count the set bits, but requires a pre-processing.
+ * Alternative naive approach - counts set bits by iterating through all numbers
+ * Time Complexity: O(n log n)
*
- * So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk.
- *
- * Then the range is from 0-255 (0 to 2^7).
- * So, we may need to count set bits from 0 to 255 in individual chunks.
- *
- * @param num takes a long number
- * @return the count of set bits in the binary equivalent
+ * @param n the upper limit (inclusive)
+ * @return total count of set bits from 1 to n
*/
- public int lookupApproach(int num) {
- int[] table = new int[256];
- table[0] = 0;
-
- for (int i = 1; i < 256; i++) {
- table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2
+ public static int countSetBitsNaive(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative");
}
- int res = 0;
- for (int i = 0; i < 4; i++) {
- res += table[num & 0xff];
- num >>= 8;
+ int count = 0;
+ for (int i = 1; i <= n; i++) {
+ count += Integer.bitCount(i);
}
-
- return res;
+ return count;
}
}
diff --git a/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java
new file mode 100644
index 000000000000..7733f5cb46f2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java
@@ -0,0 +1,89 @@
+package com.thealgorithms.ciphers;
+
+import java.security.SecureRandom;
+import java.util.Objects;
+
+/**
+ * One-Time Pad (OTP) cipher implementation.
+ *
+ * The One-Time Pad is information-theoretically secure if:
+ *
+ * - The key is truly random.
+ * - The key length is at least as long as the plaintext.
+ * - The key is used only once and kept secret.
+ *
+ *
+ * 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/main/java/com/thealgorithms/conversions/TemperatureConverter.java b/src/main/java/com/thealgorithms/conversions/TemperatureConverter.java
new file mode 100644
index 000000000000..901db17c665d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/TemperatureConverter.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.conversions;
+
+/**
+ * A utility class to convert between different temperature units.
+ *
+ *
This class supports conversions between the following units:
+ *
+ * - Celsius
+ * - Fahrenheit
+ * - Kelvin
+ *
+ *
+ * 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/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java
new file mode 100644
index 000000000000..f6e09ec623b6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java
@@ -0,0 +1,115 @@
+package com.thealgorithms.datastructures.hashmap.hashing;
+
+/**
+ * Immutable HashMap implementation using separate chaining.
+ *
+ *
This HashMap does not allow modification of existing instances.
+ * Any update operation returns a new ImmutableHashMap.
+ *
+ * @param key type
+ * @param value type
+ */
+public final class ImmutableHashMap {
+
+ private static final int DEFAULT_CAPACITY = 16;
+
+ private final Node[] table;
+ private final int size;
+
+ /**
+ * Private constructor to enforce immutability.
+ */
+ private ImmutableHashMap(Node[] table, int size) {
+ this.table = table;
+ this.size = size;
+ }
+
+ /**
+ * Creates an empty ImmutableHashMap.
+ *
+ * @param key type
+ * @param value type
+ * @return empty ImmutableHashMap
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static ImmutableHashMap empty() {
+ Node[] table = (Node[]) new Node[DEFAULT_CAPACITY];
+ return new ImmutableHashMap<>(table, 0);
+ }
+
+ /**
+ * Returns a new ImmutableHashMap with the given key-value pair added.
+ *
+ * @param key key to add
+ * @param value value to associate
+ * @return new ImmutableHashMap instance
+ */
+ public ImmutableHashMap put(K key, V value) {
+ Node[] newTable = table.clone();
+ int index = hash(key);
+
+ newTable[index] = new Node<>(key, value, newTable[index]);
+ return new ImmutableHashMap<>(newTable, size + 1);
+ }
+
+ /**
+ * Retrieves the value associated with the given key.
+ *
+ * @param key key to search
+ * @return value if found, otherwise null
+ */
+ public V get(K key) {
+ int index = hash(key);
+ Node current = table[index];
+
+ while (current != null) {
+ if ((key == null && current.key == null) || (key != null && key.equals(current.key))) {
+ return current.value;
+ }
+ current = current.next;
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether the given key exists in the map.
+ *
+ * @param key key to check
+ * @return true if key exists, false otherwise
+ */
+ public boolean containsKey(K key) {
+ return get(key) != null;
+ }
+
+ /**
+ * Returns the number of key-value pairs.
+ *
+ * @return size of the map
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Computes hash index for a given key.
+ */
+ private int hash(K key) {
+ return key == null ? 0 : (key.hashCode() & Integer.MAX_VALUE) % table.length;
+ }
+
+ /**
+ * Node class for separate chaining.
+ */
+ private static final class Node {
+
+ private final K key;
+ private final V value;
+ private final Node next;
+
+ private Node(K key, V value, Node next) {
+ this.key = key;
+ this.value = value;
+ this.next = next;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
new file mode 100644
index 000000000000..ad7229760fd0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
@@ -0,0 +1,327 @@
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * An addressable (indexed) min-priority queue with O(log n) updates.
+ *
+ * Key features:
+ *
+ * - Each element E is tracked by a handle (its current heap index) via a map,
+ * enabling O(log n) {@code remove(e)} and O(log n) key updates
+ * ({@code changeKey/decreaseKey/increaseKey}).
+ * - The queue order is determined by the provided {@link Comparator}. If the
+ * comparator is {@code null}, elements must implement {@link Comparable}
+ * (same contract as {@link java.util.PriorityQueue}).
+ * - By default this implementation uses {@link IdentityHashMap} for the index
+ * mapping to avoid issues with duplicate-equals elements or mutable equals/hashCode.
+ * If you need value-based equality, switch to {@code HashMap} and read the caveats
+ * in the class-level Javadoc carefully.
+ *
+ *
+ * IMPORTANT contracts
+ *
+ * - Do not mutate comparator-relevant fields of an element directly while it is
+ * inside the queue. Always use {@code changeKey}/{@code decreaseKey}/{@code increaseKey}
+ * so the heap can be restored accordingly.
+ * - If you replace {@link IdentityHashMap} with {@link HashMap}, you must ensure:
+ * (a) no two distinct elements are {@code equals()}-equal at the same time in the queue, and
+ * (b) {@code equals/hashCode} of elements remain stable while enqueued.
+ * - {@code peek()} returns {@code null} when empty (matching {@link java.util.PriorityQueue}).
+ * - Not thread-safe.
+ *
+ *
+ * Complexities:
+ * {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
+ * {@code peek, isEmpty, size, contains} are O(1).
+ */
+public class IndexedPriorityQueue {
+
+ /** Binary heap storage (min-heap). */
+ private Object[] heap;
+
+ /** Number of elements in the heap. */
+ private int size;
+
+ /** Comparator used for ordering; if null, elements must be Comparable. */
+ private final Comparator super E> cmp;
+
+ /**
+ * Index map: element -> current heap index.
+ * We use IdentityHashMap by default to:
+ *
+ * - allow duplicate-equals elements;
+ * - avoid corruption when equals/hashCode are mutable or not ID-based.
+ *
+ * If you prefer value-based semantics, replace with HashMap and
+ * respect the warnings in the class Javadoc.
+ */
+ private final IdentityHashMap index;
+
+ private static final int DEFAULT_INITIAL_CAPACITY = 11;
+
+ public IndexedPriorityQueue() {
+ this(DEFAULT_INITIAL_CAPACITY, null);
+ }
+
+ public IndexedPriorityQueue(Comparator super E> cmp) {
+ this(DEFAULT_INITIAL_CAPACITY, cmp);
+ }
+
+ public IndexedPriorityQueue(int initialCapacity, Comparator super E> cmp) {
+ if (initialCapacity < 1) {
+ throw new IllegalArgumentException("initialCapacity < 1");
+ }
+ this.heap = new Object[initialCapacity];
+ this.cmp = cmp;
+ this.index = new IdentityHashMap<>();
+ }
+
+ /** Returns current number of elements. */
+ public int size() {
+ return size;
+ }
+
+ /** Returns {@code true} if empty. */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * Returns the minimum element without removing it, or {@code null} if empty.
+ * Matches {@link java.util.PriorityQueue#peek()} behavior.
+ */
+ @SuppressWarnings("unchecked")
+ public E peek() {
+ return size == 0 ? null : (E) heap[0];
+ }
+
+ /**
+ * Inserts the specified element (O(log n)).
+ * @throws NullPointerException if {@code e} is null
+ * @throws ClassCastException if {@code cmp == null} and {@code e} is not Comparable,
+ * or if incompatible with other elements
+ */
+ public boolean offer(E e) {
+ Objects.requireNonNull(e, "element is null");
+ if (size >= heap.length) {
+ grow(size + 1);
+ }
+ // Insert at the end and bubble up. siftUp will maintain 'index' for all touched nodes.
+ siftUp(size, e);
+ size++;
+ return true;
+ }
+
+ /**
+ * Removes and returns the minimum element (O(log n)), or {@code null} if empty.
+ */
+ @SuppressWarnings("unchecked")
+ public E poll() {
+ if (size == 0) {
+ return null;
+ }
+ E min = (E) heap[0];
+ removeAt(0); // updates map and heap structure
+ return min;
+ }
+
+ /**
+ * Removes one occurrence of the specified element e (O(log n)) if present.
+ * Uses the index map for O(1) lookup.
+ */
+ public boolean remove(Object o) {
+ Integer i = index.get(o);
+ if (i == null) {
+ return false;
+ }
+ removeAt(i);
+ return true;
+ }
+
+ /** O(1): returns whether the queue currently contains the given element reference. */
+ public boolean contains(Object o) {
+ return index.containsKey(o);
+ }
+
+ /** Clears the heap and the index map. */
+ public void clear() {
+ Arrays.fill(heap, 0, size, null);
+ index.clear();
+ size = 0;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key update API
+ // ------------------------------------------------------------------------------------
+
+ /**
+ * Changes comparator-relevant fields of {@code e} via the provided {@code mutator},
+ * then restores the heap in O(log n) by bubbling in the correct direction.
+ *
+ * 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 mutator) {
+ Integer i = index.get(e);
+ if (i == null) {
+ throw new IllegalArgumentException("Element not in queue");
+ }
+ // Mutate fields used by comparator (do NOT mutate equality/hash if using value-based map)
+ mutator.accept(e);
+ // Try bubbling up; if no movement occurred, bubble down.
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
+ }
+
+ /**
+ * Faster variant if the new key is strictly smaller (higher priority).
+ * Performs a single sift-up (O(log n)).
+ */
+ public void decreaseKey(E e, Consumer mutator) {
+ Integer i = index.get(e);
+ if (i == null) {
+ throw new IllegalArgumentException("Element not in queue");
+ }
+ mutator.accept(e);
+ siftUp(i);
+ }
+
+ /**
+ * Faster variant if the new key is strictly larger (lower priority).
+ * Performs a single sift-down (O(log n)).
+ */
+ public void increaseKey(E e, Consumer mutator) {
+ Integer i = index.get(e);
+ if (i == null) {
+ throw new IllegalArgumentException("Element not in queue");
+ }
+ mutator.accept(e);
+ siftDown(i);
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Internal utilities
+ // ------------------------------------------------------------------------------------
+
+ /** Grows the internal array to accommodate at least {@code minCapacity}. */
+ private void grow(int minCapacity) {
+ int old = heap.length;
+ int pref = (old < 64) ? old + 2 : old + (old >> 1); // +2 if small, else +50%
+ int newCap = Math.max(minCapacity, pref);
+ heap = Arrays.copyOf(heap, newCap);
+ }
+
+ @SuppressWarnings("unchecked")
+ private int compare(E a, E b) {
+ if (cmp != null) {
+ return cmp.compare(a, b);
+ }
+ return ((Comparable super E>) a).compareTo(b);
+ }
+
+ /**
+ * Inserts item {@code x} at position {@code k}, bubbling up while maintaining the heap.
+ * Also maintains the index map for all moved elements.
+ */
+ @SuppressWarnings("unchecked")
+ private void siftUp(int k, E x) {
+ while (k > 0) {
+ int p = (k - 1) >>> 1;
+ E e = (E) heap[p];
+ if (compare(x, e) >= 0) {
+ break;
+ }
+ heap[k] = e;
+ index.put(e, k);
+ k = p;
+ }
+ heap[k] = x;
+ index.put(x, k);
+ }
+
+ /**
+ * Attempts to bubble up the element currently at {@code k}.
+ * @return true if it moved; false otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ private boolean siftUp(int k) {
+ int orig = k;
+ E x = (E) heap[k];
+ while (k > 0) {
+ int p = (k - 1) >>> 1;
+ E e = (E) heap[p];
+ if (compare(x, e) >= 0) {
+ break;
+ }
+ heap[k] = e;
+ index.put(e, k);
+ k = p;
+ }
+ if (k != orig) {
+ heap[k] = x;
+ index.put(x, k);
+ return true;
+ }
+ return false;
+ }
+
+ /** Bubbles down the element currently at {@code k}. */
+ @SuppressWarnings("unchecked")
+ private void siftDown(int k) {
+ int n = size;
+ E x = (E) heap[k];
+ int half = n >>> 1; // loop while k has at least one child
+ while (k < half) {
+ int child = (k << 1) + 1; // assume left is smaller
+ E c = (E) heap[child];
+ int r = child + 1;
+ if (r < n && compare(c, (E) heap[r]) > 0) {
+ child = r;
+ c = (E) heap[child];
+ }
+ if (compare(x, c) <= 0) {
+ break;
+ }
+ heap[k] = c;
+ index.put(c, k);
+ k = child;
+ }
+ heap[k] = x;
+ index.put(x, k);
+ }
+
+ /**
+ * Removes the element at heap index {@code i}, restoring the heap afterwards.
+ * 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/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java
new file mode 100644
index 000000000000..2f9b3b489d56
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java
@@ -0,0 +1,100 @@
+package com.thealgorithms.datastructures.trees;
+
+/**
+ * Leetcode 606: Construct String from Binary Tree:
+ * https://leetcode.com/problems/construct-string-from-binary-tree/
+ *
+ * Utility class to convert a {@link BinaryTree} into its string representation.
+ *
+ * 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.
+ *
+ *
+ * Rules:
+ *
+ * - Each node is represented as {@code (value)}.
+ * - If a node has only a right child, include {@code ()} before the right
+ * child
+ * to indicate the missing left child.
+ * - If a node has no children, it appears as just {@code (value)}.
+ * - The outermost parentheses are removed from the final string.
+ *
+ *
+ * Example:
+ *
+ *
+ * Input tree:
+ * 1
+ * / \
+ * 2 3
+ * \
+ * 4
+ *
+ * Output string:
+ * "1(2()(4))(3)"
+ *
+ *
+ *
+ * This implementation matches the logic from LeetCode problem 606:
+ * Construct String from Binary Tree.
+ *
+ *
+ * @author Muhammad Junaid
+ * @see BinaryTree
+ */
+public class BinaryTreeToString {
+
+ /** String builder used to accumulate the string representation. */
+ private StringBuilder sb;
+
+ /**
+ * Converts a binary tree (given its root node) to its string representation.
+ *
+ * @param root the root node of the binary tree
+ * @return the string representation of the binary tree, or an empty string if
+ * the tree is null
+ */
+ public String tree2str(BinaryTree.Node root) {
+ if (root == null) {
+ return "";
+ }
+
+ sb = new StringBuilder();
+ dfs(root);
+
+ // Remove the leading and trailing parentheses added by the root call
+ return sb.substring(1, sb.length() - 1);
+ }
+
+ /**
+ * Performs a recursive depth-first traversal to build the string.
+ * Each recursive call appends the node value and its children (if any)
+ * enclosed in parentheses.
+ *
+ * @param node the current node being processed
+ */
+ private void dfs(BinaryTree.Node node) {
+ if (node == null) {
+ return;
+ }
+
+ sb.append("(").append(node.data);
+
+ // Recursively build left and right subtrees
+ if (node.left != null) {
+ dfs(node.left);
+ }
+
+ // Handle the special case: right child exists but left child is null
+ if (node.right != null && node.left == null) {
+ sb.append("()");
+ dfs(node.right);
+ } else if (node.right != null) {
+ dfs(node.right);
+ }
+
+ sb.append(")");
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java
new file mode 100644
index 000000000000..0b29dd6f5f5e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java
@@ -0,0 +1,217 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Centroid Decomposition is a divide-and-conquer technique for trees.
+ * It recursively partitions a tree by finding centroids - nodes whose removal
+ * creates balanced subtrees (each with at most N/2 nodes).
+ *
+ *
+ * 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> 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/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
new file mode 100644
index 000000000000..fd8876cecb70
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
@@ -0,0 +1,145 @@
+/*
+ * TheAlgorithms (https://github.com/TheAlgorithms/Java)
+ * Author: Shewale41
+ * This file is licensed under the MIT License.
+ */
+
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Threaded binary tree implementation that supports insertion and
+ * in-order traversal without recursion or stack by using threads.
+ *
+ * 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 inorderTraversal() {
+ List result = new ArrayList<>();
+ Node current = root;
+ if (current == null) {
+ return result;
+ }
+
+ // Move to the leftmost node
+ while (current.left != null && !current.leftIsThread) {
+ current = current.left;
+ }
+
+ while (current != null) {
+ result.add(current.value);
+
+ // If right pointer is a thread, follow it
+ if (current.rightIsThread) {
+ current = current.right;
+ } else {
+ // Move to leftmost node in right subtree
+ current = current.right;
+ while (current != null && !current.leftIsThread && current.left != null) {
+ current = current.left;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Helper: checks whether the tree is empty.
+ *
+ * @return true if tree has no nodes
+ */
+ public boolean isEmpty() {
+ return root == null;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
index 134561766830..0d4c8d501f9f 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
@@ -3,53 +3,76 @@
import java.util.Arrays;
/**
- * A Dynamic Programming based solution for the 0-1 Knapsack problem.
- * This class provides a method, `knapSack`, that calculates the maximum value that can be
- * obtained from a given set of items with weights and values, while not exceeding a
- * given weight capacity.
+ * 0/1 Knapsack Problem - Dynamic Programming solution.
*
- * @see 0-1 Knapsack Problem
+ * This algorithm solves the classic optimization problem where we have n items,
+ * each with a weight and a value. The goal is to maximize the total value
+ * without exceeding the knapsack's weight capacity.
+ *
+ * Time Complexity: O(n * W)
+ * Space Complexity: O(W)
+ *
+ * Example:
+ * values = {60, 100, 120}
+ * weights = {10, 20, 30}
+ * W = 50
+ * Output: 220
+ *
+ * @author Arpita
+ * @see Knapsack Problem
*/
public final class Knapsack {
private Knapsack() {
}
+ /**
+ * Validates the input to ensure correct constraints.
+ */
private static void throwIfInvalidInput(final int weightCapacity, final int[] weights, final int[] values) {
if (weightCapacity < 0) {
throw new IllegalArgumentException("Weight capacity should not be negative.");
}
if (weights == null || values == null || weights.length != values.length) {
- throw new IllegalArgumentException("Input arrays must not be null and must have the same length.");
+ throw new IllegalArgumentException("Weights and values must be non-null and of the same length.");
}
if (Arrays.stream(weights).anyMatch(w -> w <= 0)) {
- throw new IllegalArgumentException("Input array should not contain non-positive weight(s).");
+ throw new IllegalArgumentException("Weights must be positive.");
}
}
/**
- * Solves the 0-1 Knapsack problem using Dynamic Programming.
+ * Solves the 0/1 Knapsack problem using Dynamic Programming (bottom-up approach).
*
* @param weightCapacity The maximum weight capacity of the knapsack.
- * @param weights An array of item weights.
- * @param values An array of item values.
- * @return The maximum value that can be obtained without exceeding the weight capacity.
- * @throws IllegalArgumentException If the input arrays are null or have different lengths.
+ * @param weights The array of item weights.
+ * @param values The array of item values.
+ * @return The maximum total value achievable without exceeding capacity.
*/
- public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) throws IllegalArgumentException {
+ public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) {
throwIfInvalidInput(weightCapacity, weights, values);
- // DP table to store the state of the maximum possible return for a given weight capacity.
int[] dp = new int[weightCapacity + 1];
+ // Fill dp[] array iteratively
for (int i = 0; i < values.length; i++) {
- for (int w = weightCapacity; w > 0; w--) {
- if (weights[i] <= w) {
- dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
- }
+ for (int w = weightCapacity; w >= weights[i]; w--) {
+ dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[weightCapacity];
}
+
+ /*
+ // Example main method for local testing only.
+ public static void main(String[] args) {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int weightCapacity = 50;
+
+ int maxValue = knapSack(weightCapacity, weights, values);
+ System.out.println("Maximum value = " + maxValue); // Output: 220
+ }
+ */
}
diff --git a/src/main/java/com/thealgorithms/graph/GomoryHuTree.java b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java
new file mode 100644
index 000000000000..f8c110f25571
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java
@@ -0,0 +1,144 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
+
+/**
+ * GomoryβHu tree construction for undirected graphs via nβ1 max-flow computations.
+ *
+ * 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 q = new ArrayDeque<>();
+ q.add(source);
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ for (int v = 0; v < residual.length; v++) {
+ if (residual[u][v] > 0 && parent[v] == -1) {
+ parent[v] = u;
+ if (v == sink) {
+ return true;
+ }
+ q.add(v);
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void markReachable(int[][] residual, int source, boolean[] vis) {
+ Arrays.fill(vis, false);
+ Queue q = new ArrayDeque<>();
+ vis[source] = true;
+ q.add(source);
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ for (int v = 0; v < residual.length; v++) {
+ if (!vis[v] && residual[u][v] > 0) {
+ vis[v] = true;
+ q.add(v);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbundantNumber.java b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
new file mode 100644
index 000000000000..804ac4d71477
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an abundant number or excessive number is a positive integer for which
+ * the sum of its proper divisors is greater than the number.
+ * Equivalently, it is a number for which the sum of proper divisors (or aliquot sum) is greater than n.
+ *
+ * The integer 12 is the first abundant number. Its proper divisors are 1, 2, 3, 4 and 6 for a total of 16.
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Abundant_number
+ */
+public final class AbundantNumber {
+
+ private AbundantNumber() {
+ }
+
+ // Function to calculate sum of all divisors including n
+ private static int sumOfDivisors(int n) {
+ int sum = 1 + n; // 1 and n are always divisors
+ for (int i = 2; i <= n / 2; i++) {
+ if (n % i == 0) {
+ sum += i; // adding divisor to sum
+ }
+ }
+ return sum;
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking sum of divisors > 2n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Abundant number, otherwise false
+ */
+ public static boolean isAbundant(int number) {
+ validatePositiveNumber(number);
+
+ return sumOfDivisors(number) > 2 * number;
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking Aliquot Sum > n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Abundant number, otherwise false
+ */
+ public static boolean isAbundantNumber(int number) {
+ validatePositiveNumber(number);
+
+ return AliquotSum.getAliquotSum(number) > number;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java
index a34ad6b01ab5..1eba6666dde3 100644
--- a/src/main/java/com/thealgorithms/maths/Area.java
+++ b/src/main/java/com/thealgorithms/maths/Area.java
@@ -96,7 +96,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh
throw new IllegalArgumentException(POSITIVE_RADIUS);
}
if (height <= 0) {
- throw new IllegalArgumentException(POSITIVE_RADIUS);
+ throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
return 2 * (Math.PI * radius * radius + Math.PI * radius * height);
}
diff --git a/src/main/java/com/thealgorithms/maths/EvilNumber.java b/src/main/java/com/thealgorithms/maths/EvilNumber.java
new file mode 100644
index 000000000000..419133702fd4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/EvilNumber.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an evil number is a non-negative integer that has an even number of 1s in its binary expansion.
+ * Non-negative integers that are not evil are called odious numbers.
+ *
+ * Evil Number Wiki: https://en.wikipedia.org/wiki/Evil_number
+ * Odious Number Wiki: https://en.wikipedia.org/wiki/Odious_number
+ */
+public final class EvilNumber {
+
+ private EvilNumber() {
+ }
+
+ // Function to count number of one bits in a number using bitwise operators
+ private static int countOneBits(int number) {
+ int oneBitCounter = 0;
+ while (number > 0) {
+ oneBitCounter += number & 1; // increment count if last bit is 1
+ number >>= 1; // right shift to next bit
+ }
+ return oneBitCounter;
+ }
+
+ /**
+ * Check either {@code number} is an Evil number or Odious number
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Evil number, otherwise false (in case of of Odious number)
+ */
+ public static boolean isEvilNumber(int number) {
+ if (number < 0) {
+ throw new IllegalArgumentException("Negative numbers are not allowed.");
+ }
+
+ int noOfOneBits = countOneBits(number);
+ return noOfOneBits % 2 == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
new file mode 100644
index 000000000000..4934d4493bf2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.maths;
+
+/**
+ * In mathematics, the extended Euclidean algorithm is an extension to the
+ * Euclidean algorithm, and computes, in addition to the greatest common divisor
+ * (gcd) of integers a and b, also the coefficients of BΓ©zout's identity, which
+ * are integers x and y such that ax + by = gcd(a, b).
+ *
+ *
+ * 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:
+ *
+ * - Index 0: The greatest common divisor (gcd) of a and b.
+ * - Index 1: The value of x in the equation ax + by = gcd(a, b).
+ * - Index 2: The value of y in the equation ax + by = gcd(a, b).
+ *
+ */
+ 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/main/java/com/thealgorithms/maths/LuckyNumber.java b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
new file mode 100644
index 000000000000..70308e1e0edd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, a lucky number is a natural number in a set which is generated by a certain "sieve".
+ * This sieve is similar to the sieve of Eratosthenes that generates the primes,
+ * but it eliminates numbers based on their position in the remaining set,
+ * instead of their value (or position in the initial set of natural numbers).
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Lucky_number
+ */
+public final class LuckyNumber {
+
+ private LuckyNumber() {
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ // Function to check recursively for Lucky Number
+ private static boolean isLuckyRecursiveApproach(int n, int counter) {
+ // Base case: If counter exceeds n, number is lucky
+ if (counter > n) {
+ return true;
+ }
+
+ // If number is eliminated in this step, it's not lucky
+ if (n % counter == 0) {
+ return false;
+ }
+
+ // Calculate new position after removing every counter-th number
+ int newNumber = n - (n / counter);
+
+ // Recursive call for next round
+ return isLuckyRecursiveApproach(newNumber, counter + 1);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using recursive approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLuckyNumber(int number) {
+ validatePositiveNumber(number);
+ int counterStarting = 2;
+ return isLuckyRecursiveApproach(number, counterStarting);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using iterative approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLucky(int number) {
+ validatePositiveNumber(number);
+
+ int counter = 2; // Position starts from 2 (since first elimination happens at 2)
+ int position = number; // The position of the number in the sequence
+
+ while (counter <= position) {
+ if (position % counter == 0) {
+ return false;
+ } // Number is eliminated
+
+ // Update the position of n after removing every counter-th number
+ position = position - (position / counter);
+ counter++;
+ }
+
+ return true; // Survives all eliminations β Lucky Number
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Perimeter.java b/src/main/java/com/thealgorithms/maths/Perimeter.java
index f8aa1876d388..670851eb346b 100644
--- a/src/main/java/com/thealgorithms/maths/Perimeter.java
+++ b/src/main/java/com/thealgorithms/maths/Perimeter.java
@@ -27,7 +27,7 @@ public static float perimeterRegularPolygon(int n, float side) {
* @param side2 for length of side 2
* @param side3 for length of side 3
* @param sides for length of remaining sides
- * @return Perimeter of given trapezoid.
+ * @return Perimeter of given irregular polygon.
*/
public static float perimeterIrregularPolygon(float side1, float side2, float side3, float... sides) {
float perimeter = side1 + side2 + side3;
diff --git a/src/main/java/com/thealgorithms/maths/PowerOfFour.java b/src/main/java/com/thealgorithms/maths/PowerOfFour.java
new file mode 100644
index 000000000000..e5fe6255821b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/PowerOfFour.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.maths;
+
+/**
+ * Utility class for checking if a number is a power of four.
+ * A power of four is a number that can be expressed as 4^n where n is a non-negative integer.
+ * This class provides a method to determine if a given integer is a power of four using bit manipulation.
+ *
+ * @author krishna-medapati (https://github.com/krishna-medapati)
+ */
+public final class PowerOfFour {
+ private PowerOfFour() {
+ }
+
+ /**
+ * Checks if the given integer is a power of four.
+ *
+ * A number is considered a power of four if:
+ * 1. It is greater than zero
+ * 2. It has exactly one '1' bit in its binary representation (power of two)
+ * 3. The '1' bit is at an even position (0, 2, 4, 6, ...)
+ *
+ * The method uses the mask 0x55555555 (binary: 01010101010101010101010101010101)
+ * to check if the set bit is at an even position.
+ *
+ * @param number the integer to check
+ * @return true if the number is a power of four, false otherwise
+ */
+ public static boolean isPowerOfFour(int number) {
+ if (number <= 0) {
+ return false;
+ }
+ boolean isPowerOfTwo = (number & (number - 1)) == 0;
+ boolean hasEvenBitPosition = (number & 0x55555555) != 0;
+ return isPowerOfTwo && hasEvenBitPosition;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java b/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java
index f22d22e8c6af..5a15c4201a15 100644
--- a/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java
+++ b/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java
@@ -1,66 +1,82 @@
package com.thealgorithms.maths;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
/**
- * @brief utility class implementing Sieve of Eratosthenes
+ * Sieve of Eratosthenes Algorithm
+ * An efficient algorithm to find all prime numbers up to a given limit.
+ *
+ * Algorithm:
+ * 1. Create a boolean array of size n+1, initially all true
+ * 2. Mark 0 and 1 as not prime
+ * 3. For each number i from 2 to sqrt(n):
+ * - If i is still marked as prime
+ * - Mark all multiples of i (starting from iΒ²) as not prime
+ * 4. Collect all numbers still marked as prime
+ *
+ * Time Complexity: O(n log log n)
+ * Space Complexity: O(n)
+ *
+ * @author Navadeep0007
+ * @see Sieve of Eratosthenes
*/
public final class SieveOfEratosthenes {
+
private SieveOfEratosthenes() {
+ // Utility class, prevent instantiation
}
- private static void checkInput(int n) {
- if (n <= 0) {
- throw new IllegalArgumentException("n must be positive.");
+ /**
+ * Finds all prime numbers up to n using the Sieve of Eratosthenes algorithm
+ *
+ * @param n the upper limit (inclusive)
+ * @return a list of all prime numbers from 2 to n
+ * @throws IllegalArgumentException if n is negative
+ */
+ public static List findPrimes(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative");
}
- }
- private static Type[] sievePrimesTill(int n) {
- checkInput(n);
- Type[] isPrimeArray = new Type[n + 1];
- Arrays.fill(isPrimeArray, Type.PRIME);
- isPrimeArray[0] = Type.NOT_PRIME;
- isPrimeArray[1] = Type.NOT_PRIME;
+ if (n < 2) {
+ return new ArrayList<>();
+ }
+
+ // Create boolean array, initially all true
+ boolean[] isPrime = new boolean[n + 1];
+ for (int i = 2; i <= n; i++) {
+ isPrime[i] = true;
+ }
- double cap = Math.sqrt(n);
- for (int i = 2; i <= cap; i++) {
- if (isPrimeArray[i] == Type.PRIME) {
- for (int j = 2; i * j <= n; j++) {
- isPrimeArray[i * j] = Type.NOT_PRIME;
+ // Sieve process
+ for (int i = 2; i * i <= n; i++) {
+ if (isPrime[i]) {
+ // Mark all multiples of i as not prime
+ for (int j = i * i; j <= n; j += i) {
+ isPrime[j] = false;
}
}
}
- return isPrimeArray;
- }
-
- private static int countPrimes(Type[] isPrimeArray) {
- return (int) Arrays.stream(isPrimeArray).filter(element -> element == Type.PRIME).count();
- }
- private static int[] extractPrimes(Type[] isPrimeArray) {
- int numberOfPrimes = countPrimes(isPrimeArray);
- int[] primes = new int[numberOfPrimes];
- int primeIndex = 0;
- for (int curNumber = 0; curNumber < isPrimeArray.length; ++curNumber) {
- if (isPrimeArray[curNumber] == Type.PRIME) {
- primes[primeIndex++] = curNumber;
+ // Collect all prime numbers
+ List primes = new ArrayList<>();
+ for (int i = 2; i <= n; i++) {
+ if (isPrime[i]) {
+ primes.add(i);
}
}
+
return primes;
}
/**
- * @brief finds all of the prime numbers up to the given upper (inclusive) limit
- * @param n upper (inclusive) limit
- * @exception IllegalArgumentException n is non-positive
- * @return the array of all primes up to the given number (inclusive)
+ * Counts the number of prime numbers up to n
+ *
+ * @param n the upper limit (inclusive)
+ * @return count of prime numbers from 2 to n
*/
- public static int[] findPrimesTill(int n) {
- return extractPrimes(sievePrimesTill(n));
- }
-
- private enum Type {
- PRIME,
- NOT_PRIME,
+ public static int countPrimes(int n) {
+ return findPrimes(n).size();
}
}
diff --git a/src/main/java/com/thealgorithms/maths/SmithNumber.java b/src/main/java/com/thealgorithms/maths/SmithNumber.java
new file mode 100644
index 000000000000..c06e0023d9bb
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/SmithNumber.java
@@ -0,0 +1,52 @@
+package com.thealgorithms.maths;
+
+import com.thealgorithms.maths.Prime.PrimeCheck;
+
+/**
+ * In number theory, a smith number is a composite number for which, in a given number base,
+ * the sum of its digits is equal to the sum of the digits in its prime factorization in the same base.
+ *
+ * For example, in base 10, 378 = 21 X 33 X 71 is a Smith number since 3 + 7 + 8 = 2βXβ1 + 3βXβ3 + 7βXβ1,
+ * and 22 = 21 X 111 is a Smith number, because 2 + 2 = 2βXβ1 + (1 + 1)βXβ1.
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Smith_number
+ */
+public final class SmithNumber {
+
+ private SmithNumber() {
+ }
+
+ private static int primeFactorDigitSum(int n) {
+ int sum = 0;
+ int num = n;
+
+ // Factorize the number using trial division
+ for (int i = 2; i * i <= num; i++) {
+ while (n % i == 0) { // while i divides n
+ sum += SumOfDigits.sumOfDigits(i); // add sum of digits of factor
+ n /= i; // divide n by the factor
+ }
+ }
+
+ // If n is still > 1, it itself is a prime factor
+ if (n > 1) {
+ sum += SumOfDigits.sumOfDigits(n);
+ }
+
+ return sum;
+ }
+
+ /**
+ * Check if {@code number} is Smith number or not
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Smith number, otherwise false
+ */
+ public static boolean isSmithNumber(int number) {
+ if (PrimeCheck.isPrime(number)) {
+ return false; // Smith numbers must be composite
+ }
+
+ return SumOfDigits.sumOfDigits(number) == primeFactorDigitSum(number);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java b/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java
new file mode 100644
index 000000000000..8b071113f9cc
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java
@@ -0,0 +1,74 @@
+package com.thealgorithms.matrix;
+
+/**
+ * Utility class to check whether a matrix is stochastic.
+ * A matrix is stochastic if all its elements are non-negative
+ * and the sum of each row or column is equal to 1.
+ *Reference: https://en.wikipedia.org/wiki/Stochastic_matrix
+ */
+public final class StochasticMatrix {
+
+ private static final double TOLERANCE = 1e-9;
+
+ private StochasticMatrix() {
+ // Utility class
+ }
+ /**
+ * Checks if a matrix is row-stochastic.
+ *
+ * @param matrix the matrix to check
+ * @return true if the matrix is row-stochastic
+ * @throws IllegalArgumentException if matrix is null or empty
+ */
+ public static boolean isRowStochastic(double[][] matrix) {
+ validateMatrix(matrix);
+
+ for (double[] row : matrix) {
+ double sum = 0.0;
+ for (double value : row) {
+ if (value < 0) {
+ return false;
+ }
+ sum += value;
+ }
+ if (Math.abs(sum - 1.0) > TOLERANCE) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if a matrix is column-stochastic.
+ *
+ * @param matrix the matrix to check
+ * @return true if the matrix is column-stochastic
+ * @throws IllegalArgumentException if matrix is null or empty
+ */
+ public static boolean isColumnStochastic(double[][] matrix) {
+ validateMatrix(matrix);
+
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+
+ for (int j = 0; j < cols; j++) {
+ double sum = 0.0;
+ for (int i = 0; i < rows; i++) {
+ if (matrix[i][j] < 0) {
+ return false;
+ }
+ sum += matrix[i][j];
+ }
+ if (Math.abs(sum - 1.0) > TOLERANCE) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void validateMatrix(double[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
+ throw new IllegalArgumentException("Matrix must not be null or empty");
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/physics/SnellLaw.java b/src/main/java/com/thealgorithms/physics/SnellLaw.java
new file mode 100644
index 000000000000..2736984814fd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/SnellLaw.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.physics;
+
+/**
+ * Calculates refraction angle using Snell's Law:
+ * n1 * sin(theta1) = n2 * sin(theta2)
+ * @see Snell's Law
+ */
+public final class SnellLaw {
+
+ private SnellLaw() {
+ throw new AssertionError("No instances.");
+ }
+
+ /**
+ * Computes the refracted angle (theta2) in radians.
+ *
+ * @param n1 index of refraction of medium 1
+ * @param n2 index of refraction of medium 2
+ * @param theta1 incident angle in radians
+ * @return refracted angle (theta2) in radians
+ * @throws IllegalArgumentException if total internal reflection occurs
+ */
+ public static double refractedAngle(double n1, double n2, double theta1) {
+ double ratio = n1 / n2;
+ double sinTheta2 = ratio * Math.sin(theta1);
+
+ if (Math.abs(sinTheta2) > 1.0) {
+ throw new IllegalArgumentException("Total internal reflection: no refraction possible.");
+ }
+
+ return Math.asin(sinTheta2);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/physics/ThinLens.java b/src/main/java/com/thealgorithms/physics/ThinLens.java
new file mode 100644
index 000000000000..5fb29d8c41e4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/ThinLens.java
@@ -0,0 +1,74 @@
+package com.thealgorithms.physics;
+
+/**
+ * Implements the Thin Lens Formula used in ray optics:
+ *
+ *
+ * 1/f = 1/v + 1/u
+ *
+ *
+ * where:
+ *
+ * - f = focal length
+ * - u = object distance
+ * - v = image distance
+ *
+ *
+ * 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/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java
deleted file mode 100644
index fce665c4de00..000000000000
--- a/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.thealgorithms.puzzlesandgames;
-
-/**
- * A class that provides methods to solve Sudoku puzzles of any n x n size
- * using a backtracking approach, where n must be a perfect square.
- * The algorithm checks for safe number placements in rows, columns,
- * and subgrids (which are sqrt(n) x sqrt(n) in size) and recursively solves the puzzle.
- * Though commonly used for 9x9 grids, it is adaptable to other valid Sudoku dimensions.
- */
-final class Sudoku {
-
- private Sudoku() {
- }
-
- /**
- * Checks if placing a number in a specific position on the Sudoku board is safe.
- * The number is considered safe if it does not violate any of the Sudoku rules:
- * - It should not be present in the same row.
- * - It should not be present in the same column.
- * - It should not be present in the corresponding 3x3 subgrid.
- * - It should not be present in the corresponding subgrid, which is sqrt(n) x sqrt(n) in size (e.g., for a 9x9 grid, the subgrid will be 3x3).
- *
- * @param board The current state of the Sudoku board.
- * @param row The row index where the number is to be placed.
- * @param col The column index where the number is to be placed.
- * @param num The number to be placed on the board.
- * @return True if the placement is safe, otherwise false.
- */
- public static boolean isSafe(int[][] board, int row, int col, int num) {
- // Check the row for duplicates
- for (int d = 0; d < board.length; d++) {
- if (board[row][d] == num) {
- return false;
- }
- }
-
- // Check the column for duplicates
- for (int r = 0; r < board.length; r++) {
- if (board[r][col] == num) {
- return false;
- }
- }
-
- // Check the corresponding 3x3 subgrid for duplicates
- int sqrt = (int) Math.sqrt(board.length);
- int boxRowStart = row - row % sqrt;
- int boxColStart = col - col % sqrt;
-
- for (int r = boxRowStart; r < boxRowStart + sqrt; r++) {
- for (int d = boxColStart; d < boxColStart + sqrt; d++) {
- if (board[r][d] == num) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Solves the Sudoku puzzle using backtracking.
- * The algorithm finds an empty cell and tries placing numbers
- * from 1 to n, where n is the size of the board
- * (for example, from 1 to 9 in a standard 9x9 Sudoku).
- * The algorithm finds an empty cell and tries placing numbers from 1 to 9.
- * The standard version of Sudoku uses numbers from 1 to 9, so the algorithm can be
- * easily modified for other variations of the game.
- * If a number placement is valid (checked via `isSafe`), the number is
- * placed and the function recursively attempts to solve the rest of the puzzle.
- * If no solution is possible, the number is removed (backtracked),
- * and the process is repeated.
- *
- * @param board The current state of the Sudoku board.
- * @param n The size of the Sudoku board (typically 9 for a standard puzzle).
- * @return True if the Sudoku puzzle is solvable, false otherwise.
- */
- public static boolean solveSudoku(int[][] board, int n) {
- int row = -1;
- int col = -1;
- boolean isEmpty = true;
-
- // Find the next empty cell
- for (int i = 0; i < n; i++) {
- for (int j = 0; j < n; j++) {
- if (board[i][j] == 0) {
- row = i;
- col = j;
- isEmpty = false;
- break;
- }
- }
- if (!isEmpty) {
- break;
- }
- }
-
- // No empty space left
- if (isEmpty) {
- return true;
- }
-
- // Try placing numbers 1 to n in the empty cell (n should be a perfect square)
- // Eg: n=9 for a standard 9x9 Sudoku puzzle, n=16 for a 16x16 puzzle, etc.
- for (int num = 1; num <= n; num++) {
- if (isSafe(board, row, col, num)) {
- board[row][col] = num;
- if (solveSudoku(board, n)) {
- return true;
- } else {
- // replace it
- board[row][col] = 0;
- }
- }
- }
- return false;
- }
-
- /**
- * Prints the current state of the Sudoku board in a readable format.
- * Each row is printed on a new line, with numbers separated by spaces.
- *
- * @param board The current state of the Sudoku board.
- * @param n The size of the Sudoku board (typically 9 for a standard puzzle).
- */
- public static void print(int[][] board, int n) {
- // Print the board in a nxn grid format
- // if n=9, print the board in a 9x9 grid format
- // if n=16, print the board in a 16x16 grid format
- for (int r = 0; r < n; r++) {
- for (int d = 0; d < n; d++) {
- System.out.print(board[r][d]);
- System.out.print(" ");
- }
- System.out.print("\n");
-
- if ((r + 1) % (int) Math.sqrt(n) == 0) {
- System.out.print("");
- }
- }
- }
-
- /**
- * The driver method to demonstrate solving a Sudoku puzzle.
- * A sample 9x9 Sudoku puzzle is provided, and the program attempts to solve it
- * using the `solveSudoku` method. If a solution is found, it is printed to the console.
- *
- * @param args Command-line arguments (not used in this program).
- */
- public static void main(String[] args) {
- int[][] board = new int[][] {
- {3, 0, 6, 5, 0, 8, 4, 0, 0},
- {5, 2, 0, 0, 0, 0, 0, 0, 0},
- {0, 8, 7, 0, 0, 0, 0, 3, 1},
- {0, 0, 3, 0, 1, 0, 0, 8, 0},
- {9, 0, 0, 8, 6, 3, 0, 0, 5},
- {0, 5, 0, 0, 9, 0, 6, 0, 0},
- {1, 3, 0, 0, 0, 0, 2, 5, 0},
- {0, 0, 0, 0, 0, 0, 0, 7, 4},
- {0, 0, 5, 2, 0, 6, 3, 0, 0},
- };
- int n = board.length;
-
- if (solveSudoku(board, n)) {
- print(board, n);
- } else {
- System.out.println("No solution");
- }
- }
-}
diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
index 05d7abbbcd6c..06101295e880 100644
--- a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
+++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
@@ -64,13 +64,21 @@ private static double doApproximate(Function fx, double a, doubl
if (!validate(fx, a, b, n)) {
throw new IllegalArgumentException("Invalid input parameters");
}
- double totalArea = 0.0;
+ double total = 0.0;
double interval = b - a;
- for (int i = 0; i < n; i++) {
+ int pairs = n / 2;
+ for (int i = 0; i < pairs; i++) {
+ double u = generator.nextDouble();
+ double x1 = a + u * interval;
+ double x2 = a + (1.0 - u) * interval;
+ total += fx.apply(x1);
+ total += fx.apply(x2);
+ }
+ if ((n & 1) == 1) {
double x = a + generator.nextDouble() * interval;
- totalArea += fx.apply(x);
+ total += fx.apply(x);
}
- return interval * totalArea / n;
+ return interval * total / n;
}
private static boolean validate(Function fx, double a, double b, int n) {
diff --git a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
similarity index 92%
rename from src/main/java/com/thealgorithms/maths/FactorialRecursion.java
rename to src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
index d9bafd1e39e9..673f216bdc9a 100644
--- a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java
+++ b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.recursion;
public final class FactorialRecursion {
private FactorialRecursion() {
diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
index e5f474085367..9bc6da2f7443 100644
--- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
+++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
@@ -12,10 +12,12 @@ private FibonacciSeries() {
throw new UnsupportedOperationException("Utility class");
}
public static int fibonacci(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be a non-negative integer");
+ }
if (n <= 1) {
return n;
- } else {
- return fibonacci(n - 1) + fibonacci(n - 2);
}
+ return fibonacci(n - 1) + fibonacci(n - 2);
}
}
diff --git a/src/main/java/com/thealgorithms/sorts/BubbleSort.java b/src/main/java/com/thealgorithms/sorts/BubbleSort.java
index 6823c68d0a74..d2eca3506c2d 100644
--- a/src/main/java/com/thealgorithms/sorts/BubbleSort.java
+++ b/src/main/java/com/thealgorithms/sorts/BubbleSort.java
@@ -10,6 +10,13 @@ class BubbleSort implements SortAlgorithm {
/**
* Implements generic bubble sort algorithm.
*
+ * Time Complexity:
+ * - Best case: O(n) β array is already sorted.
+ * - Average case: O(n^2)
+ * - Worst case: O(n^2)
+ *
+ * Space Complexity: O(1) β in-place sorting.
+ *
* @param array the array to be sorted.
* @param the type of elements in the array.
* @return the sorted array.
diff --git a/src/main/java/com/thealgorithms/sorts/HeapSort.java b/src/main/java/com/thealgorithms/sorts/HeapSort.java
index e798fb91b925..5e3b20f43e10 100644
--- a/src/main/java/com/thealgorithms/sorts/HeapSort.java
+++ b/src/main/java/com/thealgorithms/sorts/HeapSort.java
@@ -1,9 +1,20 @@
package com.thealgorithms.sorts;
/**
- * Heap Sort Algorithm Implementation
+ * Heap Sort algorithm implementation.
+ *
+ * Heap sort converts the array into a max-heap and repeatedly extracts the maximum
+ * element to sort the array in increasing order.
+ *
+ * Time Complexity:
+ * - Best case: O(n log n)
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ * Space Complexity: O(1) β in-place sorting
*
* @see Heap Sort Algorithm
+ * @see SortAlgorithm
*/
public class HeapSort implements SortAlgorithm {
diff --git a/src/main/java/com/thealgorithms/sorts/InsertionSort.java b/src/main/java/com/thealgorithms/sorts/InsertionSort.java
index 21ebf3827b5f..fdbfd9cd1cfa 100644
--- a/src/main/java/com/thealgorithms/sorts/InsertionSort.java
+++ b/src/main/java/com/thealgorithms/sorts/InsertionSort.java
@@ -1,5 +1,23 @@
package com.thealgorithms.sorts;
+/**
+ * Generic Insertion Sort algorithm.
+ *
+ * Standard insertion sort iterates through the array and inserts each element into its
+ * correct position in the sorted portion of the array.
+ *
+ * Sentinel sort is a variation that first places the minimum element at index 0 to
+ * avoid redundant comparisons in subsequent passes.
+ *
+ * Time Complexity:
+ * - Best case: O(n) β array is already sorted (sentinel sort can improve slightly)
+ * - Average case: O(n^2)
+ * - Worst case: O(n^2) β array is reverse sorted
+ *
+ * Space Complexity: O(1) β in-place sorting
+ *
+ * @see SortAlgorithm
+ */
class InsertionSort implements SortAlgorithm {
/**
diff --git a/src/main/java/com/thealgorithms/sorts/MergeSort.java b/src/main/java/com/thealgorithms/sorts/MergeSort.java
index 86a184f67b26..f7a7c8da004d 100644
--- a/src/main/java/com/thealgorithms/sorts/MergeSort.java
+++ b/src/main/java/com/thealgorithms/sorts/MergeSort.java
@@ -13,11 +13,16 @@ class MergeSort implements SortAlgorithm {
private Comparable[] aux;
/**
- * Generic merge sort algorithm implements.
+ * Generic merge sort algorithm.
*
- * @param unsorted the array which should be sorted.
- * @param Comparable class.
- * @return sorted array.
+ * Time Complexity:
+ * - Best case: O(n log n)
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ * Space Complexity: O(n) β requires auxiliary array for merging.
+ *
+ * @see SortAlgorithm
*/
@Override
public > T[] sort(T[] unsorted) {
diff --git a/src/main/java/com/thealgorithms/sorts/QuickSort.java b/src/main/java/com/thealgorithms/sorts/QuickSort.java
index 3abb1aae2306..b0ca8b9f159d 100644
--- a/src/main/java/com/thealgorithms/sorts/QuickSort.java
+++ b/src/main/java/com/thealgorithms/sorts/QuickSort.java
@@ -1,17 +1,36 @@
package com.thealgorithms.sorts;
/**
- * @author Varun Upadhyay (https://github.com/varunu28)
- * @author Podshivalov Nikita (https://github.com/nikitap492)
+ * QuickSort is a divide-and-conquer sorting algorithm.
+ *
+ * The algorithm selects a pivot element and partitions the array into two
+ * subarrays such that:
+ *
+ * - Elements smaller than the pivot are placed on the left
+ * - Elements greater than the pivot are placed on the right
+ *
+ *
+ * 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:
+ *
+ * - Best Case: O(n log n)
+ * - Average Case: O(n log n)
+ * - Worst Case: O(n^2)
+ *
+ *
+ * Space Complexity: O(log n) due to recursion stack (in-place sorting).
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see SortAlgorithm
*/
+
class QuickSort implements SortAlgorithm {
- /**
- * This method implements the Generic Quick Sort
- *
- * @param array The array to be sorted Sorts the array in increasing order
- */
@Override
public > T[] sort(T[] array) {
doSort(array, 0, array.length - 1);
@@ -21,27 +40,33 @@ public > T[] sort(T[] array) {
/**
* The sorting process
*
- * @param left The first index of an array
- * @param right The last index of an array
* @param array The array to be sorted
+ * @param left The first index of an array
+ * @param right The last index of an array
*/
private static > void doSort(T[] array, final int left, final int right) {
+ // Continue sorting only if the subarray has more than one element
if (left < right) {
+ // Randomly choose a pivot and partition the array
final int pivot = randomPartition(array, left, right);
+ // Recursively sort elements before the pivot
doSort(array, left, pivot - 1);
+ // Recursively sort elements after the pivot
doSort(array, pivot, right);
}
}
/**
- * Randomize the array to avoid the basically ordered sequences
+ * Randomizes the array to avoid already ordered or nearly ordered sequences
*
* @param array The array to be sorted
- * @param left The first index of an array
+ * @param left The first index of an array
* @param right The last index of an array
* @return the partition index of the array
*/
private static > int randomPartition(T[] array, final int left, final int right) {
+ // Randomizing the pivot helps avoid worst-case performance
+ // for already sorted or nearly sorted arrays
final int randomIndex = left + (int) (Math.random() * (right - left + 1));
SortUtils.swap(array, randomIndex, right);
return partition(array, left, right);
@@ -51,21 +76,26 @@ private static > int randomPartition(T[] array, final in
* This method finds the partition index for an array
*
* @param array The array to be sorted
- * @param left The first index of an array
- * @param right The last index of an array Finds the partition index of an
- * array
+ * @param left The first index of an array
+ * @param right The last index of an array
*/
private static > int partition(T[] array, int left, int right) {
final int mid = (left + right) >>> 1;
+ // Choose the middle element as the pivot
final T pivot = array[mid];
-
+ // Move the left and right pointers towards each other
while (left <= right) {
+ // Move left pointer until an element >= pivot is found
while (SortUtils.less(array[left], pivot)) {
++left;
}
+
+ // Move right pointer until an element <= pivot is found
while (SortUtils.less(pivot, array[right])) {
--right;
}
+
+ // Swap elements that are on the wrong side of the pivot
if (left <= right) {
SortUtils.swap(array, left, right);
++left;
diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java
index db7732d7e218..2d1814441701 100644
--- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java
+++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java
@@ -2,11 +2,16 @@
public class SelectionSort implements SortAlgorithm {
/**
- * Sorts an array of comparable elements in increasing order using the selection sort algorithm.
+ * Generic Selection Sort algorithm.
*
- * @param array the array to be sorted
- * @param the class of array elements
- * @return the sorted array
+ * Time Complexity:
+ * - Best case: O(n^2)
+ * - Average case: O(n^2)
+ * - Worst case: O(n^2)
+ *
+ * Space Complexity: O(1) β in-place sorting.
+ *
+ * @see SortAlgorithm
*/
@Override
public > T[] sort(T[] array) {
diff --git a/src/main/java/com/thealgorithms/sorts/TopologicalSort.java b/src/main/java/com/thealgorithms/sorts/TopologicalSort.java
index e4ed240a9947..382ddde9a6f2 100644
--- a/src/main/java/com/thealgorithms/sorts/TopologicalSort.java
+++ b/src/main/java/com/thealgorithms/sorts/TopologicalSort.java
@@ -11,9 +11,16 @@
* a linked list. A Directed Graph is proven to be acyclic when a DFS or Depth First Search is
* performed, yielding no back-edges.
*
- * https://en.wikipedia.org/wiki/Topological_sorting
+ * Time Complexity: O(V + E)
+ * - V: number of vertices
+ * - E: number of edges
*
- * @author Jonathan Taylor (https://github.com/Jtmonument)
+ * Space Complexity: O(V + E)
+ * - adjacency list and recursion stack in DFS
+ *
+ * Reference: https://en.wikipedia.org/wiki/Topological_sorting
+ *
+ * Author: Jonathan Taylor (https://github.com/Jtmonument)
* Based on Introduction to Algorithms 3rd Edition
*/
public final class TopologicalSort {
diff --git a/src/main/java/com/thealgorithms/stacks/ValidParentheses.java b/src/main/java/com/thealgorithms/stacks/ValidParentheses.java
new file mode 100644
index 000000000000..2cc616a38826
--- /dev/null
+++ b/src/main/java/com/thealgorithms/stacks/ValidParentheses.java
@@ -0,0 +1,74 @@
+package com.thealgorithms.stacks;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Valid Parentheses Problem
+ *
+ * Given a string containing just the characters '(', ')', '{', '}', '[' and ']',
+ * determine if the input string is valid.
+ *
+ * An input string is valid if:
+ * 1. Open brackets must be closed by the same type of brackets.
+ * 2. Open brackets must be closed in the correct order.
+ * 3. Every close bracket has a corresponding open bracket of the same type.
+ *
+ * Examples:
+ * Input: "()"
+ * Output: true
+ *
+ * Input: "()[]{}"
+ * Output: true
+ *
+ * Input: "(]"
+ * Output: false
+ *
+ * Input: "([)]"
+ * Output: false
+ *
+ * @author Gokul45-45
+ */
+public final class ValidParentheses {
+ private ValidParentheses() {
+ }
+
+ /**
+ * Checks if the given string has valid parentheses
+ *
+ * @param s the input string containing parentheses
+ * @return true if valid, false otherwise
+ */
+ public static boolean isValid(String s) {
+ if (s == null || s.length() % 2 != 0) {
+ return false;
+ }
+
+ Map parenthesesMap = new HashMap<>();
+ parenthesesMap.put('(', ')');
+ parenthesesMap.put('{', '}');
+ parenthesesMap.put('[', ']');
+
+ Stack stack = new Stack<>();
+
+ for (char c : s.toCharArray()) {
+ if (parenthesesMap.containsKey(c)) {
+ // Opening bracket - push to stack
+ stack.push(c);
+ } else {
+ // Closing bracket - check if it matches
+ if (stack.isEmpty()) {
+ return false;
+ }
+ char openBracket = stack.pop();
+ if (parenthesesMap.get(openBracket) != c) {
+ return false;
+ }
+ }
+ }
+
+ // Stack should be empty if all brackets are matched
+ return stack.isEmpty();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java b/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java
new file mode 100644
index 000000000000..7eed59a5ef99
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.strings;
+
+/**
+ * The {@code LengthOfLastWord} class provides a utility method to determine
+ * the length of the last word in a given string.
+ *
+ * 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:
+ *
{@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
+ * }
+ *
+ * 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/main/java/com/thealgorithms/strings/Upper.java b/src/main/java/com/thealgorithms/strings/Upper.java
index 5e248cb6ee39..85db7d41e1aa 100644
--- a/src/main/java/com/thealgorithms/strings/Upper.java
+++ b/src/main/java/com/thealgorithms/strings/Upper.java
@@ -15,23 +15,27 @@ public static void main(String[] args) {
}
/**
- * Converts all the characters in this {@code String} to upper case
+ * Converts all the characters in this {@code String} to upper case.
*
* @param s the string to convert
* @return the {@code String}, converted to uppercase.
*/
public static String toUpperCase(String s) {
if (s == null) {
- throw new IllegalArgumentException("Input string connot be null");
+ throw new IllegalArgumentException("Input string cannot be null");
}
if (s.isEmpty()) {
return s;
}
- StringBuilder result = new StringBuilder(s);
- for (int i = 0; i < result.length(); ++i) {
- char currentChar = result.charAt(i);
- if (Character.isLetter(currentChar) && Character.isLowerCase(currentChar)) {
- result.setCharAt(i, Character.toUpperCase(currentChar));
+
+ StringBuilder result = new StringBuilder(s.length());
+
+ for (int i = 0; i < s.length(); ++i) {
+ char currentChar = s.charAt(i);
+ if (Character.isLowerCase(currentChar)) {
+ result.append(Character.toUpperCase(currentChar));
+ } else {
+ result.append(currentChar);
}
}
return result.toString();
diff --git a/src/main/java/com/thealgorithms/strings/ZAlgorithm.java b/src/main/java/com/thealgorithms/strings/ZAlgorithm.java
new file mode 100644
index 000000000000..dc029b751f45
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/ZAlgorithm.java
@@ -0,0 +1,48 @@
+/*
+ * https://en.wikipedia.org/wiki/Z-algorithm
+ */
+package com.thealgorithms.strings;
+
+public final class ZAlgorithm {
+
+ private ZAlgorithm() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ public static int[] zFunction(String s) {
+ int n = s.length();
+ int[] z = new int[n];
+ int l = 0;
+ int r = 0;
+
+ for (int i = 1; i < n; i++) {
+ if (i <= r) {
+ z[i] = Math.min(r - i + 1, z[i - l]);
+ }
+
+ while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) {
+ z[i]++;
+ }
+
+ if (i + z[i] - 1 > r) {
+ l = i;
+ r = i + z[i] - 1;
+ }
+ }
+
+ return z;
+ }
+
+ public static int search(String text, String pattern) {
+ String s = pattern + "$" + text;
+ int[] z = zFunction(s);
+ int p = pattern.length();
+
+ for (int i = 0; i < z.length; i++) {
+ if (z[i] == p) {
+ return i - p - 1;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java
new file mode 100644
index 000000000000..75d3eae08629
--- /dev/null
+++ b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.backtracking;
+
+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;
+
+class SudokuSolverTest {
+
+ @Test
+ void testSolveSudokuEasyPuzzle() {
+ int[][] board = {{5, 3, 0, 0, 7, 0, 0, 0, 0}, {6, 0, 0, 1, 9, 5, 0, 0, 0}, {0, 9, 8, 0, 0, 0, 0, 6, 0}, {8, 0, 0, 0, 6, 0, 0, 0, 3}, {4, 0, 0, 8, 0, 3, 0, 0, 1}, {7, 0, 0, 0, 2, 0, 0, 0, 6}, {0, 6, 0, 0, 0, 0, 2, 8, 0}, {0, 0, 0, 4, 1, 9, 0, 0, 5}, {0, 0, 0, 0, 8, 0, 0, 7, 9}};
+
+ assertTrue(SudokuSolver.solveSudoku(board));
+
+ int[][] expected = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}};
+
+ assertArrayEquals(expected, board);
+ }
+
+ @Test
+ void testSolveSudokuHardPuzzle() {
+ int[][] board = {{0, 0, 0, 0, 0, 0, 6, 8, 0}, {0, 0, 0, 0, 7, 3, 0, 0, 9}, {3, 0, 9, 0, 0, 0, 0, 4, 5}, {4, 9, 0, 0, 0, 0, 0, 0, 0}, {8, 0, 3, 0, 5, 0, 9, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 3, 6}, {9, 6, 0, 0, 0, 0, 3, 0, 8}, {7, 0, 0, 6, 8, 0, 0, 0, 0}, {0, 2, 8, 0, 0, 0, 0, 0, 0}};
+
+ assertTrue(SudokuSolver.solveSudoku(board));
+ }
+
+ @Test
+ void testSolveSudokuAlreadySolved() {
+ int[][] board = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}};
+
+ assertTrue(SudokuSolver.solveSudoku(board));
+ }
+
+ @Test
+ void testSolveSudokuInvalidSize() {
+ int[][] board = {{1, 2, 3}, {4, 5, 6}};
+ assertFalse(SudokuSolver.solveSudoku(board));
+ }
+
+ @Test
+ void testSolveSudokuNullBoard() {
+ assertFalse(SudokuSolver.solveSudoku(null));
+ }
+
+ @Test
+ void testSolveSudokuEmptyBoard() {
+ int[][] board = {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}};
+
+ assertTrue(SudokuSolver.solveSudoku(board));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java b/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java
new file mode 100644
index 000000000000..c8e7cd0af0dd
--- /dev/null
+++ b/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java
@@ -0,0 +1,31 @@
+package com.thealgorithms.backtracking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+public class UniquePermutationTest {
+
+ @Test
+ void testUniquePermutationsAab() {
+ List expected = Arrays.asList("AAB", "ABA", "BAA");
+ List result = UniquePermutation.generateUniquePermutations("AAB");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void testUniquePermutationsAbc() {
+ List expected = Arrays.asList("ABC", "ACB", "BAC", "BCA", "CAB", "CBA");
+ List result = UniquePermutation.generateUniquePermutations("ABC");
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void testEmptyString() {
+ List expected = Arrays.asList("");
+ List result = UniquePermutation.generateUniquePermutations("");
+ assertEquals(expected, result);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
index 61e0757f9c12..757c6edc0151 100644
--- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
+++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
@@ -1,26 +1,56 @@
package com.thealgorithms.bitmanipulation;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
-public class CountSetBitsTest {
+class CountSetBitsTest {
@Test
- void testSetBits() {
- CountSetBits csb = new CountSetBits();
- assertEquals(1L, csb.countSetBits(16));
- assertEquals(4, csb.countSetBits(15));
- assertEquals(5, csb.countSetBits(10000));
- assertEquals(5, csb.countSetBits(31));
+ void testCountSetBitsZero() {
+ assertEquals(0, CountSetBits.countSetBits(0));
}
@Test
- void testSetBitsLookupApproach() {
- CountSetBits csb = new CountSetBits();
- assertEquals(1L, csb.lookupApproach(16));
- assertEquals(4, csb.lookupApproach(15));
- assertEquals(5, csb.lookupApproach(10000));
- assertEquals(5, csb.lookupApproach(31));
+ void testCountSetBitsOne() {
+ assertEquals(1, CountSetBits.countSetBits(1));
+ }
+
+ @Test
+ void testCountSetBitsSmallNumber() {
+ assertEquals(4, CountSetBits.countSetBits(3)); // 1(1) + 10(1) + 11(2) = 4
+ }
+
+ @Test
+ void testCountSetBitsFive() {
+ assertEquals(7, CountSetBits.countSetBits(5)); // 1 + 1 + 2 + 1 + 2 = 7
+ }
+
+ @Test
+ void testCountSetBitsTen() {
+ assertEquals(17, CountSetBits.countSetBits(10));
+ }
+
+ @Test
+ void testCountSetBitsLargeNumber() {
+ assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42
+ }
+
+ @Test
+ void testCountSetBitsPowerOfTwo() {
+ assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13
+ }
+
+ @Test
+ void testCountSetBitsNegativeInput() {
+ assertThrows(IllegalArgumentException.class, () -> CountSetBits.countSetBits(-1));
+ }
+
+ @Test
+ void testNaiveApproachMatchesOptimized() {
+ for (int i = 0; i <= 100; i++) {
+ assertEquals(CountSetBits.countSetBitsNaive(i), CountSetBits.countSetBits(i), "Mismatch at n = " + i);
+ }
}
}
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));
+ }
+}
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);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java
new file mode 100644
index 000000000000..c824241c680d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java
@@ -0,0 +1,158 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the BellmanFord algorithm implementation.
+ * Tests cover various graph scenarios including:
+ * - Simple weighted graphs
+ * - Graphs with negative weights
+ * - Single vertex graphs
+ * - Disconnected graphs
+ * - Linear path graphs
+ */
+class BellmanFordTest {
+
+ @Test
+ void testSimpleGraph() {
+ // Create a simple graph with 5 vertices and 8 edges
+ // Graph visualization:
+ // 1
+ // /|\
+ // 6 | 7
+ // / | \
+ // 0 5 2
+ // \ | /
+ // 8 | -2
+ // \|/
+ // 4---3
+ // 9
+ BellmanFord bellmanFord = new BellmanFord(5, 8);
+ bellmanFord.addEdge(0, 1, 6);
+ bellmanFord.addEdge(0, 4, 8);
+ bellmanFord.addEdge(1, 2, 7);
+ bellmanFord.addEdge(1, 4, 5);
+ bellmanFord.addEdge(2, 3, -2);
+ bellmanFord.addEdge(2, 4, -3);
+ bellmanFord.addEdge(3, 4, 9);
+ bellmanFord.addEdge(4, 3, 7);
+
+ // Verify edge array creation
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(8, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testGraphWithNegativeWeights() {
+ // Graph with negative edge weights (but no negative cycle)
+ BellmanFord bellmanFord = new BellmanFord(4, 5);
+ bellmanFord.addEdge(0, 1, 4);
+ bellmanFord.addEdge(0, 2, 5);
+ bellmanFord.addEdge(1, 2, -3);
+ bellmanFord.addEdge(2, 3, 4);
+ bellmanFord.addEdge(1, 3, 6);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(5, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testSingleVertexGraph() {
+ // Graph with single vertex and no edges
+ BellmanFord bellmanFord = new BellmanFord(1, 0);
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(0, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testLinearGraph() {
+ // Linear graph: 0 -> 1 -> 2 -> 3
+ BellmanFord bellmanFord = new BellmanFord(4, 3);
+ bellmanFord.addEdge(0, 1, 2);
+ bellmanFord.addEdge(1, 2, 3);
+ bellmanFord.addEdge(2, 3, 4);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(3, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testEdgeAddition() {
+ BellmanFord bellmanFord = new BellmanFord(3, 3);
+
+ bellmanFord.addEdge(0, 1, 5);
+ bellmanFord.addEdge(1, 2, 3);
+ bellmanFord.addEdge(0, 2, 10);
+
+ // Verify all edges were added
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(3, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testGraphWithZeroWeightEdges() {
+ // Graph with zero weight edges
+ BellmanFord bellmanFord = new BellmanFord(3, 3);
+ bellmanFord.addEdge(0, 1, 0);
+ bellmanFord.addEdge(1, 2, 0);
+ bellmanFord.addEdge(0, 2, 1);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(3, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testLargerGraph() {
+ // Larger graph with 6 vertices
+ BellmanFord bellmanFord = new BellmanFord(6, 9);
+ bellmanFord.addEdge(0, 1, 5);
+ bellmanFord.addEdge(0, 2, 3);
+ bellmanFord.addEdge(1, 3, 6);
+ bellmanFord.addEdge(1, 2, 2);
+ bellmanFord.addEdge(2, 4, 4);
+ bellmanFord.addEdge(2, 5, 2);
+ bellmanFord.addEdge(2, 3, 7);
+ bellmanFord.addEdge(3, 4, -1);
+ bellmanFord.addEdge(4, 5, -2);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(9, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testVertexAndEdgeCount() {
+ BellmanFord bellmanFord = new BellmanFord(10, 15);
+ assertEquals(10, bellmanFord.vertex);
+ assertEquals(15, bellmanFord.edge);
+ }
+
+ @Test
+ void testMultipleEdgesBetweenSameVertices() {
+ // Graph allowing multiple edges between same vertices
+ BellmanFord bellmanFord = new BellmanFord(2, 3);
+ bellmanFord.addEdge(0, 1, 5);
+ bellmanFord.addEdge(0, 1, 3);
+ bellmanFord.addEdge(1, 0, 2);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(3, bellmanFord.getEdgeArray().length);
+ }
+
+ @Test
+ void testCompleteGraph() {
+ // Complete graph with 4 vertices (6 edges for undirected equivalent)
+ BellmanFord bellmanFord = new BellmanFord(4, 6);
+ bellmanFord.addEdge(0, 1, 1);
+ bellmanFord.addEdge(0, 2, 2);
+ bellmanFord.addEdge(0, 3, 3);
+ bellmanFord.addEdge(1, 2, 4);
+ bellmanFord.addEdge(1, 3, 5);
+ bellmanFord.addEdge(2, 3, 6);
+
+ assertNotNull(bellmanFord.getEdgeArray());
+ assertEquals(6, bellmanFord.getEdgeArray().length);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java
new file mode 100644
index 000000000000..b5cfdd9de04f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java
@@ -0,0 +1,204 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the Graph class in ConnectedComponent.java.
+ * Tests the depth-first search implementation and connected component counting.
+ * Covers various graph topologies including:
+ * - Single connected components
+ * - Multiple disconnected components
+ * - Self-loops
+ * - Linear chains
+ * - Cyclic graphs
+ */
+class ConnectedComponentTest {
+
+ @Test
+ void testSingleConnectedComponent() {
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 4);
+ graph.addEdge(4, 1);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testTwoDisconnectedComponents() {
+ Graph graph = new Graph<>();
+ // Component 1: 1-2-3
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ // Component 2: 4-5
+ graph.addEdge(4, 5);
+
+ assertEquals(2, graph.countGraphs());
+ }
+
+ @Test
+ void testThreeDisconnectedComponents() {
+ Graph graph = new Graph<>();
+ // Component 1: a-b-c-d-e
+ graph.addEdge('a', 'b');
+ graph.addEdge('a', 'e');
+ graph.addEdge('b', 'e');
+ graph.addEdge('b', 'c');
+ graph.addEdge('c', 'd');
+ graph.addEdge('d', 'a');
+ // Component 2: x-y-z
+ graph.addEdge('x', 'y');
+ graph.addEdge('x', 'z');
+ // Component 3: w (self-loop)
+ graph.addEdge('w', 'w');
+
+ assertEquals(3, graph.countGraphs());
+ }
+
+ @Test
+ void testSingleNodeSelfLoop() {
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 1);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testLinearChain() {
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 4);
+ graph.addEdge(4, 5);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testStarTopology() {
+ // Star graph with center node 0 connected to nodes 1, 2, 3, 4
+ Graph graph = new Graph<>();
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(0, 3);
+ graph.addEdge(0, 4);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testCompleteGraph() {
+ // Complete graph K4: every node connected to every other node
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 2);
+ graph.addEdge(1, 3);
+ graph.addEdge(1, 4);
+ graph.addEdge(2, 3);
+ graph.addEdge(2, 4);
+ graph.addEdge(3, 4);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testStringVertices() {
+ Graph graph = new Graph<>();
+ // Component 1
+ graph.addEdge("New York", "Los Angeles");
+ graph.addEdge("Los Angeles", "Chicago");
+ // Component 2
+ graph.addEdge("London", "Paris");
+ // Component 3
+ graph.addEdge("Tokyo", "Tokyo");
+
+ assertEquals(3, graph.countGraphs());
+ }
+
+ @Test
+ void testEmptyGraph() {
+ Graph graph = new Graph<>();
+ assertEquals(0, graph.countGraphs());
+ }
+
+ @Test
+ void testDepthFirstSearchBasic() {
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+
+ // Get the first node and perform DFS
+ assertNotNull(graph.nodeList);
+ assertEquals(3, graph.nodeList.size());
+ }
+
+ @Test
+ void testManyIsolatedComponents() {
+ Graph graph = new Graph<>();
+ // Create 5 isolated components (each is a self-loop)
+ graph.addEdge(1, 1);
+ graph.addEdge(2, 2);
+ graph.addEdge(3, 3);
+ graph.addEdge(4, 4);
+ graph.addEdge(5, 5);
+
+ assertEquals(5, graph.countGraphs());
+ }
+
+ @Test
+ void testBidirectionalEdges() {
+ Graph graph = new Graph<>();
+ // Note: This is a directed graph representation
+ // Adding edge 1->2 does not automatically add 2->1
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 1);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 2);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testCyclicGraph() {
+ Graph graph = new Graph<>();
+ // Create a cycle: 1 -> 2 -> 3 -> 4 -> 1
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 4);
+ graph.addEdge(4, 1);
+
+ assertEquals(1, graph.countGraphs());
+ }
+
+ @Test
+ void testMultipleCycles() {
+ Graph graph = new Graph<>();
+ // Cycle 1: 1 -> 2 -> 3 -> 1
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 1);
+ // Cycle 2: 4 -> 5 -> 4
+ graph.addEdge(4, 5);
+ graph.addEdge(5, 4);
+
+ assertEquals(2, graph.countGraphs());
+ }
+
+ @Test
+ void testIntegerGraphFromMainExample() {
+ // Recreate the example from main method
+ Graph graph = new Graph<>();
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(2, 4);
+ graph.addEdge(3, 5);
+ graph.addEdge(7, 8);
+ graph.addEdge(8, 10);
+ graph.addEdge(10, 8);
+
+ assertEquals(2, graph.countGraphs());
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java
index c5df9acdf33b..a189091c17d3 100644
--- a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.datastructures.graphs;
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 org.junit.jupiter.api.BeforeEach;
@@ -61,4 +62,120 @@ void testInvalidSourceVertex() {
assertThrows(IllegalArgumentException.class, () -> dijkstraAlgorithm.run(graph, -1));
assertThrows(IllegalArgumentException.class, () -> dijkstraAlgorithm.run(graph, graph.length));
}
+
+ @Test
+ void testLinearGraph() {
+ // Linear graph: 0 - 1 - 2 - 3
+ // with weights: 2 3 4
+ int[][] linearGraph = {{0, 2, 0, 0}, {2, 0, 3, 0}, {0, 3, 0, 4}, {0, 0, 4, 0}};
+
+ DijkstraAlgorithm dijkstraLinear = new DijkstraAlgorithm(4);
+ int[] distances = dijkstraLinear.run(linearGraph, 0);
+
+ assertArrayEquals(new int[] {0, 2, 5, 9}, distances);
+ }
+
+ @Test
+ void testStarTopology() {
+ // Star graph: center node 0 connected to all others
+ // 1(2)
+ // |
+ // 3(4)-0-2(3)
+ // |
+ // 4(5)
+ int[][] starGraph = {{0, 2, 3, 4, 5}, {2, 0, 0, 0, 0}, {3, 0, 0, 0, 0}, {4, 0, 0, 0, 0}, {5, 0, 0, 0, 0}};
+
+ DijkstraAlgorithm dijkstraStar = new DijkstraAlgorithm(5);
+ int[] distances = dijkstraStar.run(starGraph, 0);
+
+ assertArrayEquals(new int[] {0, 2, 3, 4, 5}, distances);
+ }
+
+ @Test
+ void testCompleteGraphK4() {
+ // Complete graph K4 with varying weights
+ int[][] completeGraph = {{0, 1, 2, 3}, {1, 0, 4, 5}, {2, 4, 0, 6}, {3, 5, 6, 0}};
+
+ DijkstraAlgorithm dijkstraComplete = new DijkstraAlgorithm(4);
+ int[] distances = dijkstraComplete.run(completeGraph, 0);
+
+ // Direct paths from 0 are shortest
+ assertArrayEquals(new int[] {0, 1, 2, 3}, distances);
+ }
+
+ @Test
+ void testDifferentSourceVertex() {
+ // Test running from different source vertices
+ int[][] simpleGraph = {{0, 5, 0, 0}, {5, 0, 3, 0}, {0, 3, 0, 2}, {0, 0, 2, 0}};
+
+ DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(4);
+
+ // From vertex 0
+ int[] distFrom0 = dijkstra.run(simpleGraph, 0);
+ assertArrayEquals(new int[] {0, 5, 8, 10}, distFrom0);
+
+ // From vertex 2
+ int[] distFrom2 = dijkstra.run(simpleGraph, 2);
+ assertArrayEquals(new int[] {8, 3, 0, 2}, distFrom2);
+
+ // From vertex 3
+ int[] distFrom3 = dijkstra.run(simpleGraph, 3);
+ assertArrayEquals(new int[] {10, 5, 2, 0}, distFrom3);
+ }
+
+ @Test
+ void testUnitWeightGraph() {
+ // Graph with all unit weights (like BFS distance)
+ int[][] unitGraph = {{0, 1, 1, 0}, {1, 0, 1, 1}, {1, 1, 0, 1}, {0, 1, 1, 0}};
+
+ DijkstraAlgorithm dijkstraUnit = new DijkstraAlgorithm(4);
+ int[] distances = dijkstraUnit.run(unitGraph, 0);
+
+ assertArrayEquals(new int[] {0, 1, 1, 2}, distances);
+ }
+
+ @Test
+ void testTwoVertexGraph() {
+ int[][] twoVertexGraph = {{0, 7}, {7, 0}};
+
+ DijkstraAlgorithm dijkstraTwo = new DijkstraAlgorithm(2);
+ int[] distances = dijkstraTwo.run(twoVertexGraph, 0);
+
+ assertArrayEquals(new int[] {0, 7}, distances);
+ }
+
+ @Test
+ void testShortcutPath() {
+ // Graph where direct path is longer than indirect path
+ // 0 --(10)--> 2
+ // 0 --(1)--> 1 --(2)--> 2
+ int[][] shortcutGraph = {{0, 1, 10}, {1, 0, 2}, {10, 2, 0}};
+
+ DijkstraAlgorithm dijkstraShortcut = new DijkstraAlgorithm(3);
+ int[] distances = dijkstraShortcut.run(shortcutGraph, 0);
+
+ // The shortest path to vertex 2 should be 3 (via vertex 1), not 10 (direct)
+ assertArrayEquals(new int[] {0, 1, 3}, distances);
+ }
+
+ @Test
+ void testSourceToSourceDistanceIsZero() {
+ // Verify distance from source to itself is always 0
+ int[] distances = dijkstraAlgorithm.run(graph, 0);
+ assertEquals(0, distances[0]);
+
+ distances = dijkstraAlgorithm.run(graph, 5);
+ assertEquals(0, distances[5]);
+ }
+
+ @Test
+ void testLargeWeights() {
+ // Graph with large weights
+ int[][] largeWeightGraph = {{0, 1000, 0}, {1000, 0, 2000}, {0, 2000, 0}};
+
+ DijkstraAlgorithm dijkstraLarge = new DijkstraAlgorithm(3);
+ int[] distances = dijkstraLarge.run(largeWeightGraph, 0);
+
+ assertArrayEquals(new int[] {0, 1000, 3000}, distances);
+ }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java
index cc8a2df872ce..eaff0222bd36 100644
--- a/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java
@@ -137,4 +137,215 @@ void testDisconnectedGraph() {
assertTrue(dfs.containsAll(Arrays.asList(0, 1)));
assertTrue(bfs.containsAll(Arrays.asList(0, 1)));
}
+
+ @Test
+ void testSingleVertexGraphDfs() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(1);
+
+ List dfs = graph.depthFirstOrder(0);
+ assertEquals(1, dfs.size());
+ assertEquals(0, dfs.getFirst());
+ }
+
+ @Test
+ void testSingleVertexGraphBfs() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(1);
+
+ List bfs = graph.breadthFirstOrder(0);
+ assertEquals(1, bfs.size());
+ assertEquals(0, bfs.getFirst());
+ }
+
+ @Test
+ void testBfsLevelOrder() {
+ // Create a graph where BFS should visit level by level
+ // 0
+ // /|\
+ // 1 2 3
+ // |
+ // 4
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(0, 3);
+ graph.addEdge(1, 4);
+
+ List bfs = graph.breadthFirstOrder(0);
+ assertEquals(5, bfs.size());
+ assertEquals(0, bfs.get(0));
+ // Level 1 vertices (1, 2, 3) should appear before level 2 vertex (4)
+ int indexOf4 = bfs.indexOf(4);
+ assertTrue(bfs.indexOf(1) < indexOf4);
+ assertTrue(bfs.indexOf(2) < indexOf4);
+ assertTrue(bfs.indexOf(3) < indexOf4);
+ }
+
+ @Test
+ void testDfsStartFromDifferentVertices() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4);
+ graph.addEdge(0, 1);
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+
+ // DFS from vertex 0
+ List dfs0 = graph.depthFirstOrder(0);
+ assertEquals(4, dfs0.size());
+ assertEquals(0, dfs0.get(0));
+
+ // DFS from vertex 2
+ List dfs2 = graph.depthFirstOrder(2);
+ assertEquals(4, dfs2.size());
+ assertEquals(2, dfs2.get(0));
+
+ // DFS from vertex 3
+ List dfs3 = graph.depthFirstOrder(3);
+ assertEquals(4, dfs3.size());
+ assertEquals(3, dfs3.get(0));
+ }
+
+ @Test
+ void testBfsStartFromDifferentVertices() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4);
+ graph.addEdge(0, 1);
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+
+ // BFS from vertex 0
+ List bfs0 = graph.breadthFirstOrder(0);
+ assertEquals(4, bfs0.size());
+ assertEquals(0, bfs0.get(0));
+
+ // BFS from vertex 2
+ List bfs2 = graph.breadthFirstOrder(2);
+ assertEquals(4, bfs2.size());
+ assertEquals(2, bfs2.get(0));
+ }
+
+ @Test
+ void testStarTopologyBfs() {
+ // Star graph: 0 is center connected to 1, 2, 3, 4
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(0, 3);
+ graph.addEdge(0, 4);
+
+ List bfs = graph.breadthFirstOrder(0);
+ assertEquals(5, bfs.size());
+ assertEquals(0, bfs.get(0));
+ // All neighbors should be at distance 1
+ assertTrue(bfs.containsAll(Arrays.asList(1, 2, 3, 4)));
+ }
+
+ @Test
+ void testStarTopologyDfs() {
+ // Star graph: 0 is center connected to 1, 2, 3, 4
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(0, 3);
+ graph.addEdge(0, 4);
+
+ List dfs = graph.depthFirstOrder(0);
+ assertEquals(5, dfs.size());
+ assertEquals(0, dfs.get(0));
+ assertTrue(dfs.containsAll(Arrays.asList(1, 2, 3, 4)));
+ }
+
+ @Test
+ void testNegativeStartVertexDfs() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+
+ List dfs = graph.depthFirstOrder(-1);
+ assertTrue(dfs.isEmpty());
+ }
+
+ @Test
+ void testNegativeStartVertexBfs() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+
+ List bfs = graph.breadthFirstOrder(-1);
+ assertTrue(bfs.isEmpty());
+ }
+
+ @Test
+ void testCompleteGraphKFour() {
+ // Complete graph K4: every vertex connected to every other vertex
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4);
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(0, 3);
+ graph.addEdge(1, 2);
+ graph.addEdge(1, 3);
+ graph.addEdge(2, 3);
+
+ assertEquals(6, graph.numberOfEdges());
+
+ List dfs = graph.depthFirstOrder(0);
+ List bfs = graph.breadthFirstOrder(0);
+
+ assertEquals(4, dfs.size());
+ assertEquals(4, bfs.size());
+ assertTrue(dfs.containsAll(Arrays.asList(0, 1, 2, 3)));
+ assertTrue(bfs.containsAll(Arrays.asList(0, 1, 2, 3)));
+ }
+
+ @Test
+ void testLargerGraphTraversal() {
+ // Create a larger graph with 10 vertices
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(10);
+ graph.addEdge(0, 1);
+ graph.addEdge(0, 2);
+ graph.addEdge(1, 3);
+ graph.addEdge(1, 4);
+ graph.addEdge(2, 5);
+ graph.addEdge(2, 6);
+ graph.addEdge(3, 7);
+ graph.addEdge(4, 8);
+ graph.addEdge(5, 9);
+
+ List dfs = graph.depthFirstOrder(0);
+ List bfs = graph.breadthFirstOrder(0);
+
+ assertEquals(10, dfs.size());
+ assertEquals(10, bfs.size());
+ assertEquals(0, dfs.get(0));
+ assertEquals(0, bfs.get(0));
+ }
+
+ @Test
+ void testSelfLoop() {
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(3);
+ graph.addEdge(0, 0); // Self loop
+ graph.addEdge(0, 1);
+ graph.addEdge(1, 2);
+
+ List dfs = graph.depthFirstOrder(0);
+ List bfs = graph.breadthFirstOrder(0);
+
+ assertEquals(3, dfs.size());
+ assertEquals(3, bfs.size());
+ }
+
+ @Test
+ void testLinearGraphTraversal() {
+ // Linear graph: 0 - 1 - 2 - 3 - 4
+ AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5);
+ graph.addEdge(0, 1);
+ graph.addEdge(1, 2);
+ graph.addEdge(2, 3);
+ graph.addEdge(3, 4);
+
+ List dfs = graph.depthFirstOrder(0);
+ List bfs = graph.breadthFirstOrder(0);
+
+ assertEquals(5, dfs.size());
+ assertEquals(5, bfs.size());
+
+ // In a linear graph, BFS and DFS starting from 0 should be the same
+ assertEquals(Arrays.asList(0, 1, 2, 3, 4), dfs);
+ assertEquals(Arrays.asList(0, 1, 2, 3, 4), bfs);
+ }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java
new file mode 100644
index 000000000000..3d3fe63d775a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.datastructures.hashmap.hashing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class ImmutableHashMapTest {
+
+ @Test
+ void testEmptyMap() {
+ ImmutableHashMap map = ImmutableHashMap.empty();
+
+ assertEquals(0, map.size());
+ assertNull(map.get("A"));
+ }
+
+ @Test
+ void testPutDoesNotModifyOriginalMap() {
+ ImmutableHashMap map1 = ImmutableHashMap.empty();
+
+ ImmutableHashMap map2 = map1.put("A", 1);
+
+ assertEquals(0, map1.size());
+ assertEquals(1, map2.size());
+ assertNull(map1.get("A"));
+ assertEquals(1, map2.get("A"));
+ }
+
+ @Test
+ void testMultiplePuts() {
+ ImmutableHashMap map = ImmutableHashMap.empty().put("A", 1).put("B", 2);
+
+ assertEquals(2, map.size());
+ assertEquals(1, map.get("A"));
+ assertEquals(2, map.get("B"));
+ }
+
+ @Test
+ void testContainsKey() {
+ ImmutableHashMap map = ImmutableHashMap.empty().put("X", 100);
+
+ assertTrue(map.containsKey("X"));
+ assertFalse(map.containsKey("Y"));
+ }
+
+ @Test
+ void testNullKey() {
+ ImmutableHashMap map = ImmutableHashMap.empty().put(null, 50);
+
+ assertEquals(50, map.get(null));
+ }
+}
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 newNodePQ() {
+ return new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+ }
+
+ // ------------------------
+ // Basic operations
+ // ------------------------
+
+ @Test
+ void testOfferPollWithIntegersComparableMode() {
+ // cmp == null -> elements must be Comparable
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
+ Assertions.assertTrue(pq.isEmpty());
+
+ pq.offer(5);
+ pq.offer(1);
+ pq.offer(3);
+
+ Assertions.assertEquals(3, pq.size());
+ Assertions.assertEquals(1, pq.peek());
+ Assertions.assertEquals(1, pq.poll());
+ Assertions.assertEquals(3, pq.poll());
+ Assertions.assertEquals(5, pq.poll());
+ Assertions.assertNull(pq.poll()); // empty -> null
+ Assertions.assertTrue(pq.isEmpty());
+ }
+
+ @Test
+ void testPeekAndIsEmpty() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.peek());
+
+ pq.offer(new Node("A", 10));
+ pq.offer(new Node("B", 5));
+ pq.offer(new Node("C", 7));
+
+ Assertions.assertFalse(pq.isEmpty());
+ Assertions.assertEquals("B(5)", pq.peek().toString());
+ }
+
+ @Test
+ void testRemoveSpecificElement() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 5);
+ Node c = new Node("C", 7);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // remove by reference (O(log n))
+ Assertions.assertTrue(pq.remove(b));
+ Assertions.assertEquals(2, pq.size());
+ // now min should be C(7)
+ Assertions.assertEquals("C(7)", pq.peek().toString());
+ // removing an element not present -> false
+ Assertions.assertFalse(pq.remove(b));
+ }
+
+ @Test
+ void testContainsAndClear() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 2);
+ Node b = new Node("B", 3);
+
+ pq.offer(a);
+ pq.offer(b);
+
+ Assertions.assertTrue(pq.contains(a));
+ Assertions.assertTrue(pq.contains(b));
+
+ pq.clear();
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertFalse(pq.contains(a));
+ Assertions.assertNull(pq.peek());
+ }
+
+ // ------------------------
+ // Key updates
+ // ------------------------
+
+ @Test
+ void testDecreaseKeyMovesUp() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 5);
+ Node c = new Node("C", 7);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // current min is B(5)
+ Assertions.assertEquals("B(5)", pq.peek().toString());
+
+ // Make A more important: 10 -> 1 (smaller is better)
+ pq.decreaseKey(a, n -> n.prio = 1);
+
+ // Now A should be at the top
+ Assertions.assertEquals("A(1)", pq.peek().toString());
+ }
+
+ @Test
+ void testIncreaseKeyMovesDown() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 1);
+ Node b = new Node("B", 2);
+ Node c = new Node("C", 3);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // min is A(1)
+ Assertions.assertEquals("A(1)", pq.peek().toString());
+
+ // Make A worse: 1 -> 100
+ pq.increaseKey(a, n -> n.prio = 100);
+
+ // Now min should be B(2)
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+ }
+
+ @Test
+ void testChangeKeyChoosesDirectionAutomatically() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 20);
+ Node c = new Node("C", 30);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // Decrease B to 0 -> should move up
+ pq.changeKey(b, n -> n.prio = 0);
+ Assertions.assertEquals("B(0)", pq.peek().toString());
+
+ // Increase B to 100 -> should move down
+ pq.changeKey(b, n -> n.prio = 100);
+ Assertions.assertEquals("A(10)", pq.peek().toString());
+ }
+
+ @Test
+ void testDirectMutationWithoutChangeKeyDoesNotReheapByDesign() {
+ // Demonstrates the contract: do NOT mutate comparator fields directly.
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 5);
+ Node b = new Node("B", 10);
+
+ pq.offer(a);
+ pq.offer(b);
+
+ // Illegally mutate priority directly
+ a.prio = 100; // worse than b now, but heap wasn't notified
+
+ // The heap structure is unchanged; peek still returns A(100) (was A(5) before)
+ // This test documents the behavior/contract rather than relying on it.
+ Assertions.assertEquals("A(100)", pq.peek().toString());
+
+ // Now fix properly via changeKey (no change in value, but triggers reheap)
+ pq.changeKey(a, n -> n.prio = n.prio);
+ Assertions.assertEquals("B(10)", pq.peek().toString());
+ }
+
+ // ------------------------
+ // Identity semantics & duplicates
+ // ------------------------
+
+ @Test
+ void testDuplicateEqualsElementsAreSupportedIdentityMap() {
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+
+ NodeWithEquals x1 = new NodeWithEquals("X1", 7);
+ NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance
+
+ // With IdentityHashMap internally, both can coexist
+ pq.offer(x1);
+ pq.offer(x2);
+
+ Assertions.assertEquals(2, pq.size());
+ // Poll twice; both 7s should be returned (order between x1/x2 is unspecified)
+ Assertions.assertEquals(7, pq.poll().prio);
+ Assertions.assertEquals(7, pq.poll().prio);
+ Assertions.assertTrue(pq.isEmpty());
+ }
+
+ // ------------------------
+ // Capacity growth
+ // ------------------------
+
+ @Test
+ void testGrowByManyInserts() {
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
+ int n = 100; // beyond default capacity (11)
+
+ for (int i = n; i >= 1; i--) {
+ pq.offer(i);
+ }
+
+ Assertions.assertEquals(n, pq.size());
+ // Ensure min-to-max order when polling
+ for (int expected = 1; expected <= n; expected++) {
+ Integer v = pq.poll();
+ Assertions.assertEquals(expected, v);
+ }
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.poll());
+ }
+
+ // ------------------------
+ // remove/contains edge cases
+ // ------------------------
+
+ @Test
+ void testRemoveHeadAndMiddleAndTail() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 1);
+ Node b = new Node("B", 2);
+ Node c = new Node("C", 3);
+ Node d = new Node("D", 4);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+ pq.offer(d);
+
+ // remove head
+ Assertions.assertTrue(pq.remove(a));
+ Assertions.assertFalse(pq.contains(a));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove middle
+ Assertions.assertTrue(pq.remove(c));
+ Assertions.assertFalse(pq.contains(c));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove tail (last)
+ Assertions.assertTrue(pq.remove(d));
+ Assertions.assertFalse(pq.contains(d));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove last remaining
+ Assertions.assertTrue(pq.remove(b));
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.peek());
+ }
+
+ // ------------------------
+ // Error / edge cases for coverage
+ // ------------------------
+
+ @Test
+ void testInvalidInitialCapacityThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new IndexedPriorityQueue(0, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void testChangeKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.changeKey(a, n -> n.prio = 5));
+ }
+
+ @Test
+ void testDecreaseKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.decreaseKey(a, n -> n.prio = 5));
+ }
+
+ @Test
+ void testIncreaseKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.increaseKey(a, n -> n.prio = 15));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java
new file mode 100644
index 000000000000..2461fd74143d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.datastructures.trees;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for the BinaryTreeToString class.
+ */
+public class BinaryTreeToStringTest {
+
+ @Test
+ public void testTreeToStringBasic() {
+ BinaryTree tree = new BinaryTree();
+ tree.put(1);
+ tree.put(2);
+ tree.put(3);
+ tree.put(4);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(tree.getRoot());
+
+ // Output will depend on insertion logic of BinaryTree.put()
+ // which is BST-style, so result = "1()(2()(3()(4)))"
+ Assertions.assertEquals("1()(2()(3()(4)))", result);
+ }
+
+ @Test
+ public void testSingleNodeTree() {
+ BinaryTree tree = new BinaryTree();
+ tree.put(10);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(tree.getRoot());
+
+ Assertions.assertEquals("10", result);
+ }
+
+ @Test
+ public void testComplexTreeStructure() {
+ BinaryTree.Node root = new BinaryTree.Node(10);
+ root.left = new BinaryTree.Node(5);
+ root.right = new BinaryTree.Node(20);
+ root.right.left = new BinaryTree.Node(15);
+ root.right.right = new BinaryTree.Node(25);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(root);
+
+ Assertions.assertEquals("10(5)(20(15)(25))", result);
+ }
+
+ @Test
+ public void testNullTree() {
+ BinaryTreeToString converter = new BinaryTreeToString();
+ Assertions.assertEquals("", converter.tree2str(null));
+ }
+}
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<>()); });
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java
new file mode 100644
index 000000000000..c5973168438e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java
@@ -0,0 +1,50 @@
+/*
+ * TheAlgorithms (https://github.com/TheAlgorithms/Java)
+ * Author: Shewale41
+ * This file is licensed under the MIT License.
+ */
+
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Basic tests for ThreadedBinaryTree inorder traversal.
+ */
+public class ThreadedBinaryTreeTest {
+
+ @Test
+ public void testInorderTraversalSimple() {
+ ThreadedBinaryTree tree = new ThreadedBinaryTree();
+ tree.insert(50);
+ tree.insert(30);
+ tree.insert(70);
+ tree.insert(20);
+ tree.insert(40);
+ tree.insert(60);
+ tree.insert(80);
+
+ List expected = List.of(20, 30, 40, 50, 60, 70, 80);
+ List actual = tree.inorderTraversal();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testInorderWithDuplicates() {
+ ThreadedBinaryTree tree = new ThreadedBinaryTree();
+ tree.insert(5);
+ tree.insert(3);
+ tree.insert(7);
+ tree.insert(7); // duplicate
+ tree.insert(2);
+
+ List expected = List.of(2, 3, 5, 7, 7);
+ List actual = tree.inorderTraversal();
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java
new file mode 100644
index 000000000000..241f23c0fa1d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java
@@ -0,0 +1,132 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.random.RandomGenerator;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class GomoryHuTreeTest {
+
+ @Test
+ @DisplayName("Single node graph")
+ void singleNode() {
+ int[][] cap = {{0}};
+ int[][] res = GomoryHuTree.buildTree(cap);
+ int[] parent = res[0];
+ int[] weight = res[1];
+ assertEquals(-1, parent[0]);
+ assertEquals(0, weight[0]);
+ }
+
+ @Test
+ @DisplayName("Triangle undirected graph with known min-cuts")
+ void triangleGraph() {
+ // 0-1:3, 1-2:2, 0-2:4
+ int[][] cap = new int[3][3];
+ cap[0][1] = 3;
+ cap[1][0] = 3;
+ cap[1][2] = 2;
+ cap[2][1] = 2;
+ cap[0][2] = 4;
+ cap[2][0] = 4;
+
+ int[][] tree = GomoryHuTree.buildTree(cap);
+ // validate all pairs via path-min-edge equals maxflow
+ validateAllPairs(cap, tree);
+ }
+
+ @Test
+ @DisplayName("Random small undirected graphs compare to EdmondsKarp")
+ void randomSmallGraphs() {
+ Random rng = new Random(42);
+ for (int n = 2; n <= 6; n++) {
+ for (int iter = 0; iter < 10; iter++) {
+ int[][] cap = randSymmetricMatrix(n, 0, 5, rng);
+ int[][] tree = GomoryHuTree.buildTree(cap);
+ validateAllPairs(cap, tree);
+ }
+ }
+ }
+
+ private static int[][] randSymmetricMatrix(int n, int lo, int hi, RandomGenerator rng) {
+ int[][] a = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ for (int j = i + 1; j < n; j++) {
+ int w = rng.nextInt(hi - lo + 1) + lo;
+ a[i][j] = w;
+ a[j][i] = w;
+ }
+ }
+ // zero diagonal
+ for (int i = 0; i < n; i++) {
+ a[i][i] = 0;
+ }
+ return a;
+ }
+
+ private static void validateAllPairs(int[][] cap, int[][] tree) {
+ int n = cap.length;
+ int[] parent = tree[0];
+ int[] weight = tree[1];
+
+ // build adjacency list of tree without generic array creation
+ List> 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 q = new ArrayDeque<>();
+ q.add(s);
+ parent[s] = s;
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ if (u == t) {
+ break;
+ }
+ for (int[] e : g.get(u)) {
+ int v = e[0];
+ int w = e[1];
+ if (parent[v] == -1) {
+ parent[v] = u;
+ edgeW[v] = w;
+ q.add(v);
+ }
+ }
+ }
+ int cur = t;
+ int ans = Integer.MAX_VALUE;
+ while (cur != s) {
+ ans = Math.min(ans, edgeW[cur]);
+ cur = parent[cur];
+ }
+ return ans == Integer.MAX_VALUE ? 0 : ans;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java
new file mode 100644
index 000000000000..5b35345afd02
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java
@@ -0,0 +1,31 @@
+package com.thealgorithms.maths;
+
+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 org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class AbundantNumberTest {
+ @ParameterizedTest
+ @CsvSource({"12", "66", "222", "444", "888", "2424"})
+ void abundantNumbersTest(int n) {
+ assertTrue(AbundantNumber.isAbundant(n));
+ assertTrue(AbundantNumber.isAbundantNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"1", "2", "6", "111", "333", "2222"})
+ void nonAbundantNumbersTest(int n) {
+ assertFalse(AbundantNumber.isAbundant(n));
+ assertFalse(AbundantNumber.isAbundantNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0", "-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundant(n));
+ assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundantNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/EvilNumberTest.java b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java
new file mode 100644
index 000000000000..e59171fad25f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.maths;
+
+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 org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class EvilNumberTest {
+ @ParameterizedTest
+ @CsvSource({"0", "3", "10", "129", "222", "500", "777", "1198"})
+ void evilNumbersTest(int n) {
+ assertTrue(EvilNumber.isEvilNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"1", "7", "100", "333", "555", "1199"})
+ void odiousNumbersTest(int n) {
+ assertFalse(EvilNumber.isEvilNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> EvilNumber.isEvilNumber(n));
+ }
+}
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);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/GCDTest.java b/src/test/java/com/thealgorithms/maths/GCDTest.java
index bac3f8f7596c..6bc870e94df9 100644
--- a/src/test/java/com/thealgorithms/maths/GCDTest.java
+++ b/src/test/java/com/thealgorithms/maths/GCDTest.java
@@ -6,57 +6,77 @@
public class GCDTest {
@Test
- void test1() {
+ void testNegativeAndZeroThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-1, 0));
}
@Test
- void test2() {
+ void testPositiveAndNegativeThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(10, -2));
}
@Test
- void test3() {
+ void testBothNegativeThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-5, -3));
}
@Test
- void test4() {
- Assertions.assertEquals(GCD.gcd(0, 2), 2);
+ void testZeroAndPositiveReturnsPositive() {
+ Assertions.assertEquals(2, GCD.gcd(0, 2));
}
@Test
- void test5() {
- Assertions.assertEquals(GCD.gcd(10, 0), 10);
+ void testPositiveAndZeroReturnsPositive() {
+ Assertions.assertEquals(10, GCD.gcd(10, 0));
}
@Test
- void test6() {
- Assertions.assertEquals(GCD.gcd(1, 0), 1);
+ void testOneAndZeroReturnsOne() {
+ Assertions.assertEquals(1, GCD.gcd(1, 0));
}
@Test
- void test7() {
- Assertions.assertEquals(GCD.gcd(9, 6), 3);
+ void testTwoPositiveNumbers() {
+ Assertions.assertEquals(3, GCD.gcd(9, 6));
}
@Test
- void test8() {
- Assertions.assertEquals(GCD.gcd(48, 18, 30, 12), 6);
+ void testMultipleArgumentsGcd() {
+ Assertions.assertEquals(6, GCD.gcd(48, 18, 30, 12));
}
@Test
- void testArrayGcd1() {
- Assertions.assertEquals(GCD.gcd(new int[] {9, 6}), 3);
+ void testArrayInputGcd() {
+ Assertions.assertEquals(3, GCD.gcd(new int[] {9, 6}));
}
@Test
- void testArrayGcd2() {
- Assertions.assertEquals(GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13}), 5);
+ void testArrayWithCommonFactor() {
+ Assertions.assertEquals(5, GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13}));
}
@Test
- void testArrayGcdForEmptyInput() {
- Assertions.assertEquals(GCD.gcd(new int[] {}), 0);
+ void testEmptyArrayReturnsZero() {
+ Assertions.assertEquals(0, GCD.gcd(new int[] {}));
+ }
+
+ @Test
+ void testSameNumbers() {
+ Assertions.assertEquals(7, GCD.gcd(7, 7));
+ }
+
+ @Test
+ void testPrimeNumbersHaveGcdOne() {
+ Assertions.assertEquals(1, GCD.gcd(13, 17));
+ }
+
+ @Test
+ void testSingleElementArrayReturnsElement() {
+ Assertions.assertEquals(42, GCD.gcd(new int[] {42}));
+ }
+
+ @Test
+ void testLargeNumbers() {
+ Assertions.assertEquals(12, GCD.gcd(123456, 789012));
}
}
diff --git a/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java
new file mode 100644
index 000000000000..91904316b25c
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.maths;
+
+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 org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class LuckyNumberTest {
+
+ @ParameterizedTest
+ @CsvSource({"1", "3", "13", "49", "109", "459", "949"})
+ void luckyNumbersTest(int n) {
+ assertTrue(LuckyNumber.isLucky(n));
+ assertTrue(LuckyNumber.isLuckyNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"2", "17", "100", "300", "700"})
+ void nonLuckyNumbersTest(int n) {
+ assertFalse(LuckyNumber.isLucky(n));
+ assertFalse(LuckyNumber.isLuckyNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0", "-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLucky(n));
+ assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLuckyNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java b/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java
new file mode 100644
index 000000000000..c91f8b3cf1b5
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class PowerOfFourTest {
+
+ @Test
+ void testPowersOfFour() {
+ assertTrue(PowerOfFour.isPowerOfFour(1));
+ assertTrue(PowerOfFour.isPowerOfFour(4));
+ assertTrue(PowerOfFour.isPowerOfFour(16));
+ assertTrue(PowerOfFour.isPowerOfFour(64));
+ assertTrue(PowerOfFour.isPowerOfFour(256));
+ assertTrue(PowerOfFour.isPowerOfFour(1024));
+ }
+
+ @Test
+ void testNonPowersOfFour() {
+ assertFalse(PowerOfFour.isPowerOfFour(2));
+ assertFalse(PowerOfFour.isPowerOfFour(3));
+ assertFalse(PowerOfFour.isPowerOfFour(5));
+ assertFalse(PowerOfFour.isPowerOfFour(8));
+ assertFalse(PowerOfFour.isPowerOfFour(15));
+ assertFalse(PowerOfFour.isPowerOfFour(32));
+ }
+
+ @Test
+ void testEdgeCases() {
+ assertFalse(PowerOfFour.isPowerOfFour(0));
+ assertFalse(PowerOfFour.isPowerOfFour(-1));
+ assertFalse(PowerOfFour.isPowerOfFour(-4));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java b/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java
index ebbd5df712fc..5d491a493ee7 100644
--- a/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java
+++ b/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java
@@ -1,46 +1,64 @@
package com.thealgorithms.maths;
-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 static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Arrays;
+import java.util.List;
import org.junit.jupiter.api.Test;
+/**
+ * Test cases for Sieve of Eratosthenes algorithm
+ *
+ * @author Navadeep0007
+ */
class SieveOfEratosthenesTest {
+
+ @Test
+ void testPrimesUpTo10() {
+ List expected = Arrays.asList(2, 3, 5, 7);
+ assertEquals(expected, SieveOfEratosthenes.findPrimes(10));
+ }
+
+ @Test
+ void testPrimesUpTo30() {
+ List expected = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
+ assertEquals(expected, SieveOfEratosthenes.findPrimes(30));
+ }
+
@Test
- public void testfFindPrimesTill1() {
- assertArrayEquals(new int[] {}, SieveOfEratosthenes.findPrimesTill(1));
+ void testPrimesUpTo2() {
+ List expected = Arrays.asList(2);
+ assertEquals(expected, SieveOfEratosthenes.findPrimes(2));
}
@Test
- public void testfFindPrimesTill2() {
- assertArrayEquals(new int[] {2}, SieveOfEratosthenes.findPrimesTill(2));
+ void testPrimesUpTo1() {
+ assertTrue(SieveOfEratosthenes.findPrimes(1).isEmpty());
}
@Test
- public void testfFindPrimesTill4() {
- var primesTill4 = new int[] {2, 3};
- assertArrayEquals(primesTill4, SieveOfEratosthenes.findPrimesTill(3));
- assertArrayEquals(primesTill4, SieveOfEratosthenes.findPrimesTill(4));
+ void testPrimesUpTo0() {
+ assertTrue(SieveOfEratosthenes.findPrimes(0).isEmpty());
}
@Test
- public void testfFindPrimesTill40() {
- var primesTill40 = new int[] {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
- assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(37));
- assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(38));
- assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(39));
- assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(40));
+ void testNegativeInput() {
+ assertThrows(IllegalArgumentException.class, () -> { SieveOfEratosthenes.findPrimes(-1); });
}
@Test
- public void testfFindPrimesTill240() {
- var primesTill240 = new int[] {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239};
- assertArrayEquals(primesTill240, SieveOfEratosthenes.findPrimesTill(239));
- assertArrayEquals(primesTill240, SieveOfEratosthenes.findPrimesTill(240));
+ void testCountPrimes() {
+ assertEquals(4, SieveOfEratosthenes.countPrimes(10));
+ assertEquals(25, SieveOfEratosthenes.countPrimes(100));
}
@Test
- public void testFindPrimesTillThrowsExceptionForNonPositiveInput() {
- assertThrows(IllegalArgumentException.class, () -> SieveOfEratosthenes.findPrimesTill(0));
+ void testLargeNumber() {
+ List primes = SieveOfEratosthenes.findPrimes(1000);
+ assertEquals(168, primes.size()); // There are 168 primes up to 1000
+ assertEquals(2, primes.get(0)); // First prime
+ assertEquals(997, primes.get(primes.size() - 1)); // Last prime up to 1000
}
}
diff --git a/src/test/java/com/thealgorithms/maths/SmithNumberTest.java b/src/test/java/com/thealgorithms/maths/SmithNumberTest.java
new file mode 100644
index 000000000000..4e2ba0b88e33
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/SmithNumberTest.java
@@ -0,0 +1,22 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class SmithNumberTest {
+
+ @ParameterizedTest
+ @CsvSource({"4", "22", "121", "562", "985", "4937775"})
+ void positiveSmithNumbersTest(int n) {
+ assertTrue(SmithNumber.isSmithNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"2", "11", "100", "550", "999", "1234557"})
+ void negativeSmithNumbersTest(int n) {
+ assertFalse(SmithNumber.isSmithNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java b/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java
new file mode 100644
index 000000000000..1bba918dadac
--- /dev/null
+++ b/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.matrix;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class StochasticMatrixTest {
+
+ @Test
+ void testRowStochasticMatrix() {
+ double[][] matrix = {{0.2, 0.5, 0.3}, {0.1, 0.6, 0.3}};
+ assertTrue(StochasticMatrix.isRowStochastic(matrix));
+ assertFalse(StochasticMatrix.isColumnStochastic(matrix));
+ }
+
+ @Test
+ void testColumnStochasticMatrix() {
+ double[][] matrix = {{0.4, 0.2}, {0.6, 0.8}};
+ assertTrue(StochasticMatrix.isColumnStochastic(matrix));
+ }
+
+ @Test
+ void testInvalidMatrix() {
+ double[][] matrix = {{0.5, -0.5}, {0.5, 1.5}};
+ assertFalse(StochasticMatrix.isRowStochastic(matrix));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/physics/SnellLawTest.java b/src/test/java/com/thealgorithms/physics/SnellLawTest.java
new file mode 100644
index 000000000000..ddd5fb1d5af7
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/SnellLawTest.java
@@ -0,0 +1,41 @@
+package com.thealgorithms.physics;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class SnellLawTest {
+
+ @Test
+ public void testRefractedAngle() {
+ double n1 = 1.0; // air
+ double n2 = 1.5; // glass
+ double theta1 = Math.toRadians(30);
+
+ double theta2 = SnellLaw.refractedAngle(n1, n2, theta1);
+
+ double expected = Math.asin(n1 / n2 * Math.sin(theta1));
+
+ assertEquals(expected, theta2, 1e-12);
+ }
+
+ @Test
+ public void testTotalInternalReflection() {
+ double n1 = 1.5;
+ double n2 = 1.0;
+ double theta1 = Math.toRadians(60); // large angle
+
+ assertThrows(IllegalArgumentException.class, () -> SnellLaw.refractedAngle(n1, n2, theta1));
+ }
+
+ @Test
+ public void testNoTotalInternalReflectionAtLowAngles() {
+ double n1 = 1.5;
+ double n2 = 1.0;
+ double theta1 = Math.toRadians(10);
+
+ assertDoesNotThrow(() -> SnellLaw.refractedAngle(n1, n2, theta1));
+ }
+}
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);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java
deleted file mode 100644
index 7fb96dcf805f..000000000000
--- a/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.thealgorithms.puzzlesandgames;
-
-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 org.junit.jupiter.api.Test;
-
-public class SudokuTest {
-
- @Test
- void testIsSafe2() {
- int[][] board = {{3, 0, 6, 5, 0, 8, 4, 0, 0}, {5, 2, 0, 0, 0, 0, 0, 0, 0}, {0, 8, 7, 0, 0, 0, 0, 3, 1}, {0, 0, 3, 0, 1, 0, 0, 8, 0}, {9, 0, 0, 8, 6, 3, 0, 0, 5}, {0, 5, 0, 0, 9, 0, 6, 0, 0}, {1, 3, 0, 0, 0, 0, 2, 5, 0}, {0, 0, 0, 0, 0, 0, 0, 7, 4}, {0, 0, 5, 2, 0, 6, 3, 0, 0}};
-
- assertFalse(Sudoku.isSafe(board, 0, 1, 3));
- assertTrue(Sudoku.isSafe(board, 1, 2, 1));
- assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.isSafe(board, 10, 10, 5); });
- assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.isSafe(board, -1, 0, 5); });
- }
-
- @Test
- void testSolveSudoku() {
- int[][] board = {{3, 0, 6, 5, 0, 8, 4, 0, 0}, {5, 2, 0, 0, 0, 0, 0, 0, 0}, {0, 8, 7, 0, 0, 0, 0, 3, 1}, {0, 0, 3, 0, 1, 0, 0, 8, 0}, {9, 0, 0, 8, 6, 3, 0, 0, 5}, {0, 5, 0, 0, 9, 0, 6, 0, 0}, {1, 3, 0, 0, 0, 0, 2, 5, 0}, {0, 0, 0, 0, 0, 0, 0, 7, 4}, {0, 0, 5, 2, 0, 6, 3, 0, 0}};
-
- assertTrue(Sudoku.solveSudoku(board, board.length));
- assertEquals(1, board[0][1]);
- assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.solveSudoku(board, 10); });
- assertTrue(Sudoku.solveSudoku(board, -1));
- }
-
- @Test
- void testUnsolvableSudoku() {
- int[][] unsolvableBoard = {{5, 1, 6, 8, 4, 9, 7, 3, 2}, {3, 0, 7, 6, 0, 5, 0, 0, 0}, {8, 0, 9, 7, 0, 0, 0, 6, 5}, {1, 3, 5, 0, 6, 0, 9, 0, 7}, {4, 7, 2, 5, 9, 1, 0, 0, 6}, {9, 6, 8, 3, 7, 0, 0, 5, 0}, {2, 5, 3, 1, 8, 6, 0, 7, 4}, {6, 8, 4, 2, 5, 7, 3, 9, 0}, {7, 9, 1, 4, 3, 0, 5, 0, 0}};
-
- assertFalse(Sudoku.solveSudoku(unsolvableBoard, unsolvableBoard.length));
- }
-}
diff --git a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java b/src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java
similarity index 96%
rename from src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java
rename to src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java
index db18b46356b4..198fcd558f63 100644
--- a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java
+++ b/src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.recursion;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
diff --git a/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java
new file mode 100644
index 000000000000..39014780caa9
--- /dev/null
+++ b/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.stacks;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class ValidParenthesesTest {
+
+ @Test
+ void testValidParentheses() {
+ assertTrue(ValidParentheses.isValid("()"));
+ assertTrue(ValidParentheses.isValid("()[]{}"));
+ assertTrue(ValidParentheses.isValid("{[]}"));
+ assertTrue(ValidParentheses.isValid(""));
+ }
+
+ @Test
+ void testInvalidParentheses() {
+ assertFalse(ValidParentheses.isValid("(]"));
+ assertFalse(ValidParentheses.isValid("([)]"));
+ assertFalse(ValidParentheses.isValid("{{{"));
+ assertFalse(ValidParentheses.isValid("}"));
+ assertFalse(ValidParentheses.isValid("("));
+ }
+
+ @Test
+ void testNullAndOddLength() {
+ assertFalse(ValidParentheses.isValid(null));
+ assertFalse(ValidParentheses.isValid("(()"));
+ }
+}
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 "));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java
new file mode 100644
index 000000000000..df749ed9a8b5
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java
@@ -0,0 +1,25 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class ZAlgorithmTest {
+
+ @Test
+ void testZFunction() {
+ int[] z = ZAlgorithm.zFunction("aaaaa");
+ assertArrayEquals(new int[] {0, 4, 3, 2, 1}, z);
+ }
+
+ @Test
+ void testSearchFound() {
+ assertEquals(2, ZAlgorithm.search("abcabca", "cab"));
+ }
+
+ @Test
+ void testSearchNotFound() {
+ assertEquals(-1, ZAlgorithm.search("abcdef", "gh"));
+ }
+}