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/Readme.md b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
index 252b06ea59b0..4400a97d8128 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
@@ -2,6 +2,8 @@
A hash map organizes data so you can quickly look up values for a given key.
+> Note: The term “hash map” refers to the data structure concept, while `HashMap` refers specifically to Java’s implementation.
+
## Strengths:
- **Fast lookups**: Lookups take O(1) time on average.
- **Flexible keys**: Most data types can be used for keys, as long as they're hashable.
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/lists/MiddleOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
new file mode 100644
index 000000000000..0ee788db2ff9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.datastructures.lists;
+
+/**
+ * Returns the middle node of a singly linked list using the two-pointer technique.
+ *
+ *
The {@code slow} pointer advances by one node per iteration while {@code fast} advances by two.
+ * When {@code fast == null} or {@code fast.next == null}, {@code slow} points to the middle node.
+ * For even-length lists, this returns the second middle node.
+ *
+ * This method does not modify the input list.
+ *
+ * Reference: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
+ *
+ * Complexity:
+ *
+ * - Time: {@code O(n)}
+ * - Space: {@code O(1)}
+ *
+ */
+public final class MiddleOfLinkedList {
+
+ private MiddleOfLinkedList() {
+ }
+
+ /**
+ * Returns the middle node of the list.
+ *
+ * @param head the head of the singly linked list; may be {@code null}
+ * @return the middle node (second middle for even-sized lists), or {@code null} if {@code head} is {@code null}
+ */
+ public static SinglyLinkedListNode middleNode(final SinglyLinkedListNode head) {
+ if (head == null) {
+ return null;
+ }
+
+ SinglyLinkedListNode slow = head;
+ SinglyLinkedListNode fast = head;
+
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+
+ return slow;
+ }
+}
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/BellNumbers.java b/src/main/java/com/thealgorithms/maths/BellNumbers.java
new file mode 100644
index 000000000000..d4dc1014f48b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/BellNumbers.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.maths;
+
+/**
+ * The Bell numbers count the number of partitions of a set.
+ * The n-th Bell number is the number of ways a set of n elements can be partitioned
+ * into nonempty subsets.
+ *
+ *
+ * This implementation uses the Bell Triangle (Aitken's array) method.
+ * Time Complexity: O(n^2)
+ * Space Complexity: O(n^2)
+ *
+ *
+ * @author Chahat Sandhu, singhc7
+ * @see Bell Number (Wikipedia)
+ */
+public final class BellNumbers {
+
+ private BellNumbers() {
+ }
+
+ /**
+ * Calculates the n-th Bell number using the Bell Triangle.
+ *
+ * @param n the index of the Bell number (must be non-negative)
+ * @return the n-th Bell number
+ * @throws IllegalArgumentException if n is negative or n > 25
+ */
+ public static long compute(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be non-negative");
+ }
+ if (n == 0) {
+ return 1;
+ }
+ if (n > 25) {
+ throw new IllegalArgumentException("n must be <= 25. For larger n, use BigInteger implementation.");
+ }
+
+ // We use a 2D array to visualize the Bell Triangle
+ long[][] bellTriangle = new long[n + 1][n + 1];
+
+ // Base case: The triangle starts with 1
+ bellTriangle[0][0] = 1;
+
+ for (int i = 1; i <= n; i++) {
+ // Rule 1: The first number in a new row is the LAST number of the previous row
+ bellTriangle[i][0] = bellTriangle[i - 1][i - 1];
+
+ // Rule 2: Fill the rest of the row by adding the previous neighbor and the upper-left neighbor
+ for (int j = 1; j <= i; j++) {
+ bellTriangle[i][j] = bellTriangle[i][j - 1] + bellTriangle[i - 1][j - 1];
+ }
+ }
+
+ // The Bell number B_n is the first number in the n-th row
+ return bellTriangle[n][0];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java
new file mode 100644
index 000000000000..cd1c9205b328
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.maths;
+
+/**
+ * Distance Between Two Points in 2D Space.
+ *
+ * This class provides a method to calculate the Euclidean distance between two points in a
+ * two-dimensional plane.
+ *
+ *
Formula: d = sqrt((x2 - x1)^2 + (y2 - y1)^2)
+ *
+ *
Reference: https://en.wikipedia.org/wiki/Euclidean_distance
+ */
+public final class DistanceBetweenTwoPoints {
+
+ private DistanceBetweenTwoPoints() {
+ // Utility class; prevent instantiation
+ }
+
+ /**
+ * Calculate the Euclidean distance between two points.
+ *
+ * @param x1 x-coordinate of the first point
+ * @param y1 y-coordinate of the first point
+ * @param x2 x-coordinate of the second point
+ * @param y2 y-coordinate of the second point
+ * @return Euclidean distance between the two points
+ */
+ public static double calculate(final double x1, final double y1, final double x2, final double y2) {
+ final double deltaX = x2 - x1;
+ final double deltaY = y2 - y1;
+ return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ }
+}
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/PerfectSquare.java b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
index e9318bd7d805..aec43062121a 100644
--- a/src/main/java/com/thealgorithms/maths/PerfectSquare.java
+++ b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
@@ -15,6 +15,9 @@ private PerfectSquare() {
* false
*/
public static boolean isPerfectSquare(final int number) {
+ if (number < 0) {
+ return false;
+ }
final int sqrt = (int) Math.sqrt(number);
return sqrt * sqrt == number;
}
@@ -27,6 +30,9 @@ public static boolean isPerfectSquare(final int number) {
* {@code false}
*/
public static boolean isPerfectSquareUsingPow(long number) {
+ if (number < 0) {
+ return false;
+ }
long a = (long) Math.pow(number, 1.0 / 2);
return a * a == 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/prefixsum/PrefixSum.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java
new file mode 100644
index 000000000000..47f6366e2924
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * A class that implements the Prefix Sum algorithm.
+ *
+ * Prefix Sum is a technique used to preprocess an array such that
+ * range sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N) time.
+ *
+ *
This implementation uses a long array for the prefix sums to prevent
+ * integer overflow when the sum of elements exceeds Integer.MAX_VALUE.
+ *
+ * @see Prefix Sum (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum {
+
+ private final long[] prefixSums;
+
+ /**
+ * Constructor to preprocess the input array.
+ *
+ * @param array The input integer array.
+ * @throws IllegalArgumentException if the array is null.
+ */
+ public PrefixSum(int[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("Input array cannot be null");
+ }
+ this.prefixSums = new long[array.length + 1];
+ this.prefixSums[0] = 0;
+
+ for (int i = 0; i < array.length; i++) {
+ // Automatically promotes int to long during addition
+ this.prefixSums[i + 1] = this.prefixSums[i] + array[i];
+ }
+ }
+
+ /**
+ * Calculates the sum of elements in the range [left, right].
+ * Indices are 0-based.
+ *
+ * @param left The starting index (inclusive).
+ * @param right The ending index (inclusive).
+ * @return The sum of elements from index left to right as a long.
+ * @throws IndexOutOfBoundsException if indices are out of valid range.
+ */
+ public long sumRange(int left, int right) {
+ if (left < 0 || right >= prefixSums.length - 1 || left > right) {
+ throw new IndexOutOfBoundsException("Invalid range indices");
+ }
+ return prefixSums[right + 1] - prefixSums[left];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
new file mode 100644
index 000000000000..9c168bc6bcc4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * A class that implements the 2D Prefix Sum algorithm.
+ *
+ *
2D Prefix Sum is a technique used to preprocess a 2D matrix such that
+ * sub-matrix sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N*M) time.
+ *
+ *
This implementation uses a long array for the prefix sums to prevent
+ * integer overflow.
+ *
+ * @see Summed-area table (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum2D {
+
+ private final long[][] prefixSums;
+
+ /**
+ * Constructor to preprocess the input matrix.
+ *
+ * @param matrix The input integer matrix.
+ * @throws IllegalArgumentException if the matrix is null or empty.
+ */
+ public PrefixSum2D(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
+ throw new IllegalArgumentException("Input matrix cannot be null or empty");
+ }
+
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ this.prefixSums = new long[rows + 1][cols + 1];
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ // P[i+1][j+1] = current + above + left - diagonal_overlap
+ this.prefixSums[i + 1][j + 1] = matrix[i][j] + this.prefixSums[i][j + 1] + this.prefixSums[i + 1][j] - this.prefixSums[i][j];
+ }
+ }
+ }
+
+ /**
+ * Calculates the sum of the sub-matrix defined by (row1, col1) to (row2, col2).
+ * Indices are 0-based.
+ *
+ * @param row1 Top row index.
+ * @param col1 Left column index.
+ * @param row2 Bottom row index.
+ * @param col2 Right column index.
+ * @return The sum of the sub-matrix.
+ * @throws IndexOutOfBoundsException if indices are invalid.
+ */
+ public long sumRegion(int row1, int col1, int row2, int col2) {
+ if (row1 < 0 || row2 >= prefixSums.length - 1 || row2 < row1) {
+ throw new IndexOutOfBoundsException("Invalid row indices");
+ }
+ if (col1 < 0 || col2 >= prefixSums[0].length - 1 || col2 < col1) {
+ throw new IndexOutOfBoundsException("Invalid column indices");
+ }
+
+ return prefixSums[row2 + 1][col2 + 1] - prefixSums[row1][col2 + 1] - prefixSums[row2 + 1][col1] + prefixSums[row1][col1];
+ }
+}
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..9c809858099e 100644
--- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
+++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
@@ -1,21 +1,33 @@
package com.thealgorithms.recursion;
-/*
- The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones,
- starting with 0 and 1.
- NUMBER 0 1 2 3 4 5 6 7 8 9 10 ...
- FIBONACCI 0 1 1 2 3 5 8 13 21 34 55 ...
-*/
+/**
+ * The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones,
+ * starting with 0 and 1.
+ *
+ * Example:
+ * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...
+ *
+ */
public final class FibonacciSeries {
private FibonacciSeries() {
throw new UnsupportedOperationException("Utility class");
}
+
+ /**
+ * Calculates the nth term in the Fibonacci sequence using recursion.
+ *
+ * @param n the position in the Fibonacci sequence (must be non-negative)
+ * @return the nth Fibonacci number
+ * @throws IllegalArgumentException if n is negative
+ */
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/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java
index bedad1667f33..0cac484d56b4 100644
--- a/src/main/java/com/thealgorithms/searches/BinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java
@@ -5,7 +5,9 @@
/**
* Binary search is one of the most popular algorithms The algorithm finds the
* position of a target value within a sorted array
- *
+ * IMPORTANT
+ * This algorithm works correctly only if the input array is sorted
+ * in ascending order.
*
* Worst-case performance O(log n) Best-case performance O(1) Average
* performance O(log n) Worst-case space complexity O(1)
@@ -25,6 +27,9 @@ class BinarySearch implements SearchAlgorithm {
*/
@Override
public > int find(T[] array, T key) {
+ if (array == null || array.length == 0) {
+ return -1;
+ }
return search(array, key, 0, array.length - 1);
}
diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java
index c7b70edb5112..cb483d8dfedc 100644
--- a/src/main/java/com/thealgorithms/searches/LinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java
@@ -1,21 +1,26 @@
package com.thealgorithms.searches;
import com.thealgorithms.devutils.searches.SearchAlgorithm;
-
/**
- * Linear search is the easiest search algorithm It works with sorted and
- * unsorted arrays (an binary search works only with sorted array) This
- * algorithm just compares all elements of an array to find a value
+ * Linear Search is a simple searching algorithm that checks
+ * each element of the array sequentially until the target
+ * value is found or the array ends.
+ *
+ * It works for both sorted and unsorted arrays.
*
- *
- * Worst-case performance O(n) Best-case performance O(1) Average performance
- * O(n) Worst-case space complexity
+ * Time Complexity:
+ * - Best case: O(1)
+ * - Average case: O(n)
+ * - Worst case: O(n)
*
- * @author Varun Upadhyay (https://github.com/varunu28)
- * @author Podshivalov Nikita (https://github.com/nikitap492)
+ * Space Complexity: O(1)
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see BinarySearch
* @see SearchAlgorithm
*/
+
public class LinearSearch implements SearchAlgorithm {
/**
diff --git a/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java
new file mode 100644
index 000000000000..86099b2fa2fa
--- /dev/null
+++ b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.searches;
+
+import com.thealgorithms.devutils.searches.SearchAlgorithm;
+
+/**
+ * Searches for a key in a sorted array that has been rotated at an unknown pivot.
+ *
+ *
+ * Example:
+ * {@code [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]}
+ *
+ *
+ * This is a modified binary search. When the array contains no duplicates, the
+ * time complexity is {@code O(log n)}. With duplicates, the algorithm still
+ * works but may degrade to {@code O(n)} in the worst case.
+ *
+ * @see Search in rotated sorted array
+ * @see SearchAlgorithm
+ */
+public final class RotatedBinarySearch implements SearchAlgorithm {
+
+ @Override
+ public > int find(T[] array, T key) {
+ int left = 0;
+ int right = array.length - 1;
+
+ while (left <= right) {
+ int middle = (left + right) >>> 1;
+ int cmp = key.compareTo(array[middle]);
+ if (cmp == 0) {
+ return middle;
+ }
+
+ // Handle duplicates: if we cannot determine which side is sorted.
+ if (array[left].compareTo(array[middle]) == 0 && array[middle].compareTo(array[right]) == 0) {
+ left++;
+ right--;
+ continue;
+ }
+
+ // Left half is sorted.
+ if (array[left].compareTo(array[middle]) <= 0) {
+ if (array[left].compareTo(key) <= 0 && key.compareTo(array[middle]) < 0) {
+ right = middle - 1;
+ } else {
+ left = middle + 1;
+ }
+ } else {
+ // Right half is sorted.
+ if (array[middle].compareTo(key) < 0 && key.compareTo(array[right]) <= 0) {
+ left = middle + 1;
+ } else {
+ right = middle - 1;
+ }
+ }
+ }
+
+ return -1;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java
new file mode 100644
index 000000000000..46f8deeb58dd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java
@@ -0,0 +1,99 @@
+package com.thealgorithms.slidingwindow;
+
+/**
+ * Counts the number of "nice subarrays".
+ * A nice subarray is a contiguous subarray that contains exactly k odd numbers.
+ *
+ * This implementation uses the sliding window technique.
+ *
+ * Reference:
+ * https://leetcode.com/problems/count-number-of-nice-subarrays/
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(n)
+ */
+public final class CountNiceSubarrays {
+
+ // Private constructor to prevent instantiation
+ private CountNiceSubarrays() {
+ }
+
+ /**
+ * Returns the count of subarrays containing exactly k odd numbers.
+ *
+ * @param nums input array of integers
+ * @param k number of odd elements required in the subarray
+ * @return number of nice subarrays
+ */
+ public static int countNiceSubarrays(int[] nums, int k) {
+
+ int n = nums.length;
+
+ // Left pointer of the sliding window
+ int left = 0;
+
+ // Tracks number of odd elements in the current window
+ int oddCount = 0;
+
+ // Final answer: total number of nice subarrays
+ int result = 0;
+
+ /*
+ * memo[i] stores how many valid starting positions exist
+ * when the left pointer is at index i.
+ *
+ * This avoids recomputing the same values again.
+ */
+ int[] memo = new int[n];
+
+ // Right pointer moves forward to expand the window
+ for (int right = 0; right < n; right++) {
+
+ // If current element is odd, increment odd count
+ if ((nums[right] & 1) == 1) {
+ oddCount++;
+ }
+
+ /*
+ * If oddCount exceeds k, shrink the window from the left
+ * until oddCount becomes valid again.
+ */
+ if (oddCount > k) {
+ left += memo[left];
+ oddCount--;
+ }
+
+ /*
+ * When the window contains exactly k odd numbers,
+ * count all possible valid subarrays starting at `left`.
+ */
+ if (oddCount == k) {
+
+ /*
+ * If this left index hasn't been processed before,
+ * count how many consecutive even numbers follow it.
+ */
+ if (memo[left] == 0) {
+ int count = 0;
+ int temp = left;
+
+ // Count consecutive even numbers
+ while ((nums[temp] & 1) == 0) {
+ count++;
+ temp++;
+ }
+
+ /*
+ * Number of valid subarrays starting at `left`
+ * is (count of even numbers + 1)
+ */
+ memo[left] = count + 1;
+ }
+
+ // Add number of valid subarrays for this left position
+ result += memo[left];
+ }
+ }
+ return result;
+ }
+}
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/SmoothSort.java b/src/main/java/com/thealgorithms/sorts/SmoothSort.java
new file mode 100644
index 000000000000..c45d6f1f02b2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/SmoothSort.java
@@ -0,0 +1,168 @@
+package com.thealgorithms.sorts;
+
+/**
+ * Smooth Sort is an in-place, comparison-based sorting algorithm proposed by Edsger W. Dijkstra (1981).
+ *
+ * It can be viewed as a variant of heapsort that maintains a forest of heap-ordered Leonardo trees
+ * (trees whose sizes are Leonardo numbers). The algorithm is adaptive: when the input is already
+ * sorted or nearly sorted, the heap invariants are often satisfied and the expensive rebalancing
+ * operations do little work, yielding near-linear behavior.
+ *
+ *
Time Complexity:
+ *
+ * - Best case: O(n) for already sorted input
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ *
+ * Space Complexity: O(1) auxiliary space (in-place).
+ *
+ * @see Smoothsort
+ * @see Leonardo numbers
+ * @see SortAlgorithm
+ */
+public class SmoothSort implements SortAlgorithm {
+
+ /**
+ * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that
+ * fits into a signed 32-bit integer.
+ */
+ private static final int[] LEONARDO = {1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703, 48315633, 78176337,
+ 126491971, 204668309, 331160281, 535828591, 866988873, 1402817465};
+
+ /**
+ * Sorts the given array in ascending order using Smooth Sort.
+ *
+ * @param array the array to sort
+ * @param the element type
+ * @return the sorted array
+ */
+ @Override
+ public > T[] sort(final T[] array) {
+ if (array.length < 2) {
+ return array;
+ }
+
+ final int last = array.length - 1;
+
+ // The forest shape is encoded as (p, pshift): p is a bit-vector of present tree orders,
+ // shifted right by pshift. pshift is the order of the rightmost (current) Leonardo tree.
+ long p = 1L;
+ int pshift = 1;
+
+ int head = 0;
+ while (head < last) {
+ if ((p & 3L) == 3L) {
+ sift(array, pshift, head);
+ p >>>= 2;
+ pshift += 2;
+ } else {
+ // Add a new singleton tree; if it will not be merged anymore, we must fully trinkle.
+ if (LEONARDO[pshift - 1] >= last - head) {
+ trinkle(array, p, pshift, head, false);
+ } else {
+ // This tree will be merged later, so it is enough to restore its internal heap property.
+ sift(array, pshift, head);
+ }
+
+ if (pshift == 1) {
+ // If L(1) is used, the new singleton is L(0).
+ p <<= 1;
+ pshift = 0;
+ } else {
+ // Otherwise, shift to order 1 and append a singleton of order 1.
+ p <<= (pshift - 1);
+ pshift = 1;
+ }
+ }
+
+ p |= 1L;
+ head++;
+ }
+
+ trinkle(array, p, pshift, head, false);
+
+ // Repeatedly remove the maximum (always at head) by shrinking the heap region.
+ while (pshift != 1 || p != 1L) {
+ if (pshift <= 1) {
+ // Rightmost tree is a singleton (order 0 or 1). Move to the previous tree root.
+ final long mask = p & ~1L;
+ final int shift = Long.numberOfTrailingZeros(mask);
+ p >>>= shift;
+ pshift += shift;
+ } else {
+ // Split a tree of order (pshift) into two children trees of orders (pshift-1) and (pshift-2).
+ p <<= 2;
+ p ^= 7L;
+ pshift -= 2;
+
+ trinkle(array, p >>> 1, pshift + 1, head - LEONARDO[pshift] - 1, true);
+ trinkle(array, p, pshift, head - 1, true);
+ }
+
+ head--;
+ }
+
+ return array;
+ }
+
+ private static > void sift(final T[] array, int order, int root) {
+ final T value = array[root];
+
+ while (order > 1) {
+ final int right = root - 1;
+ final int left = root - 1 - LEONARDO[order - 2];
+
+ if (!SortUtils.less(value, array[left]) && !SortUtils.less(value, array[right])) {
+ break;
+ }
+
+ if (!SortUtils.less(array[left], array[right])) {
+ array[root] = array[left];
+ root = left;
+ order -= 1;
+ } else {
+ array[root] = array[right];
+ root = right;
+ order -= 2;
+ }
+ }
+
+ array[root] = value;
+ }
+
+ private static > void trinkle(final T[] array, long p, int order, int root, boolean trusty) {
+ final T value = array[root];
+
+ while (p != 1L) {
+ final int stepson = root - LEONARDO[order];
+
+ if (!SortUtils.less(value, array[stepson])) {
+ break;
+ }
+
+ if (!trusty && order > 1) {
+ final int right = root - 1;
+ final int left = root - 1 - LEONARDO[order - 2];
+
+ if (!SortUtils.less(array[right], array[stepson]) || !SortUtils.less(array[left], array[stepson])) {
+ break;
+ }
+ }
+
+ array[root] = array[stepson];
+ root = stepson;
+
+ final long mask = p & ~1L;
+ final int shift = Long.numberOfTrailingZeros(mask);
+ p >>>= shift;
+ order += shift;
+ trusty = false;
+ }
+
+ if (!trusty) {
+ array[root] = value;
+ sift(array, order, root);
+ }
+ }
+}
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/sorts/TournamentSort.java b/src/main/java/com/thealgorithms/sorts/TournamentSort.java
new file mode 100644
index 000000000000..ec51a1e2c0a9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/TournamentSort.java
@@ -0,0 +1,84 @@
+package com.thealgorithms.sorts;
+
+import java.util.Arrays;
+
+/**
+ * Tournament Sort algorithm implementation.
+ *
+ * Tournament sort builds a winner tree (a complete binary tree storing the index
+ * of the smallest element in each subtree). It then repeatedly extracts the
+ * winner (minimum) and updates the path from the removed leaf to the root.
+ *
+ * Time Complexity:
+ * - Best case: O(n log n)
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ * Space Complexity: O(n) – additional winner-tree storage
+ *
+ * @see Tournament Sort Algorithm
+ * @see SortAlgorithm
+ */
+public class TournamentSort implements SortAlgorithm {
+
+ @Override
+ public > T[] sort(T[] array) {
+ if (array == null || array.length < 2) {
+ return array;
+ }
+
+ final int n = array.length;
+ final int leafCount = nextPowerOfTwo(n);
+
+ // Winner tree represented as an array:
+ // - Leaves live at [leafCount .. 2*leafCount)
+ // - Internal nodes live at [1 .. leafCount)
+ // Each node stores an index into the original array or -1 for "empty".
+ final int[] tree = new int[2 * leafCount];
+ Arrays.fill(tree, -1);
+
+ for (int i = 0; i < n; i++) {
+ tree[leafCount + i] = i;
+ }
+
+ for (int node = leafCount - 1; node >= 1; node--) {
+ tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]);
+ }
+
+ final T[] result = array.clone();
+ for (int out = 0; out < n; out++) {
+ final int winner = tree[1];
+ result[out] = array[winner];
+
+ int node = leafCount + winner;
+ tree[node] = -1;
+
+ for (node /= 2; node >= 1; node /= 2) {
+ tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]);
+ }
+ }
+
+ System.arraycopy(result, 0, array, 0, n);
+ return array;
+ }
+
+ private static int nextPowerOfTwo(int n) {
+ int power = 1;
+ while (power < n) {
+ power <<= 1;
+ }
+ return power;
+ }
+
+ private static > int winnerIndex(T[] array, int leftIndex, int rightIndex) {
+ if (leftIndex == -1) {
+ return rightIndex;
+ }
+ if (rightIndex == -1) {
+ return leftIndex;
+ }
+
+ // If equal, prefer the left element to keep ordering deterministic.
+ return SortUtils.less(array[rightIndex], array[leftIndex]) ? rightIndex : leftIndex;
+ }
+}
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/ciphers/PermutationCipherTest.java b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
index 4ba6787cc97e..ecb7455c1ba2 100644
--- a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
+++ b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.ciphers;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
@@ -121,8 +122,8 @@ void testNullString() {
String decrypted = cipher.decrypt(encrypted, key);
// then
- assertEquals(null, encrypted);
- assertEquals(null, decrypted);
+ assertNull(encrypted);
+ assertNull(decrypted);
}
@Test
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/HeapElementTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
index d04a9de8a94b..792969200c82 100644
--- a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
@@ -2,6 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
@@ -39,7 +40,7 @@ void testEquals() {
assertEquals(element1, element2); // Same key and info
assertNotEquals(element1, element3); // Different key
- assertNotEquals(null, element1); // Check for null
+ assertNotNull(element1);
assertNotEquals("String", element1); // Check for different type
}
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/lists/MiddleOfLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java
new file mode 100644
index 000000000000..ba5614a07916
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java
@@ -0,0 +1,74 @@
+package com.thealgorithms.datastructures.lists;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Objects;
+import org.junit.jupiter.api.Test;
+
+public class MiddleOfLinkedListTest {
+
+ private static SinglyLinkedListNode listOf(int firstValue, int... remainingValues) {
+ SinglyLinkedListNode head = new SinglyLinkedListNode(firstValue);
+ SinglyLinkedListNode current = head;
+
+ for (int i = 0; i < remainingValues.length; i++) {
+ current.next = new SinglyLinkedListNode(remainingValues[i]);
+ current = current.next;
+ }
+ return head;
+ }
+
+ @Test
+ void middleNodeOddLength() {
+ SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(3, middle.value);
+ }
+
+ @Test
+ void middleNodeEvenLengthReturnsSecondMiddle() {
+ SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5, 6);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(4, middle.value);
+ }
+
+ @Test
+ void middleNodeSingleElement() {
+ SinglyLinkedListNode head = listOf(42);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(42, middle.value);
+ }
+
+ @Test
+ void middleNodeTwoElementsReturnsSecond() {
+ SinglyLinkedListNode head = listOf(10, 20);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(20, middle.value);
+ }
+
+ @Test
+ void middleNodeNullHead() {
+ assertNull(MiddleOfLinkedList.middleNode(null));
+ }
+
+ @Test
+ void middleNodeDoesNotModifyListStructure() {
+ SinglyLinkedListNode first = new SinglyLinkedListNode(1);
+ SinglyLinkedListNode second = new SinglyLinkedListNode(2);
+ SinglyLinkedListNode third = new SinglyLinkedListNode(3);
+ SinglyLinkedListNode fourth = new SinglyLinkedListNode(4);
+
+ first.next = second;
+ second.next = third;
+ third.next = fourth;
+
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(first));
+ assertEquals(3, middle.value);
+
+ assertEquals(second, first.next);
+ assertEquals(third, second.next);
+ assertEquals(fourth, third.next);
+ assertNull(fourth.next);
+ }
+}
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/datastructures/trees/TreapTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
index 09ada594faca..52b74a7a1faf 100644
--- a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
@@ -2,6 +2,7 @@
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 org.junit.jupiter.api.Test;
@@ -30,7 +31,7 @@ public void searchAndNotFound() {
treap.insert(3);
treap.insert(8);
treap.insert(1);
- assertEquals(null, treap.search(4));
+ assertNull(treap.search(4));
}
@Test
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
index 40bbdff15ca6..91169c4cc9d8 100644
--- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.dynamicprogramming;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
@@ -55,27 +56,24 @@ public void testLCSWithBothEmptyStrings() {
public void testLCSWithNullFirstString() {
String str1 = null;
String str2 = "XYZ";
- String expected = null; // Should return null if first string is null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
public void testLCSWithNullSecondString() {
String str1 = "ABC";
String str2 = null;
- String expected = null; // Should return null if second string is null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
public void testLCSWithNullBothStrings() {
String str1 = null;
String str2 = null;
- String expected = null; // Should return null if both strings are null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
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/BellNumbersTest.java b/src/test/java/com/thealgorithms/maths/BellNumbersTest.java
new file mode 100644
index 000000000000..8dd83cf0f7a9
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/BellNumbersTest.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class BellNumbersTest {
+
+ @Test
+ void testStandardCases() {
+ // Base cases and small numbers
+ assertEquals(1, BellNumbers.compute(0));
+ assertEquals(1, BellNumbers.compute(1));
+ assertEquals(2, BellNumbers.compute(2));
+ assertEquals(5, BellNumbers.compute(3));
+ assertEquals(15, BellNumbers.compute(4));
+ assertEquals(52, BellNumbers.compute(5));
+ }
+
+ @Test
+ void testMediumNumber() {
+ // B10 = 115,975
+ assertEquals(115975, BellNumbers.compute(10));
+ // B15 = 1,382,958,545
+ assertEquals(1382958545L, BellNumbers.compute(15));
+ }
+
+ @Test
+ void testLargeNumber() {
+ // B20 = 51,724,158,235,372
+ // We use the 'L' suffix to tell Java this is a long literal
+ assertEquals(51724158235372L, BellNumbers.compute(20));
+ }
+
+ @Test
+ void testMaxLongCapacity() {
+ // B25 is the largest Bell number that fits in a Java long (signed 64-bit)
+ // B25 = 4,638,590,332,229,999,353
+ assertEquals(4638590332229999353L, BellNumbers.compute(25));
+ }
+
+ @Test
+ void testNegativeInput() {
+ assertThrows(IllegalArgumentException.class, () -> BellNumbers.compute(-1));
+ }
+
+ @Test
+ void testOverflowProtection() {
+ // We expect an exception if the user asks for the impossible
+ assertThrows(IllegalArgumentException.class, () -> BellNumbers.compute(26));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java b/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java
new file mode 100644
index 000000000000..6bd124629740
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java
@@ -0,0 +1,23 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class DistanceBetweenTwoPointsTest {
+
+ @Test
+ void testDistanceSimple() {
+ assertEquals(5.0, DistanceBetweenTwoPoints.calculate(0, 0, 3, 4), 1e-9);
+ }
+
+ @Test
+ void testDistanceNegativeCoordinates() {
+ assertEquals(5.0, DistanceBetweenTwoPoints.calculate(-1, -1, 2, 3), 1e-9);
+ }
+
+ @Test
+ void testSamePoint() {
+ assertEquals(0.0, DistanceBetweenTwoPoints.calculate(2, 2, 2, 2), 1e-9);
+ }
+}
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/LinearDiophantineEquationsSolverTest.java b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java
index c4205985dbfd..885382e29ca2 100644
--- a/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java
+++ b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java
@@ -176,7 +176,7 @@ void testSolutionEquality() {
assertEquals(solution1, solution2);
assertNotEquals(solution3, solution1);
assertEquals(solution1, solution1);
- assertNotEquals(null, solution1);
+ assertNotNull(solution1);
assertNotEquals("string", solution1);
}
@@ -217,7 +217,7 @@ void testGcdSolutionWrapperEquality() {
assertEquals(wrapper1, wrapper2);
assertNotEquals(wrapper3, wrapper1);
assertEquals(wrapper1, wrapper1);
- assertNotEquals(null, wrapper1);
+ assertNotNull(wrapper1);
assertNotEquals("string", wrapper1);
}
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/prefixsum/PrefixSum2DTest.java b/src/test/java/com/thealgorithms/prefixsum/PrefixSum2DTest.java
new file mode 100644
index 000000000000..87feff859356
--- /dev/null
+++ b/src/test/java/com/thealgorithms/prefixsum/PrefixSum2DTest.java
@@ -0,0 +1,92 @@
+package com.thealgorithms.prefixsum;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class PrefixSum2DTest {
+
+ @Test
+ @DisplayName("Test basic 3x3 square matrix")
+ void testStandardSquare() {
+ int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ // Sum of top-left 2x2: {1,2, 4,5} -> 12
+ assertEquals(12L, ps.sumRegion(0, 0, 1, 1));
+ // Sum of bottom-right 2x2: {5,6, 8,9} -> 28
+ assertEquals(28L, ps.sumRegion(1, 1, 2, 2));
+ // Full matrix -> 45
+ assertEquals(45L, ps.sumRegion(0, 0, 2, 2));
+ }
+
+ @Test
+ @DisplayName("Test rectangular matrix (more cols than rows)")
+ void testRectangularWide() {
+ int[][] matrix = {{1, 1, 1, 1}, {2, 2, 2, 2}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ // Sum of first 3 columns of both rows -> (1*3) + (2*3) = 9
+ assertEquals(9L, ps.sumRegion(0, 0, 1, 2));
+ }
+
+ @Test
+ @DisplayName("Test rectangular matrix (more rows than cols)")
+ void testRectangularTall() {
+ int[][] matrix = {{1}, {2}, {3}, {4}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ // Sum of middle two elements -> 2+3 = 5
+ assertEquals(5L, ps.sumRegion(1, 0, 2, 0));
+ }
+
+ @Test
+ @DisplayName("Test single element matrix")
+ void testSingleElement() {
+ int[][] matrix = {{100}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ assertEquals(100L, ps.sumRegion(0, 0, 0, 0));
+ }
+
+ @Test
+ @DisplayName("Test large numbers for overflow (Integer -> Long)")
+ void testLargeNumbers() {
+ // 2 billion. Two of these sum to > MAX_INT
+ int val = 2_000_000_000;
+ int[][] matrix = {{val, val}, {val, val}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ // 4 * 2B = 8 Billion
+ assertEquals(8_000_000_000L, ps.sumRegion(0, 0, 1, 1));
+ }
+
+ @Test
+ @DisplayName("Test invalid inputs")
+ void testInvalidInputs() {
+ assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(null));
+ assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(new int[][] {})); // empty
+ assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(new int[][] {{}})); // empty row
+ }
+
+ @Test
+ @DisplayName("Test invalid query ranges")
+ void testInvalidRanges() {
+ int[][] matrix = {{1, 2}, {3, 4}};
+ PrefixSum2D ps = new PrefixSum2D(matrix);
+
+ // Negative indices
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(-1, 0, 0, 0));
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, -1, 0, 0));
+
+ // Out of bounds
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 0, 2, 0)); // row2 too big
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 0, 0, 2)); // col2 too big
+
+ // Inverted ranges (start > end)
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(1, 0, 0, 0)); // row1 > row2
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 1, 0, 0)); // col1 > col2
+ }
+}
diff --git a/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java b/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java
new file mode 100644
index 000000000000..a421b62e9306
--- /dev/null
+++ b/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java
@@ -0,0 +1,80 @@
+package com.thealgorithms.prefixsum;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class PrefixSumTest {
+
+ @Test
+ @DisplayName("Test basic sum with positive integers")
+ void testStandardCase() {
+ int[] input = {1, 2, 3, 4, 5};
+ PrefixSum ps = new PrefixSum(input);
+
+ // Sum of range [0, 4] -> 15
+ assertEquals(15L, ps.sumRange(0, 4));
+
+ // Sum of range [1, 3] -> 9
+ assertEquals(9L, ps.sumRange(1, 3));
+ }
+
+ @Test
+ @DisplayName("Test array with negative numbers and zeros")
+ void testNegativeAndZeros() {
+ int[] input = {-2, 0, 3, -5, 2, -1};
+ PrefixSum ps = new PrefixSum(input);
+
+ assertEquals(1L, ps.sumRange(0, 2));
+ assertEquals(-1L, ps.sumRange(2, 5));
+ assertEquals(0L, ps.sumRange(1, 1));
+ }
+
+ @Test
+ @DisplayName("Test with large integers to verify overflow handling")
+ void testLargeNumbers() {
+ // Two values that fit in int, but their sum exceeds Integer.MAX_VALUE
+ // Integer.MAX_VALUE is approx 2.14 billion.
+ int val = 2_000_000_000;
+ int[] input = {val, val, val};
+ PrefixSum ps = new PrefixSum(input);
+
+ // Sum of three 2 billion values is 6 billion (fits in long, overflows int)
+ assertEquals(6_000_000_000L, ps.sumRange(0, 2));
+ }
+
+ @Test
+ @DisplayName("Test single element array")
+ void testSingleElement() {
+ int[] input = {42};
+ PrefixSum ps = new PrefixSum(input);
+ assertEquals(42L, ps.sumRange(0, 0));
+ }
+
+ @Test
+ @DisplayName("Test constructor with null input")
+ void testNullInput() {
+ assertThrows(IllegalArgumentException.class, () -> new PrefixSum(null));
+ }
+
+ @Test
+ @DisplayName("Test empty array behavior")
+ void testEmptyArray() {
+ int[] input = {};
+ PrefixSum ps = new PrefixSum(input);
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(0, 0));
+ }
+
+ @Test
+ @DisplayName("Test invalid range indices")
+ void testInvalidIndices() {
+ int[] input = {10, 20, 30};
+ PrefixSum ps = new PrefixSum(input);
+
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(-1, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(0, 3));
+ assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(2, 1));
+ }
+}
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/searches/RotatedBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java
new file mode 100644
index 000000000000..1e6ab4c37fcc
--- /dev/null
+++ b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.searches;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class RotatedBinarySearchTest {
+
+ @Test
+ void shouldFindElementInRotatedArrayLeftSide() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7};
+ assertEquals(2, search.find(array, 10));
+ }
+
+ @Test
+ void shouldFindElementInRotatedArrayRightSide() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7};
+ assertEquals(6, search.find(array, 2));
+ }
+
+ @Test
+ void shouldFindElementInNotRotatedArray() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {1, 2, 3, 4, 5, 6, 7};
+ assertEquals(4, search.find(array, 5));
+ }
+
+ @Test
+ void shouldReturnMinusOneWhenNotFound() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {4, 5, 6, 7, 0, 1, 2};
+ assertEquals(-1, search.find(array, 3));
+ }
+
+ @Test
+ void shouldHandleWhenMiddleIsGreaterThanKeyInRightSortedHalf() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {6, 7, 0, 1, 2, 3, 4, 5};
+ assertEquals(2, search.find(array, 0));
+ }
+
+ @Test
+ void shouldHandleDuplicates() {
+ RotatedBinarySearch search = new RotatedBinarySearch();
+ Integer[] array = {2, 2, 2, 3, 4, 2};
+ int index = search.find(array, 3);
+ assertTrue(index >= 0 && index < array.length);
+ assertEquals(3, array[index]);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java b/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java
new file mode 100644
index 000000000000..71bf24cc9e30
--- /dev/null
+++ b/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.slidingwindow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class CountNiceSubarraysTest {
+ @Test
+ void testExampleCase() {
+ int[] nums = {1, 1, 2, 1, 1};
+ assertEquals(2, CountNiceSubarrays.countNiceSubarrays(nums, 3));
+ }
+
+ @Test
+ void testAllEvenNumbers() {
+ int[] nums = {2, 4, 6, 8};
+ assertEquals(0, CountNiceSubarrays.countNiceSubarrays(nums, 1));
+ }
+
+ @Test
+ void testSingleOdd() {
+ int[] nums = {1};
+ assertEquals(1, CountNiceSubarrays.countNiceSubarrays(nums, 1));
+ }
+
+ @Test
+ void testMultipleChoices() {
+ int[] nums = {2, 2, 1, 2, 2, 1, 2};
+ assertEquals(6, CountNiceSubarrays.countNiceSubarrays(nums, 2));
+ }
+
+ @Test
+ void testTrailingEvenNumbers() {
+ int[] nums = {1, 2, 2, 2};
+ assertEquals(4, CountNiceSubarrays.countNiceSubarrays(nums, 1));
+ }
+
+ @Test
+ void testMultipleWindowShrinks() {
+ int[] nums = {1, 1, 1, 1};
+ assertEquals(3, CountNiceSubarrays.countNiceSubarrays(nums, 2));
+ }
+
+ @Test
+ void testEvensBetweenOdds() {
+ int[] nums = {2, 1, 2, 1, 2};
+ assertEquals(4, CountNiceSubarrays.countNiceSubarrays(nums, 2));
+ }
+
+ @Test
+ void testShrinkWithTrailingEvens() {
+ int[] nums = {2, 2, 1, 2, 2, 1, 2, 2};
+ assertEquals(9, CountNiceSubarrays.countNiceSubarrays(nums, 2));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java
new file mode 100644
index 000000000000..8df0502e80e7
--- /dev/null
+++ b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java
@@ -0,0 +1,8 @@
+package com.thealgorithms.sorts;
+
+public class SmoothSortTest extends SortingAlgorithmTest {
+ @Override
+ SortAlgorithm getSortAlgorithm() {
+ return new SmoothSort();
+ }
+}
diff --git a/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java
new file mode 100644
index 000000000000..91da746447a8
--- /dev/null
+++ b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java
@@ -0,0 +1,19 @@
+package com.thealgorithms.sorts;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class TournamentSortTest extends SortingAlgorithmTest {
+
+ @Test
+ void shouldAcceptWhenNullArrayIsPassed() {
+ Integer[] array = null;
+ assertNull(getSortAlgorithm().sort(array));
+ }
+
+ @Override
+ SortAlgorithm getSortAlgorithm() {
+ return new TournamentSort();
+ }
+}
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"));
+ }
+}