com.h3xstream.findsecbugs
diff --git a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
index c35a36d97a57..269880b8ddae 100644
--- a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
+++ b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
@@ -4,11 +4,36 @@
import java.util.List;
/**
- * Program description - To find all possible paths from source to destination
- * Wikipedia
+ * Finds all possible simple paths from a given source vertex to a destination vertex
+ * in a directed graph using backtracking.
*
- * @author Siddhant Swarup Mallick
+ * This algorithm performs a Depth First Search (DFS) traversal while keeping track
+ * of visited vertices to avoid cycles. Whenever the destination vertex is reached,
+ * the current path is stored as one valid path.
+ *
+ * Key Characteristics:
+ *
+ * - Works on directed graphs
+ * - Does not allow revisiting vertices in the same path
+ * - Stores all possible paths from source to destination
+ *
+ *
+ * Time Complexity:
+ *
+ * - Worst Case: O(V!) β when the graph is fully connected
+ *
+ *
+ * Space Complexity:
+ *
+ * - O(V) for recursion stack and visited array
+ * - Additional space for storing all valid paths
+ *
+ *
+ * This implementation is intended for educational purposes.
+ *
+ * @see Depth First Search
*/
+
@SuppressWarnings({"rawtypes", "unchecked"})
public class AllPathsFromSourceToTarget {
diff --git a/src/main/java/com/thealgorithms/backtracking/Combination.java b/src/main/java/com/thealgorithms/backtracking/Combination.java
index ecaf7428f986..377d2c862d54 100644
--- a/src/main/java/com/thealgorithms/backtracking/Combination.java
+++ b/src/main/java/com/thealgorithms/backtracking/Combination.java
@@ -7,8 +7,7 @@
import java.util.TreeSet;
/**
- * Finds all permutations of given array
- * @author Alan Piao (git-Alan Piao)
+ * Finds all combinations of a given array using backtracking algorithm * @author Alan Piao (git-Alan Piao)
*/
public final class Combination {
private Combination() {
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/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/trees/BinaryTreeToString.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java
new file mode 100644
index 000000000000..2f9b3b489d56
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java
@@ -0,0 +1,100 @@
+package com.thealgorithms.datastructures.trees;
+
+/**
+ * Leetcode 606: Construct String from Binary Tree:
+ * https://leetcode.com/problems/construct-string-from-binary-tree/
+ *
+ * Utility class to convert a {@link BinaryTree} into its string representation.
+ *
+ * The conversion follows a preorder traversal pattern (root β left β right)
+ * and uses parentheses to denote the tree structure.
+ * Empty parentheses "()" are used to explicitly represent missing left children
+ * when a right child exists, ensuring the structure is unambiguous.
+ *
+ *
+ * Rules:
+ *
+ * - Each node is represented as {@code (value)}.
+ * - If a node has only a right child, include {@code ()} before the right
+ * child
+ * to indicate the missing left child.
+ * - If a node has no children, it appears as just {@code (value)}.
+ * - The outermost parentheses are removed from the final string.
+ *
+ *
+ * Example:
+ *
+ *
+ * Input tree:
+ * 1
+ * / \
+ * 2 3
+ * \
+ * 4
+ *
+ * Output string:
+ * "1(2()(4))(3)"
+ *
+ *
+ *
+ * This implementation matches the logic from LeetCode problem 606:
+ * Construct String from Binary Tree.
+ *
+ *
+ * @author Muhammad Junaid
+ * @see BinaryTree
+ */
+public class BinaryTreeToString {
+
+ /** String builder used to accumulate the string representation. */
+ private StringBuilder sb;
+
+ /**
+ * Converts a binary tree (given its root node) to its string representation.
+ *
+ * @param root the root node of the binary tree
+ * @return the string representation of the binary tree, or an empty string if
+ * the tree is null
+ */
+ public String tree2str(BinaryTree.Node root) {
+ if (root == null) {
+ return "";
+ }
+
+ sb = new StringBuilder();
+ dfs(root);
+
+ // Remove the leading and trailing parentheses added by the root call
+ return sb.substring(1, sb.length() - 1);
+ }
+
+ /**
+ * Performs a recursive depth-first traversal to build the string.
+ * Each recursive call appends the node value and its children (if any)
+ * enclosed in parentheses.
+ *
+ * @param node the current node being processed
+ */
+ private void dfs(BinaryTree.Node node) {
+ if (node == null) {
+ return;
+ }
+
+ sb.append("(").append(node.data);
+
+ // Recursively build left and right subtrees
+ if (node.left != null) {
+ dfs(node.left);
+ }
+
+ // Handle the special case: right child exists but left child is null
+ if (node.right != null && node.left == null) {
+ sb.append("()");
+ dfs(node.right);
+ } else if (node.right != null) {
+ dfs(node.right);
+ }
+
+ sb.append(")");
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java
new file mode 100644
index 000000000000..0b29dd6f5f5e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java
@@ -0,0 +1,217 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Centroid Decomposition is a divide-and-conquer technique for trees.
+ * It recursively partitions a tree by finding centroids - nodes whose removal
+ * creates balanced subtrees (each with at most N/2 nodes).
+ *
+ *
+ * Time Complexity: O(N log N) for construction
+ * Space Complexity: O(N)
+ *
+ *
+ * Applications:
+ * - Distance queries on trees
+ * - Path counting problems
+ * - Nearest neighbor searches
+ *
+ * @see Centroid Decomposition
+ * @see Centroid Decomposition Tutorial
+ * @author lens161
+ */
+public final class CentroidDecomposition {
+
+ private CentroidDecomposition() {
+ }
+
+ /**
+ * Represents the centroid tree structure.
+ */
+ public static final class CentroidTree {
+ private final int n;
+ private final List> adj;
+ private final int[] parent;
+ private final int[] subtreeSize;
+ private final boolean[] removed;
+ private int root;
+
+ /**
+ * Constructs a centroid tree from an adjacency list.
+ *
+ * @param adj adjacency list representation of the tree (0-indexed)
+ * @throws IllegalArgumentException if tree is empty or null
+ */
+ public CentroidTree(List> adj) {
+ if (adj == null || adj.isEmpty()) {
+ throw new IllegalArgumentException("Tree cannot be empty or null");
+ }
+
+ this.n = adj.size();
+ this.adj = adj;
+ this.parent = new int[n];
+ this.subtreeSize = new int[n];
+ this.removed = new boolean[n];
+ Arrays.fill(parent, -1);
+
+ // Build centroid tree starting from node 0
+ this.root = decompose(0, -1);
+ }
+
+ /**
+ * Recursively builds the centroid tree.
+ *
+ * @param u current node
+ * @param p parent in centroid tree
+ * @return centroid of current component
+ */
+ private int decompose(int u, int p) {
+ int size = getSubtreeSize(u, -1);
+ int centroid = findCentroid(u, -1, size);
+
+ removed[centroid] = true;
+ parent[centroid] = p;
+
+ // Recursively decompose each subtree
+ for (int v : adj.get(centroid)) {
+ if (!removed[v]) {
+ decompose(v, centroid);
+ }
+ }
+
+ return centroid;
+ }
+
+ /**
+ * Calculates subtree size from node u.
+ *
+ * @param u current node
+ * @param p parent node (-1 for root)
+ * @return size of subtree rooted at u
+ */
+ private int getSubtreeSize(int u, int p) {
+ subtreeSize[u] = 1;
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v]) {
+ subtreeSize[u] += getSubtreeSize(v, u);
+ }
+ }
+ return subtreeSize[u];
+ }
+
+ /**
+ * Finds the centroid of a subtree.
+ * A centroid is a node whose removal creates components with size <= totalSize/2.
+ *
+ * @param u current node
+ * @param p parent node
+ * @param totalSize total size of current component
+ * @return centroid node
+ */
+ private int findCentroid(int u, int p, int totalSize) {
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v] && subtreeSize[v] > totalSize / 2) {
+ return findCentroid(v, u, totalSize);
+ }
+ }
+ return u;
+ }
+
+ /**
+ * Gets the parent of a node in the centroid tree.
+ *
+ * @param node the node
+ * @return parent node in centroid tree, or -1 if root
+ */
+ public int getParent(int node) {
+ if (node < 0 || node >= n) {
+ throw new IllegalArgumentException("Invalid node: " + node);
+ }
+ return parent[node];
+ }
+
+ /**
+ * Gets the root of the centroid tree.
+ *
+ * @return root node
+ */
+ public int getRoot() {
+ return root;
+ }
+
+ /**
+ * Gets the number of nodes in the tree.
+ *
+ * @return number of nodes
+ */
+ public int size() {
+ return n;
+ }
+
+ /**
+ * Returns the centroid tree structure as a string.
+ * Format: node -> parent (or ROOT for root node)
+ *
+ * @return string representation
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Centroid Tree:\n");
+ for (int i = 0; i < n; i++) {
+ sb.append("Node ").append(i).append(" -> ");
+ if (parent[i] == -1) {
+ sb.append("ROOT");
+ } else {
+ sb.append("Parent ").append(parent[i]);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Creates a centroid tree from an edge list.
+ *
+ * @param n number of nodes (0-indexed: 0 to n-1)
+ * @param edges list of edges where each edge is [u, v]
+ * @return CentroidTree object
+ * @throws IllegalArgumentException if n <= 0 or edges is invalid
+ */
+ public static CentroidTree buildFromEdges(int n, int[][] edges) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("Number of nodes must be positive");
+ }
+ if (edges == null) {
+ throw new IllegalArgumentException("Edges cannot be null");
+ }
+ if (edges.length != n - 1) {
+ throw new IllegalArgumentException("Tree must have exactly n-1 edges");
+ }
+
+ List> adj = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ adj.add(new ArrayList<>());
+ }
+
+ for (int[] edge : edges) {
+ if (edge.length != 2) {
+ throw new IllegalArgumentException("Each edge must have exactly 2 nodes");
+ }
+ int u = edge[0];
+ int v = edge[1];
+
+ if (u < 0 || u >= n || v < 0 || v >= n) {
+ throw new IllegalArgumentException("Invalid node in edge: [" + u + ", " + v + "]");
+ }
+
+ adj.get(u).add(v);
+ adj.get(v).add(u);
+ }
+
+ return new CentroidTree(adj);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
new file mode 100644
index 000000000000..fd8876cecb70
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
@@ -0,0 +1,145 @@
+/*
+ * TheAlgorithms (https://github.com/TheAlgorithms/Java)
+ * Author: Shewale41
+ * This file is licensed under the MIT License.
+ */
+
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Threaded binary tree implementation that supports insertion and
+ * in-order traversal without recursion or stack by using threads.
+ *
+ * In this implementation, a node's null left/right pointers are used
+ * to point to the in-order predecessor/successor respectively. Two flags
+ * indicate whether left/right pointers are real children or threads.
+ *
+ * @see Wikipedia:
+ * Threaded binary tree
+ */
+public final class ThreadedBinaryTree {
+
+ private Node root;
+
+ private static final class Node {
+ int value;
+ Node left;
+ Node right;
+ boolean leftIsThread;
+ boolean rightIsThread;
+
+ Node(int value) {
+ this.value = value;
+ this.left = null;
+ this.right = null;
+ this.leftIsThread = false;
+ this.rightIsThread = false;
+ }
+ }
+
+ public ThreadedBinaryTree() {
+ this.root = null;
+ }
+
+ /**
+ * Inserts a value into the threaded binary tree. Duplicate values are inserted
+ * to the right subtree (consistent deterministic rule).
+ *
+ * @param value the integer value to insert
+ */
+ public void insert(int value) {
+ Node newNode = new Node(value);
+ if (root == null) {
+ root = newNode;
+ return;
+ }
+
+ Node current = root;
+ Node parent = null;
+
+ while (true) {
+ parent = current;
+ if (value < current.value) {
+ if (!current.leftIsThread && current.left != null) {
+ current = current.left;
+ } else {
+ break;
+ }
+ } else { // value >= current.value
+ if (!current.rightIsThread && current.right != null) {
+ current = current.right;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (value < parent.value) {
+ // attach newNode as left child
+ newNode.left = parent.left;
+ newNode.leftIsThread = parent.leftIsThread;
+ newNode.right = parent;
+ newNode.rightIsThread = true;
+
+ parent.left = newNode;
+ parent.leftIsThread = false;
+ } else {
+ // attach newNode as right child
+ newNode.right = parent.right;
+ newNode.rightIsThread = parent.rightIsThread;
+ newNode.left = parent;
+ newNode.leftIsThread = true;
+
+ parent.right = newNode;
+ parent.rightIsThread = false;
+ }
+ }
+
+ /**
+ * Returns the in-order traversal of the tree as a list of integers.
+ * Traversal is done without recursion or an explicit stack by following threads.
+ *
+ * @return list containing the in-order sequence of node values
+ */
+ public List inorderTraversal() {
+ List result = new ArrayList<>();
+ Node current = root;
+ if (current == null) {
+ return result;
+ }
+
+ // Move to the leftmost node
+ while (current.left != null && !current.leftIsThread) {
+ current = current.left;
+ }
+
+ while (current != null) {
+ result.add(current.value);
+
+ // If right pointer is a thread, follow it
+ if (current.rightIsThread) {
+ current = current.right;
+ } else {
+ // Move to leftmost node in right subtree
+ current = current.right;
+ while (current != null && !current.leftIsThread && current.left != null) {
+ current = current.left;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Helper: checks whether the tree is empty.
+ *
+ * @return true if tree has no nodes
+ */
+ public boolean isEmpty() {
+ return root == null;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
index 134561766830..0d4c8d501f9f 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
@@ -3,53 +3,76 @@
import java.util.Arrays;
/**
- * A Dynamic Programming based solution for the 0-1 Knapsack problem.
- * This class provides a method, `knapSack`, that calculates the maximum value that can be
- * obtained from a given set of items with weights and values, while not exceeding a
- * given weight capacity.
+ * 0/1 Knapsack Problem - Dynamic Programming solution.
*
- * @see 0-1 Knapsack Problem
+ * This algorithm solves the classic optimization problem where we have n items,
+ * each with a weight and a value. The goal is to maximize the total value
+ * without exceeding the knapsack's weight capacity.
+ *
+ * Time Complexity: O(n * W)
+ * Space Complexity: O(W)
+ *
+ * Example:
+ * values = {60, 100, 120}
+ * weights = {10, 20, 30}
+ * W = 50
+ * Output: 220
+ *
+ * @author Arpita
+ * @see Knapsack Problem
*/
public final class Knapsack {
private Knapsack() {
}
+ /**
+ * Validates the input to ensure correct constraints.
+ */
private static void throwIfInvalidInput(final int weightCapacity, final int[] weights, final int[] values) {
if (weightCapacity < 0) {
throw new IllegalArgumentException("Weight capacity should not be negative.");
}
if (weights == null || values == null || weights.length != values.length) {
- throw new IllegalArgumentException("Input arrays must not be null and must have the same length.");
+ throw new IllegalArgumentException("Weights and values must be non-null and of the same length.");
}
if (Arrays.stream(weights).anyMatch(w -> w <= 0)) {
- throw new IllegalArgumentException("Input array should not contain non-positive weight(s).");
+ throw new IllegalArgumentException("Weights must be positive.");
}
}
/**
- * Solves the 0-1 Knapsack problem using Dynamic Programming.
+ * Solves the 0/1 Knapsack problem using Dynamic Programming (bottom-up approach).
*
* @param weightCapacity The maximum weight capacity of the knapsack.
- * @param weights An array of item weights.
- * @param values An array of item values.
- * @return The maximum value that can be obtained without exceeding the weight capacity.
- * @throws IllegalArgumentException If the input arrays are null or have different lengths.
+ * @param weights The array of item weights.
+ * @param values The array of item values.
+ * @return The maximum total value achievable without exceeding capacity.
*/
- public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) throws IllegalArgumentException {
+ public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) {
throwIfInvalidInput(weightCapacity, weights, values);
- // DP table to store the state of the maximum possible return for a given weight capacity.
int[] dp = new int[weightCapacity + 1];
+ // Fill dp[] array iteratively
for (int i = 0; i < values.length; i++) {
- for (int w = weightCapacity; w > 0; w--) {
- if (weights[i] <= w) {
- dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
- }
+ for (int w = weightCapacity; w >= weights[i]; w--) {
+ dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[weightCapacity];
}
+
+ /*
+ // Example main method for local testing only.
+ public static void main(String[] args) {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int weightCapacity = 50;
+
+ int maxValue = knapSack(weightCapacity, weights, values);
+ System.out.println("Maximum value = " + maxValue); // Output: 220
+ }
+ */
}
diff --git a/src/main/java/com/thealgorithms/graph/GomoryHuTree.java b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java
new file mode 100644
index 000000000000..f8c110f25571
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java
@@ -0,0 +1,144 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
+
+/**
+ * GomoryβHu tree construction for undirected graphs via nβ1 max-flow computations.
+ *
+ * API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
+ *
+ * @see Wikipedia: GomoryβHu tree
+ */
+
+public final class GomoryHuTree {
+ private GomoryHuTree() {
+ }
+
+ public static int[][] buildTree(int[][] cap) {
+ validateCapacityMatrix(cap);
+ final int n = cap.length;
+ if (n == 1) {
+ return new int[][] {new int[] {-1}, new int[] {0}};
+ }
+
+ int[] parent = new int[n];
+ int[] weight = new int[n];
+ Arrays.fill(parent, 0);
+ parent[0] = -1;
+ weight[0] = 0;
+
+ for (int s = 1; s < n; s++) {
+ int t = parent[s];
+ MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t);
+ int f = res.flow;
+ weight[s] = f;
+
+ for (int v = 0; v < n; v++) {
+ if (v != s && parent[v] == t && res.reachable[v]) {
+ parent[v] = s;
+ }
+ }
+
+ if (t != 0 && res.reachable[parent[t]]) {
+ parent[s] = parent[t];
+ parent[t] = s;
+ weight[s] = weight[t];
+ weight[t] = f;
+ }
+ }
+ return new int[][] {parent, weight};
+ }
+
+ private static void validateCapacityMatrix(int[][] cap) {
+ if (cap == null || cap.length == 0) {
+ throw new IllegalArgumentException("Capacity matrix must not be null or empty");
+ }
+ final int n = cap.length;
+ for (int i = 0; i < n; i++) {
+ if (cap[i] == null || cap[i].length != n) {
+ throw new IllegalArgumentException("Capacity matrix must be square");
+ }
+ for (int j = 0; j < n; j++) {
+ if (cap[i][j] < 0) {
+ throw new IllegalArgumentException("Capacities must be non-negative");
+ }
+ }
+ }
+ }
+
+ private static final class MaxFlowResult {
+ final int flow;
+ final boolean[] reachable;
+ MaxFlowResult(int flow, boolean[] reachable) {
+ this.flow = flow;
+ this.reachable = reachable;
+ }
+ }
+
+ private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) {
+ final int n = capacity.length;
+ int[][] residual = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ residual[i] = Arrays.copyOf(capacity[i], n);
+ }
+
+ int[] parent = new int[n];
+ int maxFlow = 0;
+
+ while (bfs(residual, source, sink, parent)) {
+ int pathFlow = Integer.MAX_VALUE;
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ pathFlow = Math.min(pathFlow, residual[u][v]);
+ }
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ residual[u][v] -= pathFlow;
+ residual[v][u] += pathFlow;
+ }
+ maxFlow += pathFlow;
+ }
+
+ boolean[] reachable = new boolean[n];
+ markReachable(residual, source, reachable);
+ return new MaxFlowResult(maxFlow, reachable);
+ }
+
+ private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
+ Arrays.fill(parent, -1);
+ parent[source] = source;
+ Queue q = new ArrayDeque<>();
+ q.add(source);
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ for (int v = 0; v < residual.length; v++) {
+ if (residual[u][v] > 0 && parent[v] == -1) {
+ parent[v] = u;
+ if (v == sink) {
+ return true;
+ }
+ q.add(v);
+ }
+ }
+ }
+ return false;
+ }
+
+ private static void markReachable(int[][] residual, int source, boolean[] vis) {
+ Arrays.fill(vis, false);
+ Queue q = new ArrayDeque<>();
+ vis[source] = true;
+ q.add(source);
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ for (int v = 0; v < residual.length; v++) {
+ if (!vis[v] && residual[u][v] > 0) {
+ vis[v] = true;
+ q.add(v);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbundantNumber.java b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
new file mode 100644
index 000000000000..804ac4d71477
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an abundant number or excessive number is a positive integer for which
+ * the sum of its proper divisors is greater than the number.
+ * Equivalently, it is a number for which the sum of proper divisors (or aliquot sum) is greater than n.
+ *
+ * The integer 12 is the first abundant number. Its proper divisors are 1, 2, 3, 4 and 6 for a total of 16.
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Abundant_number
+ */
+public final class AbundantNumber {
+
+ private AbundantNumber() {
+ }
+
+ // Function to calculate sum of all divisors including n
+ private static int sumOfDivisors(int n) {
+ int sum = 1 + n; // 1 and n are always divisors
+ for (int i = 2; i <= n / 2; i++) {
+ if (n % i == 0) {
+ sum += i; // adding divisor to sum
+ }
+ }
+ return sum;
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking sum of divisors > 2n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Abundant number, otherwise false
+ */
+ public static boolean isAbundant(int number) {
+ validatePositiveNumber(number);
+
+ return sumOfDivisors(number) > 2 * number;
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking Aliquot Sum > n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Abundant number, otherwise false
+ */
+ public static boolean isAbundantNumber(int number) {
+ validatePositiveNumber(number);
+
+ return AliquotSum.getAliquotSum(number) > number;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java
index a34ad6b01ab5..1eba6666dde3 100644
--- a/src/main/java/com/thealgorithms/maths/Area.java
+++ b/src/main/java/com/thealgorithms/maths/Area.java
@@ -96,7 +96,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh
throw new IllegalArgumentException(POSITIVE_RADIUS);
}
if (height <= 0) {
- throw new IllegalArgumentException(POSITIVE_RADIUS);
+ throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
return 2 * (Math.PI * radius * radius + Math.PI * radius * height);
}
diff --git a/src/main/java/com/thealgorithms/maths/EvilNumber.java b/src/main/java/com/thealgorithms/maths/EvilNumber.java
new file mode 100644
index 000000000000..419133702fd4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/EvilNumber.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an evil number is a non-negative integer that has an even number of 1s in its binary expansion.
+ * Non-negative integers that are not evil are called odious numbers.
+ *
+ * Evil Number Wiki: https://en.wikipedia.org/wiki/Evil_number
+ * Odious Number Wiki: https://en.wikipedia.org/wiki/Odious_number
+ */
+public final class EvilNumber {
+
+ private EvilNumber() {
+ }
+
+ // Function to count number of one bits in a number using bitwise operators
+ private static int countOneBits(int number) {
+ int oneBitCounter = 0;
+ while (number > 0) {
+ oneBitCounter += number & 1; // increment count if last bit is 1
+ number >>= 1; // right shift to next bit
+ }
+ return oneBitCounter;
+ }
+
+ /**
+ * Check either {@code number} is an Evil number or Odious number
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Evil number, otherwise false (in case of of Odious number)
+ */
+ public static boolean isEvilNumber(int number) {
+ if (number < 0) {
+ throw new IllegalArgumentException("Negative numbers are not allowed.");
+ }
+
+ int noOfOneBits = countOneBits(number);
+ return noOfOneBits % 2 == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
new file mode 100644
index 000000000000..4934d4493bf2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.maths;
+
+/**
+ * In mathematics, the extended Euclidean algorithm is an extension to the
+ * Euclidean algorithm, and computes, in addition to the greatest common divisor
+ * (gcd) of integers a and b, also the coefficients of BΓ©zout's identity, which
+ * are integers x and y such that ax + by = gcd(a, b).
+ *
+ *
+ * For more details, see
+ * https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
+ */
+public final class ExtendedEuclideanAlgorithm {
+
+ private ExtendedEuclideanAlgorithm() {
+ }
+
+ /**
+ * This method implements the extended Euclidean algorithm.
+ *
+ * @param a The first number.
+ * @param b The second number.
+ * @return An array of three integers:
+ *
+ * - Index 0: The greatest common divisor (gcd) of a and b.
+ * - Index 1: The value of x in the equation ax + by = gcd(a, b).
+ * - Index 2: The value of y in the equation ax + by = gcd(a, b).
+ *
+ */
+ public static long[] extendedGCD(long a, long b) {
+ if (b == 0) {
+ // Base case: gcd(a, 0) = a. The equation is a*1 + 0*0 = a.
+ return new long[] {a, 1, 0};
+ }
+
+ // Recursive call
+ long[] result = extendedGCD(b, a % b);
+ long gcd = result[0];
+ long x1 = result[1];
+ long y1 = result[2];
+
+ // Update coefficients using the results from the recursive call
+ long x = y1;
+ long y = x1 - a / b * y1;
+
+ return new long[] {gcd, x, y};
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/LuckyNumber.java b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
new file mode 100644
index 000000000000..70308e1e0edd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, a lucky number is a natural number in a set which is generated by a certain "sieve".
+ * This sieve is similar to the sieve of Eratosthenes that generates the primes,
+ * but it eliminates numbers based on their position in the remaining set,
+ * instead of their value (or position in the initial set of natural numbers).
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Lucky_number
+ */
+public final class LuckyNumber {
+
+ private LuckyNumber() {
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ // Function to check recursively for Lucky Number
+ private static boolean isLuckyRecursiveApproach(int n, int counter) {
+ // Base case: If counter exceeds n, number is lucky
+ if (counter > n) {
+ return true;
+ }
+
+ // If number is eliminated in this step, it's not lucky
+ if (n % counter == 0) {
+ return false;
+ }
+
+ // Calculate new position after removing every counter-th number
+ int newNumber = n - (n / counter);
+
+ // Recursive call for next round
+ return isLuckyRecursiveApproach(newNumber, counter + 1);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using recursive approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLuckyNumber(int number) {
+ validatePositiveNumber(number);
+ int counterStarting = 2;
+ return isLuckyRecursiveApproach(number, counterStarting);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using iterative approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLucky(int number) {
+ validatePositiveNumber(number);
+
+ int counter = 2; // Position starts from 2 (since first elimination happens at 2)
+ int position = number; // The position of the number in the sequence
+
+ while (counter <= position) {
+ if (position % counter == 0) {
+ return false;
+ } // Number is eliminated
+
+ // Update the position of n after removing every counter-th number
+ position = position - (position / counter);
+ counter++;
+ }
+
+ return true; // Survives all eliminations β Lucky Number
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Perimeter.java b/src/main/java/com/thealgorithms/maths/Perimeter.java
index f8aa1876d388..670851eb346b 100644
--- a/src/main/java/com/thealgorithms/maths/Perimeter.java
+++ b/src/main/java/com/thealgorithms/maths/Perimeter.java
@@ -27,7 +27,7 @@ public static float perimeterRegularPolygon(int n, float side) {
* @param side2 for length of side 2
* @param side3 for length of side 3
* @param sides for length of remaining sides
- * @return Perimeter of given trapezoid.
+ * @return Perimeter of given irregular polygon.
*/
public static float perimeterIrregularPolygon(float side1, float side2, float side3, float... sides) {
float perimeter = side1 + side2 + side3;
diff --git a/src/main/java/com/thealgorithms/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/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
index 05d7abbbcd6c..06101295e880 100644
--- a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
+++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
@@ -64,13 +64,21 @@ private static double doApproximate(Function fx, double a, doubl
if (!validate(fx, a, b, n)) {
throw new IllegalArgumentException("Invalid input parameters");
}
- double totalArea = 0.0;
+ double total = 0.0;
double interval = b - a;
- for (int i = 0; i < n; i++) {
+ int pairs = n / 2;
+ for (int i = 0; i < pairs; i++) {
+ double u = generator.nextDouble();
+ double x1 = a + u * interval;
+ double x2 = a + (1.0 - u) * interval;
+ total += fx.apply(x1);
+ total += fx.apply(x2);
+ }
+ if ((n & 1) == 1) {
double x = a + generator.nextDouble() * interval;
- totalArea += fx.apply(x);
+ total += fx.apply(x);
}
- return interval * totalArea / n;
+ return interval * total / n;
}
private static boolean validate(Function fx, double a, double b, int n) {
diff --git a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
similarity index 92%
rename from src/main/java/com/thealgorithms/maths/FactorialRecursion.java
rename to src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
index d9bafd1e39e9..673f216bdc9a 100644
--- a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java
+++ b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.recursion;
public final class FactorialRecursion {
private FactorialRecursion() {
diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
index e5f474085367..9bc6da2f7443 100644
--- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
+++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
@@ -12,10 +12,12 @@ private FibonacciSeries() {
throw new UnsupportedOperationException("Utility class");
}
public static int fibonacci(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be a non-negative integer");
+ }
if (n <= 1) {
return n;
- } else {
- return fibonacci(n - 1) + fibonacci(n - 2);
}
+ return fibonacci(n - 1) + fibonacci(n - 2);
}
}
diff --git a/src/main/java/com/thealgorithms/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/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/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/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/trees/BinaryTreeToStringTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java
new file mode 100644
index 000000000000..2461fd74143d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.datastructures.trees;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for the BinaryTreeToString class.
+ */
+public class BinaryTreeToStringTest {
+
+ @Test
+ public void testTreeToStringBasic() {
+ BinaryTree tree = new BinaryTree();
+ tree.put(1);
+ tree.put(2);
+ tree.put(3);
+ tree.put(4);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(tree.getRoot());
+
+ // Output will depend on insertion logic of BinaryTree.put()
+ // which is BST-style, so result = "1()(2()(3()(4)))"
+ Assertions.assertEquals("1()(2()(3()(4)))", result);
+ }
+
+ @Test
+ public void testSingleNodeTree() {
+ BinaryTree tree = new BinaryTree();
+ tree.put(10);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(tree.getRoot());
+
+ Assertions.assertEquals("10", result);
+ }
+
+ @Test
+ public void testComplexTreeStructure() {
+ BinaryTree.Node root = new BinaryTree.Node(10);
+ root.left = new BinaryTree.Node(5);
+ root.right = new BinaryTree.Node(20);
+ root.right.left = new BinaryTree.Node(15);
+ root.right.right = new BinaryTree.Node(25);
+
+ BinaryTreeToString converter = new BinaryTreeToString();
+ String result = converter.tree2str(root);
+
+ Assertions.assertEquals("10(5)(20(15)(25))", result);
+ }
+
+ @Test
+ public void testNullTree() {
+ BinaryTreeToString converter = new BinaryTreeToString();
+ Assertions.assertEquals("", converter.tree2str(null));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java
new file mode 100644
index 000000000000..43d732e54f34
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java
@@ -0,0 +1,236 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test cases for CentroidDecomposition
+ *
+ * @author lens161
+ */
+class CentroidDecompositionTest {
+
+ @Test
+ void testSingleNode() {
+ // Tree with just one node
+ int[][] edges = {};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(1, edges);
+
+ assertEquals(1, tree.size());
+ assertEquals(0, tree.getRoot());
+ assertEquals(-1, tree.getParent(0));
+ }
+
+ @Test
+ void testTwoNodes() {
+ // Simple tree: 0 - 1
+ int[][] edges = {{0, 1}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(2, edges);
+
+ assertEquals(2, tree.size());
+ int root = tree.getRoot();
+ assertTrue(root == 0 || root == 1, "Root should be either node 0 or 1");
+
+ // One node should be root, other should have the root as parent
+ int nonRoot = (root == 0) ? 1 : 0;
+ assertEquals(-1, tree.getParent(root));
+ assertEquals(root, tree.getParent(nonRoot));
+ }
+
+ @Test
+ void testLinearTree() {
+ // Linear tree: 0 - 1 - 2 - 3 - 4
+ int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // For a linear tree of 5 nodes, the centroid should be the middle node (node 2)
+ assertEquals(2, tree.getRoot());
+ assertEquals(-1, tree.getParent(2));
+ }
+
+ @Test
+ void testBalancedBinaryTree() {
+ // Balanced binary tree:
+ // 0
+ // / \
+ // 1 2
+ // / \
+ // 3 4
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // Root should be 0 or 1 (both are valid centroids)
+ int root = tree.getRoot();
+ assertTrue(root == 0 || root == 1);
+ assertEquals(-1, tree.getParent(root));
+
+ // All nodes should have a parent in centroid tree except root
+ for (int i = 0; i < 5; i++) {
+ if (i != root) {
+ assertTrue(tree.getParent(i) >= 0 && tree.getParent(i) < 5);
+ }
+ }
+ }
+
+ @Test
+ void testStarTree() {
+ // Star tree: center node 0 connected to 1, 2, 3, 4
+ int[][] edges = {{0, 1}, {0, 2}, {0, 3}, {0, 4}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges);
+
+ assertEquals(5, tree.size());
+ // Center node (0) should be the root
+ assertEquals(0, tree.getRoot());
+
+ // All other nodes should have 0 as parent
+ for (int i = 1; i < 5; i++) {
+ assertEquals(0, tree.getParent(i));
+ }
+ }
+
+ @Test
+ void testCompleteTree() {
+ // Complete binary tree of 7 nodes:
+ // 0
+ // / \
+ // 1 2
+ // / \ / \
+ // 3 4 5 6
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(7, edges);
+
+ assertEquals(7, tree.size());
+ assertEquals(0, tree.getRoot()); // Root should be the center
+
+ // Verify all nodes are reachable in centroid tree
+ boolean[] visited = new boolean[7];
+ visited[0] = true;
+ for (int i = 1; i < 7; i++) {
+ int parent = tree.getParent(i);
+ assertTrue(parent >= 0 && parent < 7);
+ assertTrue(visited[parent], "Parent should be processed before child");
+ visited[i] = true;
+ }
+ }
+
+ @Test
+ void testLargerTree() {
+ // Tree with 10 nodes
+ int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}, {3, 7}, {4, 8}, {5, 9}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(10, edges);
+
+ assertEquals(10, tree.size());
+ int root = tree.getRoot();
+ assertTrue(root >= 0 && root < 10);
+ assertEquals(-1, tree.getParent(root));
+
+ // Verify centroid tree structure is valid
+ for (int i = 0; i < 10; i++) {
+ if (i != root) {
+ assertTrue(tree.getParent(i) >= -1 && tree.getParent(i) < 10);
+ }
+ }
+ }
+
+ @Test
+ void testPathGraph() {
+ // Path graph with 8 nodes: 0-1-2-3-4-5-6-7
+ int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}, {6, 7}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(8, edges);
+
+ assertEquals(8, tree.size());
+ // For path of 8 nodes, centroid should be around middle
+ int root = tree.getRoot();
+ assertTrue(root >= 2 && root <= 5, "Root should be near the middle of path");
+ }
+
+ @Test
+ void testInvalidEmptyTree() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(0, new int[][] {}); });
+ }
+
+ @Test
+ void testInvalidNegativeNodes() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(-1, new int[][] {}); });
+ }
+
+ @Test
+ void testInvalidNullEdges() {
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, null); });
+ }
+
+ @Test
+ void testInvalidEdgeCount() {
+ // Tree with n nodes must have n-1 edges
+ int[][] edges = {{0, 1}, {1, 2}}; // 2 edges for 5 nodes (should be 4)
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, edges); });
+ }
+
+ @Test
+ void testInvalidEdgeFormat() {
+ int[][] edges = {{0, 1, 2}}; // Edge with 3 elements instead of 2
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); });
+ }
+
+ @Test
+ void testInvalidNodeInEdge() {
+ int[][] edges = {{0, 5}}; // Node 5 doesn't exist in tree of size 3
+ assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); });
+ }
+
+ @Test
+ void testInvalidNodeQuery() {
+ int[][] edges = {{0, 1}, {1, 2}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges);
+
+ assertThrows(IllegalArgumentException.class, () -> { tree.getParent(-1); });
+
+ assertThrows(IllegalArgumentException.class, () -> { tree.getParent(5); });
+ }
+
+ @Test
+ void testToString() {
+ int[][] edges = {{0, 1}, {1, 2}};
+ CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges);
+
+ String result = tree.toString();
+ assertNotNull(result);
+ assertTrue(result.contains("Centroid Tree"));
+ assertTrue(result.contains("Node"));
+ assertTrue(result.contains("ROOT"));
+ }
+
+ @Test
+ void testAdjacencyListConstructor() {
+ List> adj = new ArrayList<>();
+ for (int i = 0; i < 3; i++) {
+ adj.add(new ArrayList<>());
+ }
+ adj.get(0).add(1);
+ adj.get(1).add(0);
+ adj.get(1).add(2);
+ adj.get(2).add(1);
+
+ CentroidDecomposition.CentroidTree tree = new CentroidDecomposition.CentroidTree(adj);
+ assertEquals(3, tree.size());
+ assertEquals(1, tree.getRoot());
+ }
+
+ @Test
+ void testNullAdjacencyList() {
+ assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(null); });
+ }
+
+ @Test
+ void testEmptyAdjacencyList() {
+ assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(new ArrayList<>()); });
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java
new file mode 100644
index 000000000000..c5973168438e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java
@@ -0,0 +1,50 @@
+/*
+ * TheAlgorithms (https://github.com/TheAlgorithms/Java)
+ * Author: Shewale41
+ * This file is licensed under the MIT License.
+ */
+
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Basic tests for ThreadedBinaryTree inorder traversal.
+ */
+public class ThreadedBinaryTreeTest {
+
+ @Test
+ public void testInorderTraversalSimple() {
+ ThreadedBinaryTree tree = new ThreadedBinaryTree();
+ tree.insert(50);
+ tree.insert(30);
+ tree.insert(70);
+ tree.insert(20);
+ tree.insert(40);
+ tree.insert(60);
+ tree.insert(80);
+
+ List expected = List.of(20, 30, 40, 50, 60, 70, 80);
+ List actual = tree.inorderTraversal();
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testInorderWithDuplicates() {
+ ThreadedBinaryTree tree = new ThreadedBinaryTree();
+ tree.insert(5);
+ tree.insert(3);
+ tree.insert(7);
+ tree.insert(7); // duplicate
+ tree.insert(2);
+
+ List expected = List.of(2, 3, 5, 7, 7);
+ List actual = tree.inorderTraversal();
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java
new file mode 100644
index 000000000000..241f23c0fa1d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java
@@ -0,0 +1,132 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.random.RandomGenerator;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class GomoryHuTreeTest {
+
+ @Test
+ @DisplayName("Single node graph")
+ void singleNode() {
+ int[][] cap = {{0}};
+ int[][] res = GomoryHuTree.buildTree(cap);
+ int[] parent = res[0];
+ int[] weight = res[1];
+ assertEquals(-1, parent[0]);
+ assertEquals(0, weight[0]);
+ }
+
+ @Test
+ @DisplayName("Triangle undirected graph with known min-cuts")
+ void triangleGraph() {
+ // 0-1:3, 1-2:2, 0-2:4
+ int[][] cap = new int[3][3];
+ cap[0][1] = 3;
+ cap[1][0] = 3;
+ cap[1][2] = 2;
+ cap[2][1] = 2;
+ cap[0][2] = 4;
+ cap[2][0] = 4;
+
+ int[][] tree = GomoryHuTree.buildTree(cap);
+ // validate all pairs via path-min-edge equals maxflow
+ validateAllPairs(cap, tree);
+ }
+
+ @Test
+ @DisplayName("Random small undirected graphs compare to EdmondsKarp")
+ void randomSmallGraphs() {
+ Random rng = new Random(42);
+ for (int n = 2; n <= 6; n++) {
+ for (int iter = 0; iter < 10; iter++) {
+ int[][] cap = randSymmetricMatrix(n, 0, 5, rng);
+ int[][] tree = GomoryHuTree.buildTree(cap);
+ validateAllPairs(cap, tree);
+ }
+ }
+ }
+
+ private static int[][] randSymmetricMatrix(int n, int lo, int hi, RandomGenerator rng) {
+ int[][] a = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ for (int j = i + 1; j < n; j++) {
+ int w = rng.nextInt(hi - lo + 1) + lo;
+ a[i][j] = w;
+ a[j][i] = w;
+ }
+ }
+ // zero diagonal
+ for (int i = 0; i < n; i++) {
+ a[i][i] = 0;
+ }
+ return a;
+ }
+
+ private static void validateAllPairs(int[][] cap, int[][] tree) {
+ int n = cap.length;
+ int[] parent = tree[0];
+ int[] weight = tree[1];
+
+ // build adjacency list of tree without generic array creation
+ List> g = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ g.add(new ArrayList<>());
+ }
+ for (int v = 1; v < n; v++) {
+ int u = parent[v];
+ int w = weight[v];
+ g.get(u).add(new int[] {v, w});
+ g.get(v).add(new int[] {u, w});
+ }
+
+ for (int s = 0; s < n; s++) {
+ for (int t = s + 1; t < n; t++) {
+ int treeVal = minEdgeOnPath(g, s, t);
+ int flowVal = EdmondsKarp.maxFlow(cap, s, t);
+ assertEquals(flowVal, treeVal, "pair (" + s + "," + t + ")");
+ }
+ }
+ }
+
+ private static int minEdgeOnPath(List> g, int s, int t) {
+ // BFS to record parent and edge weight along the path, since it's a tree, unique path exists
+ int n = g.size();
+ int[] parent = new int[n];
+ int[] edgeW = new int[n];
+ Arrays.fill(parent, -1);
+ Queue q = new ArrayDeque<>();
+ q.add(s);
+ parent[s] = s;
+ while (!q.isEmpty()) {
+ int u = q.poll();
+ if (u == t) {
+ break;
+ }
+ for (int[] e : g.get(u)) {
+ int v = e[0];
+ int w = e[1];
+ if (parent[v] == -1) {
+ parent[v] = u;
+ edgeW[v] = w;
+ q.add(v);
+ }
+ }
+ }
+ int cur = t;
+ int ans = Integer.MAX_VALUE;
+ while (cur != s) {
+ ans = Math.min(ans, edgeW[cur]);
+ cur = parent[cur];
+ }
+ return ans == Integer.MAX_VALUE ? 0 : ans;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java
new file mode 100644
index 000000000000..5b35345afd02
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java
@@ -0,0 +1,31 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class AbundantNumberTest {
+ @ParameterizedTest
+ @CsvSource({"12", "66", "222", "444", "888", "2424"})
+ void abundantNumbersTest(int n) {
+ assertTrue(AbundantNumber.isAbundant(n));
+ assertTrue(AbundantNumber.isAbundantNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"1", "2", "6", "111", "333", "2222"})
+ void nonAbundantNumbersTest(int n) {
+ assertFalse(AbundantNumber.isAbundant(n));
+ assertFalse(AbundantNumber.isAbundantNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0", "-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundant(n));
+ assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundantNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/EvilNumberTest.java b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java
new file mode 100644
index 000000000000..e59171fad25f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class EvilNumberTest {
+ @ParameterizedTest
+ @CsvSource({"0", "3", "10", "129", "222", "500", "777", "1198"})
+ void evilNumbersTest(int n) {
+ assertTrue(EvilNumber.isEvilNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"1", "7", "100", "333", "555", "1199"})
+ void odiousNumbersTest(int n) {
+ assertFalse(EvilNumber.isEvilNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> EvilNumber.isEvilNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java
new file mode 100644
index 000000000000..56c005fd51ae
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java
@@ -0,0 +1,47 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class ExtendedEuclideanAlgorithmTest {
+
+ /**
+ * Verifies that the returned values satisfy BΓ©zout's identity: a*x + b*y =
+ * gcd(a, b)
+ */
+ private void verifyBezoutIdentity(long a, long b, long[] result) {
+ long gcd = result[0];
+ long x = result[1];
+ long y = result[2];
+ assertEquals(a * x + b * y, gcd, "BΓ©zout's identity failed for gcd(" + a + ", " + b + ")");
+ }
+
+ @Test
+ public void testExtendedGCD() {
+ // Test case 1: General case gcd(30, 50) = 10
+ long[] result1 = ExtendedEuclideanAlgorithm.extendedGCD(30, 50);
+ assertEquals(10, result1[0], "Test Case 1 Failed: gcd(30, 50) should be 10");
+ verifyBezoutIdentity(30, 50, result1);
+
+ // Test case 2: Another general case gcd(240, 46) = 2
+ long[] result2 = ExtendedEuclideanAlgorithm.extendedGCD(240, 46);
+ assertEquals(2, result2[0], "Test Case 2 Failed: gcd(240, 46) should be 2");
+ verifyBezoutIdentity(240, 46, result2);
+
+ // Test case 3: Base case where b is 0, gcd(10, 0) = 10
+ long[] result3 = ExtendedEuclideanAlgorithm.extendedGCD(10, 0);
+ assertEquals(10, result3[0], "Test Case 3 Failed: gcd(10, 0) should be 10");
+ verifyBezoutIdentity(10, 0, result3);
+
+ // Test case 4: Numbers are co-prime gcd(17, 13) = 1
+ long[] result4 = ExtendedEuclideanAlgorithm.extendedGCD(17, 13);
+ assertEquals(1, result4[0], "Test Case 4 Failed: gcd(17, 13) should be 1");
+ verifyBezoutIdentity(17, 13, result4);
+
+ // Test case 5: One number is a multiple of the other gcd(100, 20) = 20
+ long[] result5 = ExtendedEuclideanAlgorithm.extendedGCD(100, 20);
+ assertEquals(20, result5[0], "Test Case 5 Failed: gcd(100, 20) should be 20");
+ verifyBezoutIdentity(100, 20, result5);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/GCDTest.java b/src/test/java/com/thealgorithms/maths/GCDTest.java
index bac3f8f7596c..6bc870e94df9 100644
--- a/src/test/java/com/thealgorithms/maths/GCDTest.java
+++ b/src/test/java/com/thealgorithms/maths/GCDTest.java
@@ -6,57 +6,77 @@
public class GCDTest {
@Test
- void test1() {
+ void testNegativeAndZeroThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-1, 0));
}
@Test
- void test2() {
+ void testPositiveAndNegativeThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(10, -2));
}
@Test
- void test3() {
+ void testBothNegativeThrowsException() {
Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-5, -3));
}
@Test
- void test4() {
- Assertions.assertEquals(GCD.gcd(0, 2), 2);
+ void testZeroAndPositiveReturnsPositive() {
+ Assertions.assertEquals(2, GCD.gcd(0, 2));
}
@Test
- void test5() {
- Assertions.assertEquals(GCD.gcd(10, 0), 10);
+ void testPositiveAndZeroReturnsPositive() {
+ Assertions.assertEquals(10, GCD.gcd(10, 0));
}
@Test
- void test6() {
- Assertions.assertEquals(GCD.gcd(1, 0), 1);
+ void testOneAndZeroReturnsOne() {
+ Assertions.assertEquals(1, GCD.gcd(1, 0));
}
@Test
- void test7() {
- Assertions.assertEquals(GCD.gcd(9, 6), 3);
+ void testTwoPositiveNumbers() {
+ Assertions.assertEquals(3, GCD.gcd(9, 6));
}
@Test
- void test8() {
- Assertions.assertEquals(GCD.gcd(48, 18, 30, 12), 6);
+ void testMultipleArgumentsGcd() {
+ Assertions.assertEquals(6, GCD.gcd(48, 18, 30, 12));
}
@Test
- void testArrayGcd1() {
- Assertions.assertEquals(GCD.gcd(new int[] {9, 6}), 3);
+ void testArrayInputGcd() {
+ Assertions.assertEquals(3, GCD.gcd(new int[] {9, 6}));
}
@Test
- void testArrayGcd2() {
- Assertions.assertEquals(GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13}), 5);
+ void testArrayWithCommonFactor() {
+ Assertions.assertEquals(5, GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13}));
}
@Test
- void testArrayGcdForEmptyInput() {
- Assertions.assertEquals(GCD.gcd(new int[] {}), 0);
+ void testEmptyArrayReturnsZero() {
+ Assertions.assertEquals(0, GCD.gcd(new int[] {}));
+ }
+
+ @Test
+ void testSameNumbers() {
+ Assertions.assertEquals(7, GCD.gcd(7, 7));
+ }
+
+ @Test
+ void testPrimeNumbersHaveGcdOne() {
+ Assertions.assertEquals(1, GCD.gcd(13, 17));
+ }
+
+ @Test
+ void testSingleElementArrayReturnsElement() {
+ Assertions.assertEquals(42, GCD.gcd(new int[] {42}));
+ }
+
+ @Test
+ void testLargeNumbers() {
+ Assertions.assertEquals(12, GCD.gcd(123456, 789012));
}
}
diff --git a/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java
new file mode 100644
index 000000000000..91904316b25c
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class LuckyNumberTest {
+
+ @ParameterizedTest
+ @CsvSource({"1", "3", "13", "49", "109", "459", "949"})
+ void luckyNumbersTest(int n) {
+ assertTrue(LuckyNumber.isLucky(n));
+ assertTrue(LuckyNumber.isLuckyNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"2", "17", "100", "300", "700"})
+ void nonLuckyNumbersTest(int n) {
+ assertFalse(LuckyNumber.isLucky(n));
+ assertFalse(LuckyNumber.isLuckyNumber(n));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0", "-1"})
+ void throwsNegativeNumbersNotAllowed(int n) {
+ assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLucky(n));
+ assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLuckyNumber(n));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/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/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/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/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"));
+ }
+}