getHuffmanCodes() {
+ return huffmanCodes;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
index 7a9448fd8fe7..7698cc832981 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
@@ -1,3 +1,10 @@
+/**
+ * [Brief description of what the algorithm does]
+ *
+ * Time Complexity: O(n) [or appropriate complexity]
+ * Space Complexity: O(n)
+ * * @author Reshma Kakkirala
+ */
package com.thealgorithms.conversions;
import java.util.Arrays;
diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
index cd5dc580b694..dbdb2d806209 100644
--- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
+++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
@@ -63,7 +63,8 @@ public void add(final E element) {
*
* @param index the index at which the element is to be placed
* @param element the element to be inserted at the specified index
- * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or
+ * equal to the number of elements
*/
public void put(final int index, E element) {
if (index < 0) {
@@ -82,7 +83,8 @@ public void put(final int index, E element) {
*
* @param index the index of the element to retrieve
* @return the element at the specified index
- * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or
+ * equal to the current size
*/
@SuppressWarnings("unchecked")
public E get(final int index) {
@@ -97,7 +99,8 @@ public E get(final int index) {
*
* @param index the index of the element to be removed
* @return the element that was removed from the array
- * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or
+ * equal to the current size
*/
public E remove(final int index) {
if (index < 0 || index >= size) {
@@ -127,6 +130,21 @@ public boolean isEmpty() {
return size == 0;
}
+ /**
+ * Checks whether the array contains the specified element.
+ *
+ * @param element the element to check for
+ * @return true if the array contains the specified element, false otherwise
+ */
+ public boolean contains(final E element) {
+ for (int i = 0; i < size; i++) {
+ if (Objects.equals(elements[i], element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns a sequential stream with this collection as its source.
*
@@ -137,7 +155,8 @@ public Stream stream() {
}
/**
- * Ensures that the array has enough capacity to hold the specified number of elements.
+ * Ensures that the array has enough capacity to hold the specified number of
+ * elements.
*
* @param minCapacity the minimum capacity required
*/
@@ -150,7 +169,8 @@ private void ensureCapacity(int minCapacity) {
/**
* Removes the element at the specified index without resizing the array.
- * This method shifts any subsequent elements to the left and clears the last element.
+ * This method shifts any subsequent elements to the left and clears the last
+ * element.
*
* @param index the index of the element to remove
*/
@@ -163,7 +183,8 @@ private void fastRemove(int index) {
}
/**
- * Returns a string representation of the array, including only the elements that are currently stored.
+ * Returns a string representation of the array, including only the elements
+ * that are currently stored.
*
* @return a string containing the elements in the array
*/
@@ -227,7 +248,9 @@ public E next() {
/**
* Removes the last element returned by this iterator.
*
- * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method
+ * @throws IllegalStateException if the next method has not yet been called, or
+ * the remove method has already been called after
+ * the last call to the next method
*/
@Override
public void remove() {
@@ -242,7 +265,8 @@ public void remove() {
/**
* Checks for concurrent modifications to the array during iteration.
*
- * @throws ConcurrentModificationException if the array has been modified structurally
+ * @throws ConcurrentModificationException if the array has been modified
+ * structurally
*/
private void checkForComodification() {
if (modCount != expectedModCount) {
@@ -251,7 +275,8 @@ private void checkForComodification() {
}
/**
- * Performs the given action for each remaining element in the iterator until all elements have been processed.
+ * Performs the given action for each remaining element in the iterator until
+ * all elements have been processed.
*
* @param action the action to be performed for each element
* @throws NullPointerException if the specified action is null
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java
new file mode 100644
index 000000000000..79275dcefe20
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.Queue;
+
+/**
+ * Reverse a queue using recursion.
+ */
+public final class ReverseQueueRecursion {
+ private ReverseQueueRecursion() {
+ // private constructor to prevent instantiation
+ }
+
+ /**
+ * Reverses the given queue recursively.
+ *
+ * @param queue the queue to reverse
+ * @param the type of elements in the queue
+ */
+ public static void reverseQueue(final Queue queue) {
+ if (queue == null || queue.isEmpty()) {
+ return;
+ }
+
+ final T front = queue.poll();
+ reverseQueue(queue);
+ queue.add(front);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java
new file mode 100644
index 000000000000..40b9e8a73533
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java
@@ -0,0 +1,201 @@
+package com.thealgorithms.datastructures.trees;
+
+/**
+ * 2D Segment Tree (Tree of Trees) implementation.
+ * This data structure supports point updates and submatrix sum queries
+ * in a 2D grid. It achieves this by nesting 1D Segment Trees within a 1D Segment Tree.
+ *
+ * Time Complexity:
+ * - Build/Initialization: O(N * M)
+ * - Point Update: O(log N * log M)
+ * - Submatrix Query: O(log N * log M)
+ *
+ * @see 2D Segment Tree
+ */
+public class SegmentTree2D {
+
+ /**
+ * Represents a 1D Segment Tree.
+ * This is equivalent to your 'Sagara' struct. It manages the columns (X-axis).
+ */
+ public static class SegmentTree1D {
+ private int n;
+ private final int[] tree;
+
+ /**
+ * Initializes the 1D Segment Tree with the nearest power of 2.
+ *
+ * @param size The expected number of elements (columns).
+ */
+ public SegmentTree1D(int size) {
+ n = 1;
+ while (n < size) {
+ n *= 2;
+ }
+ tree = new int[n * 2];
+ }
+
+ /**
+ * Recursively updates a point in the 1D tree.
+ */
+ private void update(int index, int val, int node, int lx, int rx) {
+ if (rx - lx == 1) {
+ tree[node] = val;
+ return;
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftChild = node * 2 + 1;
+ int rightChild = node * 2 + 2;
+
+ if (index < mid) {
+ update(index, val, leftChild, lx, mid);
+ } else {
+ update(index, val, rightChild, mid, rx);
+ }
+
+ tree[node] = tree[leftChild] + tree[rightChild];
+ }
+
+ /**
+ * Public wrapper to update a specific index.
+ *
+ * @param index The column index to update.
+ * @param val The new value.
+ */
+ public void update(int index, int val) {
+ update(index, val, 0, 0, n);
+ }
+
+ /**
+ * Retrieves the exact value at a specific leaf node.
+ *
+ * @param index The column index.
+ * @return The value at the given index.
+ */
+ public int get(int index) {
+ return query(index, index + 1, 0, 0, n);
+ }
+
+ /**
+ * Recursively queries the sum in a 1D range.
+ */
+ private int query(int l, int r, int node, int lx, int rx) {
+ if (lx >= r || rx <= l) {
+ return 0; // Out of bounds
+ }
+ if (lx >= l && rx <= r) {
+ return tree[node]; // Fully inside
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftSum = query(l, r, node * 2 + 1, lx, mid);
+ int rightSum = query(l, r, node * 2 + 2, mid, rx);
+
+ return leftSum + rightSum;
+ }
+
+ /**
+ * Public wrapper to query the sum in the range [l, r).
+ *
+ * @param l Left boundary (inclusive).
+ * @param r Right boundary (exclusive).
+ * @return The sum of the range.
+ */
+ public int query(int l, int r) {
+ return query(l, r, 0, 0, n);
+ }
+ }
+
+ // --- Start of 2D Segment Tree (equivalent to 'Sagara2D') ---
+
+ private int n;
+ private final SegmentTree1D[] tree;
+
+ /**
+ * Initializes the 2D Segment Tree.
+ *
+ * @param rows The number of rows in the matrix.
+ * @param cols The number of columns in the matrix.
+ */
+ public SegmentTree2D(int rows, int cols) {
+ n = 1;
+ while (n < rows) {
+ n *= 2;
+ }
+ tree = new SegmentTree1D[n * 2];
+ for (int i = 0; i < n * 2; i++) {
+ // Every node in the outer tree is a full 1D tree!
+ tree[i] = new SegmentTree1D(cols);
+ }
+ }
+
+ /**
+ * Recursively updates a point in the 2D grid.
+ */
+ private void update(int row, int col, int val, int node, int lx, int rx) {
+ if (rx - lx == 1) {
+ tree[node].update(col, val);
+ return;
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftChild = node * 2 + 1;
+ int rightChild = node * 2 + 2;
+
+ if (row < mid) {
+ update(row, col, val, leftChild, lx, mid);
+ } else {
+ update(row, col, val, rightChild, mid, rx);
+ }
+
+ // The value of the current node's column is the sum of its children's column values
+ int leftVal = tree[leftChild].get(col);
+ int rightVal = tree[rightChild].get(col);
+ tree[node].update(col, leftVal + rightVal);
+ }
+
+ /**
+ * Public wrapper to update a specific point (row, col).
+ *
+ * @param row The row index.
+ * @param col The column index.
+ * @param val The new value.
+ */
+ public void update(int row, int col, int val) {
+ update(row, col, val, 0, 0, n);
+ }
+
+ /**
+ * Recursively queries the sum in a submatrix.
+ */
+ private int query(int top, int bottom, int left, int right, int node, int lx, int rx) {
+ if (lx >= bottom || rx <= top) {
+ return 0; // Out of bounds
+ }
+ if (lx >= top && rx <= bottom) {
+ // Fully inside the row range, so delegate the column query to the 1D tree
+ return tree[node].query(left, right);
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftSum = query(top, bottom, left, right, node * 2 + 1, lx, mid);
+ int rightSum = query(top, bottom, left, right, node * 2 + 2, mid, rx);
+
+ return leftSum + rightSum;
+ }
+
+ /**
+ * Public wrapper to query the sum of a submatrix.
+ * Note: boundaries are [top, bottom) and [left, right).
+ *
+ * @param top Top row index (inclusive).
+ * @param bottom Bottom row index (exclusive).
+ * @param left Left column index (inclusive).
+ * @param right Right column index (exclusive).
+ * @return The sum of the submatrix.
+ */
+ public int query(int top, int bottom, int left, int right) {
+ return query(top, bottom, left, right, 0, 0, n);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
index 4c9c40c83174..323098a99887 100644
--- a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
+++ b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
@@ -66,10 +66,6 @@ public static class Location {
}
}
- public Location[] createLocation(int numberValues) {
- return new Location[numberValues];
- }
-
public Location buildLocation(double x, double y) {
return new Location(x, y);
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java
new file mode 100644
index 000000000000..428176ea6c40
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java
@@ -0,0 +1,130 @@
+package com.thealgorithms.dynamicprogramming;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Computes the minimum search cost of an optimal binary search tree.
+ *
+ * The algorithm sorts the keys, preserves the corresponding search frequencies, and uses
+ * dynamic programming with Knuth's optimization to compute the minimum weighted search cost.
+ *
+ *
Example: if keys = [10, 12] and frequencies = [34, 50], the best tree puts 12 at the root
+ * and 10 as its left child. The total cost is 50 * 1 + 34 * 2 = 118.
+ *
+ *
Reference:
+ * https://en.wikipedia.org/wiki/Optimal_binary_search_tree
+ */
+public final class OptimalBinarySearchTree {
+ private OptimalBinarySearchTree() {
+ }
+
+ /**
+ * Computes the minimum weighted search cost for the given keys and search frequencies.
+ *
+ * @param keys the BST keys
+ * @param frequencies the search frequencies associated with the keys
+ * @return the minimum search cost
+ * @throws IllegalArgumentException if the input is invalid
+ */
+ public static long findOptimalCost(int[] keys, int[] frequencies) {
+ validateInput(keys, frequencies);
+ if (keys.length == 0) {
+ return 0L;
+ }
+
+ int[][] sortedNodes = sortNodes(keys, frequencies);
+ int nodeCount = sortedNodes.length;
+ long[] prefixSums = buildPrefixSums(sortedNodes);
+ long[][] optimalCost = new long[nodeCount][nodeCount];
+ int[][] root = new int[nodeCount][nodeCount];
+
+ // Small example:
+ // keys = [10, 12]
+ // frequencies = [34, 50]
+ // Choosing 12 as the root gives cost 50 * 1 + 34 * 2 = 118,
+ // which is better than choosing 10 as the root.
+
+ // Base case: a subtree containing one key has cost equal to its frequency,
+ // because that key becomes the root of the subtree and is searched at depth 1.
+ for (int index = 0; index < nodeCount; index++) {
+ optimalCost[index][index] = sortedNodes[index][1];
+ root[index][index] = index;
+ }
+
+ // Build solutions for longer and longer key ranges.
+ // optimalCost[start][end] stores the minimum search cost for keys in that range.
+ for (int length = 2; length <= nodeCount; length++) {
+ for (int start = 0; start <= nodeCount - length; start++) {
+ int end = start + length - 1;
+
+ // Every key in this range moves one level deeper when we choose a root,
+ // so the sum of frequencies is added once to the subtree cost.
+ long frequencySum = prefixSums[end + 1] - prefixSums[start];
+ optimalCost[start][end] = Long.MAX_VALUE;
+
+ // Knuth's optimization:
+ // the best root for [start, end] lies between the best roots of
+ // [start, end - 1] and [start + 1, end], so we search only this interval.
+ int leftBoundary = root[start][end - 1];
+ int rightBoundary = root[start + 1][end];
+ for (int currentRoot = leftBoundary; currentRoot <= rightBoundary; currentRoot++) {
+ long leftCost = currentRoot > start ? optimalCost[start][currentRoot - 1] : 0L;
+ long rightCost = currentRoot < end ? optimalCost[currentRoot + 1][end] : 0L;
+ long currentCost = frequencySum + leftCost + rightCost;
+
+ if (currentCost < optimalCost[start][end]) {
+ optimalCost[start][end] = currentCost;
+ root[start][end] = currentRoot;
+ }
+ }
+ }
+ }
+
+ return optimalCost[0][nodeCount - 1];
+ }
+
+ private static void validateInput(int[] keys, int[] frequencies) {
+ if (keys == null || frequencies == null) {
+ throw new IllegalArgumentException("Keys and frequencies cannot be null");
+ }
+ if (keys.length != frequencies.length) {
+ throw new IllegalArgumentException("Keys and frequencies must have the same length");
+ }
+
+ for (int frequency : frequencies) {
+ if (frequency < 0) {
+ throw new IllegalArgumentException("Frequencies cannot be negative");
+ }
+ }
+ }
+
+ private static int[][] sortNodes(int[] keys, int[] frequencies) {
+ int[][] sortedNodes = new int[keys.length][2];
+ for (int index = 0; index < keys.length; index++) {
+ sortedNodes[index][0] = keys[index];
+ sortedNodes[index][1] = frequencies[index];
+ }
+
+ // Sort by key so the nodes can be treated as an in-order BST sequence.
+ Arrays.sort(sortedNodes, Comparator.comparingInt(node -> node[0]));
+
+ for (int index = 1; index < sortedNodes.length; index++) {
+ if (sortedNodes[index - 1][0] == sortedNodes[index][0]) {
+ throw new IllegalArgumentException("Keys must be distinct");
+ }
+ }
+
+ return sortedNodes;
+ }
+
+ private static long[] buildPrefixSums(int[][] sortedNodes) {
+ long[] prefixSums = new long[sortedNodes.length + 1];
+ for (int index = 0; index < sortedNodes.length; index++) {
+ // prefixSums[i] holds the total frequency of the first i sorted keys.
+ // This lets us get the frequency sum of any range in O(1) time.
+ prefixSums[index + 1] = prefixSums[index] + sortedNodes[index][1];
+ }
+ return prefixSums;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/LineIntersection.java b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
new file mode 100644
index 000000000000..8d65833816b3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.geometry;
+
+import java.awt.geom.Point2D;
+import java.util.Optional;
+
+/**
+ * Utility methods for checking and computing 2D line segment intersections.
+ */
+public final class LineIntersection {
+ private LineIntersection() {
+ }
+
+ /**
+ * Checks whether two line segments intersect.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return true when the segments intersect (including touching endpoints)
+ */
+ public static boolean intersects(Point p1, Point p2, Point q1, Point q2) {
+ int o1 = orientation(p1, p2, q1);
+ int o2 = orientation(p1, p2, q2);
+ int o3 = orientation(q1, q2, p1);
+ int o4 = orientation(q1, q2, p2);
+
+ if (o1 != o2 && o3 != o4) {
+ return true;
+ }
+
+ if (o1 == 0 && onSegment(p1, q1, p2)) {
+ return true;
+ }
+ if (o2 == 0 && onSegment(p1, q2, p2)) {
+ return true;
+ }
+ if (o3 == 0 && onSegment(q1, p1, q2)) {
+ return true;
+ }
+ if (o4 == 0 && onSegment(q1, p2, q2)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes the single geometric intersection point between two non-parallel
+ * segments when it exists.
+ *
+ *
For parallel/collinear overlap, this method returns {@code Optional.empty()}.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return the intersection point when uniquely defined and on both segments
+ */
+ public static Optional intersectionPoint(Point p1, Point p2, Point q1, Point q2) {
+ if (!intersects(p1, p2, q1, q2)) {
+ return Optional.empty();
+ }
+
+ long x1 = p1.x();
+ long y1 = p1.y();
+ long x2 = p2.x();
+ long y2 = p2.y();
+ long x3 = q1.x();
+ long y3 = q1.y();
+ long x4 = q2.x();
+ long y4 = q2.y();
+
+ long denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (denominator == 0L) {
+ return sharedEndpoint(p1, p2, q1, q2);
+ }
+
+ long determinant1 = x1 * y2 - y1 * x2;
+ long determinant2 = x3 * y4 - y3 * x4;
+ long numeratorX = determinant1 * (x3 - x4) - (x1 - x2) * determinant2;
+ long numeratorY = determinant1 * (y3 - y4) - (y1 - y2) * determinant2;
+
+ return Optional.of(new Point2D.Double(numeratorX / (double) denominator, numeratorY / (double) denominator));
+ }
+
+ private static int orientation(Point a, Point b, Point c) {
+ long cross = ((long) b.x() - a.x()) * ((long) c.y() - a.y()) - ((long) b.y() - a.y()) * ((long) c.x() - a.x());
+ return Long.compare(cross, 0L);
+ }
+
+ private static Optional sharedEndpoint(Point p1, Point p2, Point q1, Point q2) {
+ if (p1.equals(q1) || p1.equals(q2)) {
+ return Optional.of(new Point2D.Double(p1.x(), p1.y()));
+ }
+ if (p2.equals(q1) || p2.equals(q2)) {
+ return Optional.of(new Point2D.Double(p2.x(), p2.y()));
+ }
+ return Optional.empty();
+ }
+
+ private static boolean onSegment(Point a, Point b, Point c) {
+ return b.x() >= Math.min(a.x(), c.x()) && b.x() <= Math.max(a.x(), c.x()) && b.y() >= Math.min(a.y(), c.y()) && b.y() <= Math.max(a.y(), c.y());
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/AccountMerge.java b/src/main/java/com/thealgorithms/graph/AccountMerge.java
new file mode 100644
index 000000000000..cf934a72eb68
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java
@@ -0,0 +1,112 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Merges account records using Disjoint Set Union (Union-Find) on shared emails.
+ *
+ * Input format: each account is a list where the first element is the user name and the
+ * remaining elements are emails.
+ */
+public final class AccountMerge {
+ private AccountMerge() {
+ }
+
+ public static List> mergeAccounts(List> accounts) {
+ if (accounts == null || accounts.isEmpty()) {
+ return List.of();
+ }
+
+ UnionFind dsu = new UnionFind(accounts.size());
+ Map emailToAccount = new HashMap<>();
+
+ for (int i = 0; i < accounts.size(); i++) {
+ List account = accounts.get(i);
+ for (int j = 1; j < account.size(); j++) {
+ String email = account.get(j);
+ Integer previous = emailToAccount.putIfAbsent(email, i);
+ if (previous != null) {
+ dsu.union(i, previous);
+ }
+ }
+ }
+
+ Map> rootToEmails = new LinkedHashMap<>();
+ for (Map.Entry entry : emailToAccount.entrySet()) {
+ int root = dsu.find(entry.getValue());
+ rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey());
+ }
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).size() <= 1) {
+ int root = dsu.find(i);
+ rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>());
+ }
+ }
+
+ List> merged = new ArrayList<>();
+ for (Map.Entry> entry : rootToEmails.entrySet()) {
+ int root = entry.getKey();
+ List emails = entry.getValue();
+ Collections.sort(emails);
+
+ List mergedAccount = new ArrayList<>();
+ mergedAccount.add(accounts.get(root).getFirst());
+ mergedAccount.addAll(emails);
+ merged.add(mergedAccount);
+ }
+
+ merged.sort((a, b) -> {
+ int cmp = a.getFirst().compareTo(b.getFirst());
+ if (cmp != 0) {
+ return cmp;
+ }
+ if (a.size() == 1 || b.size() == 1) {
+ return Integer.compare(a.size(), b.size());
+ }
+ return a.get(1).compareTo(b.get(1));
+ });
+ return merged;
+ }
+
+ private static final class UnionFind {
+ private final int[] parent;
+ private final int[] rank;
+
+ private UnionFind(int size) {
+ this.parent = new int[size];
+ this.rank = new int[size];
+ for (int i = 0; i < size; i++) {
+ parent[i] = i;
+ }
+ }
+
+ private int find(int x) {
+ if (parent[x] != x) {
+ parent[x] = find(parent[x]);
+ }
+ return parent[x];
+ }
+
+ private void union(int x, int y) {
+ int rootX = find(x);
+ int rootY = find(y);
+ if (rootX == rootY) {
+ return;
+ }
+
+ if (rank[rootX] < rank[rootY]) {
+ parent[rootX] = rootY;
+ } else if (rank[rootX] > rank[rootY]) {
+ parent[rootY] = rootX;
+ } else {
+ parent[rootY] = rootX;
+ rank[rootX]++;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/TarjanBridges.java b/src/main/java/com/thealgorithms/graph/TarjanBridges.java
new file mode 100644
index 000000000000..dbe2e710429a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/TarjanBridges.java
@@ -0,0 +1,122 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of Tarjan's Bridge-Finding Algorithm for undirected graphs.
+ *
+ * A bridge (also called a cut-edge) is an edge in an undirected graph whose removal
+ * increases the number of connected components. Bridges represent critical links
+ * in a network β if any bridge is removed, part of the network becomes unreachable.
+ *
+ * The algorithm performs a single Depth-First Search (DFS) traversal, tracking two
+ * values for each vertex:
+ *
+ * - discoveryTime β the time step at which the vertex was first visited.
+ * - lowLink β the smallest discovery time reachable from the subtree rooted
+ * at that vertex (via back edges).
+ *
+ *
+ * An edge (u, v) is a bridge if and only if {@code lowLink[v] > discoveryTime[u]},
+ * meaning there is no back edge from the subtree of v that can reach u or any ancestor of u.
+ *
+ * Time Complexity: O(V + E), where V is the number of vertices and E is the number of edges.
+ * Space Complexity: O(V + E) for the adjacency list, discovery/low arrays, and recursion stack.
+ *
+ * @see Wikipedia: Bridge (graph theory)
+ */
+public final class TarjanBridges {
+
+ private TarjanBridges() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Finds all bridge edges in an undirected graph.
+ *
+ * The graph is represented as an adjacency list where each vertex is identified by
+ * an integer in the range {@code [0, vertexCount)}. For each undirected edge (u, v),
+ * v must appear in {@code adjacencyList.get(u)} and u must appear in
+ * {@code adjacencyList.get(v)}.
+ *
+ * @param vertexCount the total number of vertices in the graph (must be non-negative)
+ * @param adjacencyList the adjacency list representation of the graph; must contain
+ * exactly {@code vertexCount} entries (one per vertex)
+ * @return a list of bridge edges, where each bridge is represented as an {@code int[]}
+ * of length 2 with {@code edge[0] < edge[1]}; returns an empty list if no bridges exist
+ * @throws IllegalArgumentException if {@code vertexCount} is negative, or if
+ * {@code adjacencyList} is null or its size does not match
+ * {@code vertexCount}
+ */
+ public static List findBridges(int vertexCount, List> adjacencyList) {
+ if (vertexCount < 0) {
+ throw new IllegalArgumentException("vertexCount must be non-negative");
+ }
+ if (adjacencyList == null || adjacencyList.size() != vertexCount) {
+ throw new IllegalArgumentException("adjacencyList size must equal vertexCount");
+ }
+
+ List bridges = new ArrayList<>();
+
+ if (vertexCount == 0) {
+ return bridges;
+ }
+
+ BridgeFinder finder = new BridgeFinder(vertexCount, adjacencyList, bridges);
+
+ // Run DFS from every unvisited vertex to handle disconnected graphs
+ for (int i = 0; i < vertexCount; i++) {
+ if (!finder.visited[i]) {
+ finder.dfs(i, -1);
+ }
+ }
+
+ return bridges;
+ }
+
+ private static class BridgeFinder {
+ private final List> adjacencyList;
+ private final List bridges;
+ private final int[] discoveryTime;
+ private final int[] lowLink;
+ boolean[] visited;
+ private int timer;
+
+ BridgeFinder(int vertexCount, List> adjacencyList, List bridges) {
+ this.adjacencyList = adjacencyList;
+ this.bridges = bridges;
+ this.discoveryTime = new int[vertexCount];
+ this.lowLink = new int[vertexCount];
+ this.visited = new boolean[vertexCount];
+ this.timer = 0;
+ }
+
+ /**
+ * Performs DFS from the given vertex, computing discovery times and low-link values,
+ * and collects any bridge edges found.
+ *
+ * @param u the current vertex being explored
+ * @param parent the parent of u in the DFS tree (-1 if u is a root)
+ */
+ void dfs(int u, int parent) {
+ visited[u] = true;
+ discoveryTime[u] = timer;
+ lowLink[u] = timer;
+ timer++;
+
+ for (int v : adjacencyList.get(u)) {
+ if (!visited[v]) {
+ dfs(v, u);
+ lowLink[u] = Math.min(lowLink[u], lowLink[v]);
+
+ if (lowLink[v] > discoveryTime[u]) {
+ bridges.add(new int[] {Math.min(u, v), Math.max(u, v)});
+ }
+ } else if (v != parent) {
+ lowLink[u] = Math.min(lowLink[u], discoveryTime[v]);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
index b9279d5a244a..114eb71b1015 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
@@ -11,6 +11,6 @@ private AbsoluteValue() {
* @return The absolute value of the {@code number}
*/
public static int getAbsValue(int number) {
- return number < 0 ? -number : number;
+ return Math.abs(number);
}
}
diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java
index 1eba6666dde3..84fc67159379 100644
--- a/src/main/java/com/thealgorithms/maths/Area.java
+++ b/src/main/java/com/thealgorithms/maths/Area.java
@@ -10,17 +10,17 @@ private Area() {
/**
* String of IllegalArgumentException for radius
*/
- private static final String POSITIVE_RADIUS = "Must be a positive radius";
+ private static final String POSITIVE_RADIUS = "Radius must be greater than 0";
/**
* String of IllegalArgumentException for height
*/
- private static final String POSITIVE_HEIGHT = "Must be a positive height";
+ private static final String POSITIVE_HEIGHT = "Height must be greater than 0";
/**
* String of IllegalArgumentException for base
*/
- private static final String POSITIVE_BASE = "Must be a positive base";
+ private static final String POSITIVE_BASE = "Base must be greater than 0";
/**
* Calculate the surface area of a cube.
@@ -30,11 +30,32 @@ private Area() {
*/
public static double surfaceAreaCube(final double sideLength) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("Side length must be greater than 0");
}
return 6 * sideLength * sideLength;
}
+ /**
+ * Calculate the surface area of a cuboid.
+ *
+ * @param length length of the cuboid
+ * @param width width of the cuboid
+ * @param height height of the cuboid
+ * @return surface area of given cuboid
+ */
+ public static double surfaceAreaCuboid(final double length, double width, double height) {
+ if (length <= 0) {
+ throw new IllegalArgumentException("Length must be greater than 0");
+ }
+ if (width <= 0) {
+ throw new IllegalArgumentException("Width must be greater than 0");
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("Height must be greater than 0");
+ }
+ return 2 * (length * width + length * height + width * height);
+ }
+
/**
* Calculate the surface area of a sphere.
*
@@ -57,10 +78,10 @@ public static double surfaceAreaSphere(final double radius) {
*/
public static double surfaceAreaPyramid(final double sideLength, final double slantHeight) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("");
}
if (slantHeight <= 0) {
- throw new IllegalArgumentException("Must be a positive slantHeight");
+ throw new IllegalArgumentException("slant height must be greater than 0");
}
double baseArea = sideLength * sideLength;
double lateralSurfaceArea = 2 * sideLength * slantHeight;
@@ -76,10 +97,10 @@ public static double surfaceAreaPyramid(final double sideLength, final double sl
*/
public static double surfaceAreaRectangle(final double length, final double width) {
if (length <= 0) {
- throw new IllegalArgumentException("Must be a positive length");
+ throw new IllegalArgumentException("Length must be greater than 0");
}
if (width <= 0) {
- throw new IllegalArgumentException("Must be a positive width");
+ throw new IllegalArgumentException("Width must be greater than 0");
}
return length * width;
}
@@ -109,7 +130,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh
*/
public static double surfaceAreaSquare(final double sideLength) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("Side Length must be greater than 0");
}
return sideLength * sideLength;
}
@@ -121,14 +142,14 @@ public static double surfaceAreaSquare(final double sideLength) {
* @param height height of triangle
* @return area of given triangle
*/
- public static double surfaceAreaTriangle(final double base, final double height) {
- if (base <= 0) {
+ public static double surfaceAreaTriangle(final double baseLength, final double height) {
+ if (baseLength <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return base * height / 2;
+ return baseLength * height / 2;
}
/**
@@ -138,14 +159,14 @@ public static double surfaceAreaTriangle(final double base, final double height)
* @param height height of a parallelogram
* @return area of given parallelogram
*/
- public static double surfaceAreaParallelogram(final double base, final double height) {
- if (base <= 0) {
+ public static double surfaceAreaParallelogram(final double baseLength, final double height) {
+ if (baseLength <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return base * height;
+ return baseLength * height;
}
/**
@@ -156,17 +177,17 @@ public static double surfaceAreaParallelogram(final double base, final double he
* @param height height of trapezium
* @return area of given trapezium
*/
- public static double surfaceAreaTrapezium(final double base1, final double base2, final double height) {
- if (base1 <= 0) {
+ public static double surfaceAreaTrapezium(final double baseLength1, final double baseLength2, final double height) {
+ if (baseLength1 <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE + 1);
}
- if (base2 <= 0) {
+ if (baseLength2 <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE + 2);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return (base1 + base2) * height / 2;
+ return (baseLength1 + baseLength2) * height / 2;
}
/**
diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java
index a550a7f6504d..cf55af509ccc 100644
--- a/src/main/java/com/thealgorithms/maths/Average.java
+++ b/src/main/java/com/thealgorithms/maths/Average.java
@@ -1,9 +1,16 @@
package com.thealgorithms.maths;
+import java.util.Arrays;
+import java.util.OptionalDouble;
+
/**
* A utility class for computing the average of numeric arrays.
- * This class provides static methods to calculate the average of arrays
- * of both {@code double} and {@code int} values.
+ *
+ * This class provides static methods to calculate the arithmetic mean
+ * of arrays of both {@code double} and {@code int} values. It also offers
+ * a Stream-based alternative for modern, declarative usage.
+ *
+ *
All methods guard against {@code null} or empty inputs.
*/
public final class Average {
@@ -13,11 +20,14 @@ private Average() {
}
/**
- * Computes the average of a {@code double} array.
+ * Computes the arithmetic mean of a {@code double} array.
+ *
+ *
The average is calculated as the sum of all elements divided
+ * by the number of elements: {@code avg = Ξ£(numbers[i]) / n}.
*
- * @param numbers an array of {@code double} values
- * @return the average of the given numbers
- * @throws IllegalArgumentException if the input array is {@code null} or empty
+ * @param numbers a non-null, non-empty array of {@code double} values
+ * @return the arithmetic mean of the given numbers
+ * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty
*/
public static double average(double[] numbers) {
if (numbers == null || numbers.length == 0) {
@@ -31,11 +41,14 @@ public static double average(double[] numbers) {
}
/**
- * Computes the average of an {@code int} array.
+ * Computes the arithmetic mean of an {@code int} array.
+ *
+ *
The sum is accumulated in a {@code long} to prevent integer overflow
+ * when processing large arrays or large values.
*
- * @param numbers an array of {@code int} values
- * @return the average of the given numbers
- * @throws IllegalArgumentException if the input array is {@code null} or empty
+ * @param numbers a non-null, non-empty array of {@code int} values
+ * @return the arithmetic mean as a {@code long} (truncated toward zero)
+ * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty
*/
public static long average(int[] numbers) {
if (numbers == null || numbers.length == 0) {
@@ -47,4 +60,21 @@ public static long average(int[] numbers) {
}
return sum / numbers.length;
}
+
+ /**
+ * Computes the arithmetic mean of a {@code double} array using Java Streams.
+ *
+ *
This method is a declarative alternative to {@link #average(double[])}.
+ * Instead of throwing on empty input, it returns an empty {@link OptionalDouble},
+ * following the convention of the Stream API.
+ *
+ * @param numbers an array of {@code double} values, may be {@code null} or empty
+ * @return an {@link OptionalDouble} with the mean, or empty if input is null/empty
+ */
+ public static OptionalDouble averageStream(double[] numbers) {
+ if (numbers == null || numbers.length == 0) {
+ return OptionalDouble.empty();
+ }
+ return Arrays.stream(numbers).average();
+ }
}
diff --git a/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java
new file mode 100644
index 000000000000..4b68b7824574
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.maths;
+
+/**
+ * Multiplies two complex numbers represented as strings in the form "a+bi".
+ * Supports negative values and validates input format.
+ */
+public final class ComplexNumberMultiply {
+
+ private ComplexNumberMultiply() {
+ }
+
+ private static int[] parse(String num) {
+ if (num == null || !num.matches("-?\\d+\\+-?\\d+i")) {
+ throw new IllegalArgumentException("Invalid complex number format: " + num);
+ }
+
+ String[] parts = num.split("\\+");
+ int real = Integer.parseInt(parts[0]);
+ int imaginary = Integer.parseInt(parts[1].replace("i", ""));
+ return new int[] {real, imaginary};
+ }
+
+ public static String multiply(String num1, String num2) {
+ int[] a = parse(num1);
+ int[] b = parse(num2);
+
+ int real = a[0] * b[0] - a[1] * b[1];
+ int imaginary = a[0] * b[1] + a[1] * b[0];
+
+ return real + "+" + imaginary + "i";
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Correlation.java b/src/main/java/com/thealgorithms/maths/Correlation.java
new file mode 100644
index 000000000000..a46445fb23b7
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/Correlation.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.maths;
+
+/**
+ * Class for correlation of two discrete variables
+ */
+
+public final class Correlation {
+ private Correlation() {
+ }
+
+ public static final double DELTA = 1e-9;
+
+ /**
+ * Discrete correlation function.
+ * Correlation between two discrete variables is calculated
+ * according to the formula: Cor(x, y)=Cov(x, y)/sqrt(Var(x)*Var(y)).
+ * Correlation with a constant variable is taken to be zero.
+ *
+ * @param x The first discrete variable
+ * @param y The second discrete variable
+ * @param n The number of values for each variable
+ * @return The result of the correlation of variables x,y.
+ */
+ public static double correlation(double[] x, double[] y, int n) {
+ double exy = 0; // E(XY)
+ double ex = 0; // E(X)
+ double exx = 0; // E(X^2)
+ double ey = 0; // E(Y)
+ double eyy = 0; // E(Y^2)
+ for (int i = 0; i < n; i++) {
+ exy += x[i] * y[i];
+ ex += x[i];
+ exx += x[i] * x[i];
+ ey += y[i];
+ eyy += y[i] * y[i];
+ }
+ exy /= n;
+ ex /= n;
+ exx /= n;
+ ey /= n;
+ eyy /= n;
+ double cov = exy - ex * ey; // Cov(X, Y) = E(XY)-E(X)E(Y)
+ double varx = Math.sqrt(exx - ex * ex); // Var(X) = sqrt(E(X^2)-E(X)^2)
+ double vary = Math.sqrt(eyy - ey * ey); // Var(Y) = sqrt(E(Y^2)-E(Y)^2)
+ if (varx * vary < DELTA) { // Var(X) = 0 means X = const, the same about Y
+ return 0;
+ } else {
+ return cov / Math.sqrt(varx * vary);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Factorial.java b/src/main/java/com/thealgorithms/maths/Factorial.java
index 511cc1f84f05..8ad219a3066c 100644
--- a/src/main/java/com/thealgorithms/maths/Factorial.java
+++ b/src/main/java/com/thealgorithms/maths/Factorial.java
@@ -1,23 +1,19 @@
package com.thealgorithms.maths;
+import java.math.BigInteger;
+
public final class Factorial {
private Factorial() {
}
- /**
- * Calculate factorial N using iteration
- *
- * @param n the number
- * @return the factorial of {@code n}
- */
- public static long factorial(int n) {
+ public static BigInteger factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("Input number cannot be negative");
}
- long factorial = 1;
- for (int i = 1; i <= n; ++i) {
- factorial *= i;
+ BigInteger result = BigInteger.ONE;
+ for (int i = 1; i <= n; i++) {
+ result = result.multiply(BigInteger.valueOf(i));
}
- return factorial;
+ return result;
}
}
diff --git a/src/main/java/com/thealgorithms/maths/Means.java b/src/main/java/com/thealgorithms/maths/Means.java
index 5445a3caebc7..d77eb1d3f661 100644
--- a/src/main/java/com/thealgorithms/maths/Means.java
+++ b/src/main/java/com/thealgorithms/maths/Means.java
@@ -107,6 +107,28 @@ public static Double harmonic(final Iterable numbers) {
return size / sumOfReciprocals;
}
+ /**
+ * Computes the quadratic mean (root mean square) of the given numbers.
+ *
+ * The quadratic mean is calculated as: β[(xβ^2 Γ xβ^2 Γ ... Γ xβ^2)/n]
+ *
+ *
+ * Example: For numbers [1, 7], the quadratic mean is β[(1^2+7^2)/2] = β25 = 5.0
+ *
+ *
+ * @param numbers the input numbers (must not be empty)
+ * @return the quadratic mean of the input numbers
+ * @throws IllegalArgumentException if the input is empty
+ * @see Quadratic
+ * Mean
+ */
+ public static Double quadratic(final Iterable numbers) {
+ checkIfNotEmpty(numbers);
+ double sumOfSquares = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y * y);
+ int size = IterableUtils.size(numbers);
+ return Math.pow(sumOfSquares / size, 0.5);
+ }
+
/**
* Validates that the input iterable is not empty.
*
diff --git a/src/main/java/com/thealgorithms/maths/Volume.java b/src/main/java/com/thealgorithms/maths/Volume.java
index 0f282b2abae2..488b921cae83 100644
--- a/src/main/java/com/thealgorithms/maths/Volume.java
+++ b/src/main/java/com/thealgorithms/maths/Volume.java
@@ -102,4 +102,39 @@ public static double volumePyramid(double baseArea, double height) {
public static double volumeFrustumOfCone(double r1, double r2, double height) {
return (Math.PI * height / 3) * (r1 * r1 + r2 * r2 + r1 * r2);
}
+
+ /**
+ * Calculate the volume of a frustum of a pyramid.
+ *
+ * @param upperBaseArea area of the upper base
+ * @param lowerBaseArea area of the lower base
+ * @param height height of the frustum
+ * @return volume of the frustum
+ */
+ public static double volumeFrustumOfPyramid(double upperBaseArea, double lowerBaseArea, double height) {
+ return (upperBaseArea + lowerBaseArea + Math.sqrt(upperBaseArea * lowerBaseArea)) * height / 3;
+ }
+
+ /**
+ * Calculate the volume of a torus.
+ *
+ * @param majorRadius major radius of a torus
+ * @param minorRadius minor radius of a torus
+ * @return volume of the torus
+ */
+ public static double volumeTorus(double majorRadius, double minorRadius) {
+ return 2 * Math.PI * Math.PI * majorRadius * minorRadius * minorRadius;
+ }
+
+ /**
+ * Calculate the volume of an ellipsoid.
+ *
+ * @param a first semi-axis of an ellipsoid
+ * @param b second semi-axis of an ellipsoid
+ * @param c third semi-axis of an ellipsoid
+ * @return volume of the ellipsoid
+ */
+ public static double volumeEllipsoid(double a, double b, double c) {
+ return (4 * Math.PI * a * b * c) / 3;
+ }
}
diff --git a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java b/src/main/java/com/thealgorithms/others/cn/HammingDistance.java
deleted file mode 100644
index c8239d53d606..000000000000
--- a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.thealgorithms.others.cn;
-
-public final class HammingDistance {
- private HammingDistance() {
- }
-
- private static void checkChar(char inChar) {
- if (inChar != '0' && inChar != '1') {
- throw new IllegalArgumentException("Input must be a binary string.");
- }
- }
-
- public static int compute(char charA, char charB) {
- checkChar(charA);
- checkChar(charB);
- return charA == charB ? 0 : 1;
- }
-
- public static int compute(String bitsStrA, String bitsStrB) {
- if (bitsStrA.length() != bitsStrB.length()) {
- throw new IllegalArgumentException("Input strings must have the same length.");
- }
-
- int totalErrorBitCount = 0;
-
- for (int i = 0; i < bitsStrA.length(); i++) {
- totalErrorBitCount += compute(bitsStrA.charAt(i), bitsStrB.charAt(i));
- }
-
- return totalErrorBitCount;
- }
-}
diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
index 399c3f1e041f..d096e0a8d7cd 100644
--- a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
+++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
@@ -41,7 +41,7 @@ public static void resolveCollision(Body a, Body b) {
double dy = b.y - a.y;
double dist = Math.hypot(dx, dy);
- if (dist == 0) {
+ if (dist < a.radius + b.radius) {
return; // overlapping
}
diff --git a/src/main/java/com/thealgorithms/physics/Relativity.java b/src/main/java/com/thealgorithms/physics/Relativity.java
new file mode 100644
index 000000000000..ed823c2cc879
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/Relativity.java
@@ -0,0 +1,81 @@
+package com.thealgorithms.physics;
+
+/**
+ * Implements relativity theory formulae.
+ * Provides simple static methods to calculate length contraction and time dilation
+ * in the laboratory frame with respect to the object's own frame, and velocity
+ * with respect to the moving frame.
+ *
+ * @see Wikipedia
+ */
+public final class Relativity {
+
+ /* Speed of light in m s^-1 */
+ public static final double SPEED_OF_LIGHT = 299792458.0;
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ */
+ private Relativity() {
+ }
+
+ /**
+ * Calculates the gamma parameter that is of paramount importance in relativity
+ * theory. It is a dimensionless parameter that is equal to 1 for zero velocity
+ * but tends to infinity when velocity approaches the speed of light.
+ *
+ * @param v The velocity (m/s).
+ * @return The value of gamma parameter.
+ */
+ public static double gamma(double v) {
+ if (Math.abs(v) >= SPEED_OF_LIGHT) {
+ throw new IllegalArgumentException("Speed must be lower than the speed of light");
+ }
+ return 1.0 / Math.sqrt(1 - v * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT));
+ }
+
+ /**
+ * Calculates the length of an object in the moving frame.
+ *
+ * @param length The length of an object in its own frame (m).
+ * @param v The velocity of the object (m/s).
+ * @return The length of an object in the laboratory frame (m).
+ */
+ public static double lengthContraction(double length, double v) {
+ if (length < 0) {
+ throw new IllegalArgumentException("Length must be non-negative");
+ }
+ return length / gamma(v);
+ }
+
+ /**
+ * Calculates the time that has passed in the moving frame.
+ *
+ * @param length The time that has passed in the object's own frame (s).
+ * @param v The velocity of the object (m/s).
+ * @return The time that has passed in the laboratory frame (s).
+ */
+ public static double timeDilation(double time, double v) {
+ if (time < 0) {
+ throw new IllegalArgumentException("Time must be non-negative");
+ }
+ return time * gamma(v);
+ }
+
+ /**
+ * Calculates the velocity with respect to the moving frame.
+ *
+ * @param v1 The velocity of the object with respect to laboratory frame (m/s).
+ * @param v The velocity of the moving frame (m/s).
+ * @return The velocity with respect to the moving frame (m/s).
+ */
+ public static double velocityAddition(double v1, double v) {
+ if (Math.abs(v1) > SPEED_OF_LIGHT) {
+ throw new IllegalArgumentException("Speed must not exceed the speed of light");
+ }
+ if (Math.abs(v) >= SPEED_OF_LIGHT) {
+ throw new IllegalArgumentException("Frame speed must be lower than the speed of light");
+ }
+ return (v1 - v) / (1 - v1 * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT));
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java
new file mode 100644
index 000000000000..14a02a2de4d0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * Implements an algorithm to efficiently compute the sum of elements
+ * between any two indices in an integer array using the Prefix Sum technique.
+ *
+ *
+ * Given an array nums, this algorithm precomputes the prefix sum array
+ * to allow O(1) sum queries for any range [left, right].
+ *
+ *
+ *
+ * Let prefixSum[i] be the sum of elements from index 0 to i-1.
+ * The sum of elements from left to right is:
+ *
+ *
+ * prefixSum[right + 1] - prefixSum[left]
+ *
+ *
+ *
+ *
+ * Time Complexity: O(N) for preprocessing, O(1) per query
+ * Space Complexity: O(N)
+ *
+ *
+ * @author Ruturaj Jadhav, ruturajjadhav07
+ */
+public final class RangeSumQuery {
+
+ private RangeSumQuery() {
+ // Utility class; prevent instantiation
+ }
+
+ /**
+ * Computes the prefix sum array for efficient range queries.
+ *
+ * @param nums The input integer array.
+ * @return Prefix sum array where prefixSum[i+1] = sum of nums[0..i].
+ * @throws IllegalArgumentException if nums is null.
+ */
+ public static int[] buildPrefixSum(int[] nums) {
+ if (nums == null) {
+ throw new IllegalArgumentException("Input array cannot be null");
+ }
+
+ int n = nums.length;
+ int[] prefixSum = new int[n + 1];
+ for (int i = 0; i < n; i++) {
+ prefixSum[i + 1] = prefixSum[i] + nums[i];
+ }
+ return prefixSum;
+ }
+
+ /**
+ * Returns the sum of elements from index left to right (inclusive)
+ * using the provided prefix sum array.
+ *
+ * @param prefixSum The prefix sum array computed using buildPrefixSum.
+ * @param left The start index (inclusive).
+ * @param right The end index (inclusive).
+ * @return The sum of elements in the range [left, right].
+ * @throws IllegalArgumentException if indices are invalid.
+ */
+ public static int sumRange(int[] prefixSum, int left, int right) {
+ if (prefixSum == null) {
+ throw new IllegalArgumentException("Prefix sum array cannot be null");
+ }
+ if (left < 0 || right >= prefixSum.length - 1 || left > right) {
+ throw new IllegalArgumentException("Invalid range indices");
+ }
+ return prefixSum[right + 1] - prefixSum[left];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java
new file mode 100644
index 000000000000..d6a6bbc01663
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java
@@ -0,0 +1,72 @@
+package com.thealgorithms.prefixsum;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implements an algorithm to count the number of continuous subarrays
+ * whose sum equals a given value k.
+ *
+ *
+ * This algorithm uses the Prefix Sum technique combined with a HashMap
+ * to achieve O(N) time complexity.
+ *
+ *
+ *
+ * Let prefixSum[i] be the sum of elements from index 0 to i.
+ * A subarray (j + 1) to i has sum k if:
+ *
+ *
+ * prefixSum[i] - prefixSum[j] = k
+ *
+ *
+ *
+ *
+ * The HashMap stores the frequency of each prefix sum encountered so far.
+ *
+ *
+ *
+ * Time Complexity: O(N)
+ * Space Complexity: O(N)
+ *
+ *
+ * @see Prefix Sum (Wikipedia)
+ * @author Ruturaj Jadhav, ruturajjadhav07
+ */
+public final class SubarraySumEqualsK {
+
+ private SubarraySumEqualsK() {
+ // Utility class; prevent instantiation
+ }
+
+ /**
+ * Counts the number of subarrays whose sum equals k.
+ *
+ * @param nums The input integer array.
+ * @param k The target sum.
+ * @return The number of continuous subarrays summing to k.
+ * @throws IllegalArgumentException if nums is null.
+ */
+ public static int countSubarrays(int[] nums, int k) {
+ if (nums == null) {
+ throw new IllegalArgumentException("Input array cannot be null");
+ }
+
+ Map prefixSumFrequency = new HashMap<>();
+ prefixSumFrequency.put(0L, 1);
+
+ long prefixSum = 0;
+ int count = 0;
+
+ for (int num : nums) {
+ prefixSum += num;
+
+ long requiredSum = prefixSum - k;
+ count += prefixSumFrequency.getOrDefault(requiredSum, 0);
+
+ prefixSumFrequency.put(prefixSum, prefixSumFrequency.getOrDefault(prefixSum, 0) + 1);
+ }
+
+ return count;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
index 72e9a14ac070..d94bef69cd3a 100644
--- a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
+++ b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
@@ -3,27 +3,32 @@
import java.util.List;
/**
- * The {@code TowerOfHanoi} class provides a recursive solution to the Tower of Hanoi puzzle.
- * This puzzle involves moving a set of discs from one pole to another, following specific rules:
+ * Recursive solution to the Tower of Hanoi puzzle.
+ *
+ *
+ * The puzzle rules are:
* 1. Only one disc can be moved at a time.
* 2. A disc can only be placed on top of a larger disc.
* 3. All discs must start on one pole and end on another.
+ *
*
- * This implementation recursively calculates the steps required to solve the puzzle and stores them
- * in a provided list.
+ *
+ * The recursion follows three steps:
+ * 1. Move {@code n-1} discs from start to intermediate.
+ * 2. Move the largest disc from start to end.
+ * 3. Move {@code n-1} discs from intermediate to end.
+ *
*
*
- * For more information about the Tower of Hanoi, see
- * Tower of Hanoi on Wikipedia.
+ * Time Complexity: O(2^n) - exponential due to recursive expansion.
+ * Space Complexity: O(n) - recursion stack depth.
*
*
- * The {@code shift} method takes the number of discs and the names of the poles,
- * and appends the steps required to solve the puzzle to the provided list.
- * Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem.
- * Space Complexity: O(n) - Linear space complexity due to the recursion stack.
- * Wikipedia: https://en.wikipedia.org/wiki/Tower_of_Hanoi
+ *
+ * See Tower of Hanoi on Wikipedia.
+ *
*/
-final class TowerOfHanoi {
+public final class TowerOfHanoi {
private TowerOfHanoi() {
}
@@ -36,6 +41,7 @@ private TowerOfHanoi() {
* @param intermediatePole The name of the intermediate pole used as a temporary holding area.
* @param endPole The name of the end pole to which discs are moved.
* @param result A list to store the steps required to solve the puzzle.
+ * @throws IllegalArgumentException if {@code n} is negative.
*
*
* This method is called recursively to move n-1 discs
@@ -51,15 +57,20 @@ private TowerOfHanoi() {
*
*/
public static void shift(int n, String startPole, String intermediatePole, String endPole, List result) {
- if (n != 0) {
- // Move n-1 discs from startPole to intermediatePole
- shift(n - 1, startPole, endPole, intermediatePole, result);
+ if (n < 0) {
+ throw new IllegalArgumentException("Number of discs must be non-negative");
+ }
+ if (n == 0) {
+ return;
+ }
- // Add the move of the nth disc from startPole to endPole
- result.add(String.format("Move %d from %s to %s", n, startPole, endPole));
+ // Move n-1 discs from startPole to intermediatePole
+ shift(n - 1, startPole, endPole, intermediatePole, result);
- // Move the n-1 discs from intermediatePole to endPole
- shift(n - 1, intermediatePole, startPole, endPole, result);
- }
+ // Add the move of the nth disc from startPole to endPole
+ result.add(String.format("Move %d from %s to %s", n, startPole, endPole));
+
+ // Move the n-1 discs from intermediatePole to endPole
+ shift(n - 1, intermediatePole, startPole, endPole, result);
}
}
diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java
index 7a5361b280ea..ca873fc6eafa 100644
--- a/src/main/java/com/thealgorithms/searches/BinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java
@@ -58,11 +58,18 @@ class BinarySearch implements SearchAlgorithm {
*/
@Override
public > int find(T[] array, T key) {
- // Handle edge case: empty array
+ // Handle edge case: null or empty array
if (array == null || array.length == 0) {
return -1;
}
+ // Handle edge case: null key
+ // Searching for null in an array of Comparables is undefined behavior
+ // Return -1 to indicate not found rather than throwing NPE
+ if (key == null) {
+ return -1;
+ }
+
// Delegate to the core search implementation
return search(array, key, 0, array.length - 1);
}
diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
index 3ac6be25bf53..d24cc1c774bc 100644
--- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
+++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
@@ -1,3 +1,14 @@
+/**
+ * Interpolation Search estimates the position of the target value
+ * based on the distribution of values.
+ *
+ * Example:
+ * Input: [10, 20, 30, 40], target = 30
+ * Output: Index = 2
+ *
+ * Time Complexity: O(log log n) (average case)
+ * Space Complexity: O(1)
+ */
package com.thealgorithms.searches;
/**
diff --git a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java
index 05fab0534267..d051dbc7b823 100644
--- a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java
@@ -3,50 +3,53 @@
import com.thealgorithms.devutils.searches.SearchAlgorithm;
/**
- * Binary search is one of the most popular algorithms This class represents
- * iterative version {@link BinarySearch} Iterative binary search is likely to
- * have lower constant factors because it doesn't involve the overhead of
- * manipulating the call stack. But in java the recursive version can be
- * optimized by the compiler to this version.
+ * Binary search is one of the most popular algorithms.
+ * This class represents the iterative version of {@link BinarySearch}.
*
- *
- * Worst-case performance O(log n) Best-case performance O(1) Average
- * performance O(log n) Worst-case space complexity O(1)
+ *
Iterative binary search avoids recursion overhead and uses constant space.
*
- * @author Gabriele La Greca : https://github.com/thegabriele97
- * @author Podshivalov Nikita (https://github.com/nikitap492)
+ *
Performance:
+ *
+ * - Best-case: O(1)
+ * - Average-case: O(log n)
+ * - Worst-case: O(log n)
+ * - Space complexity: O(1)
+ *
+ *
+ * @author Gabriele La Greca
+ * @author Podshivalov Nikita
* @see SearchAlgorithm
* @see BinarySearch
*/
public final class IterativeBinarySearch implements SearchAlgorithm {
/**
- * This method implements an iterative version of binary search algorithm
+ * Performs iterative binary search on a sorted array.
*
- * @param array a sorted array
- * @param key the key to search in array
- * @return the index of key in the array or -1 if not found
+ * @param array the sorted array
+ * @param key the element to search
+ * @param type of elements (must be Comparable)
+ * @return index of the key if found, otherwise -1
*/
@Override
public > int find(T[] array, T key) {
- int l;
- int r;
- int k;
- int cmp;
+ if (array == null || array.length == 0 || key == null) {
+ return -1;
+ }
- l = 0;
- r = array.length - 1;
+ int left = 0;
+ int right = array.length - 1;
- while (l <= r) {
- k = (l + r) >>> 1;
- cmp = key.compareTo(array[k]);
+ while (left <= right) {
+ int mid = (left + right) >>> 1;
+ int cmp = key.compareTo(array[mid]);
if (cmp == 0) {
- return k;
+ return mid;
} else if (cmp < 0) {
- r = --k;
+ right = mid - 1;
} else {
- l = ++k;
+ left = mid + 1;
}
}
diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java
index 8dcec3a819a4..5074aa7845c8 100644
--- a/src/main/java/com/thealgorithms/searches/JumpSearch.java
+++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java
@@ -12,26 +12,57 @@
* Once the range is found, a linear search is performed within that block.
*
*
- * The Jump Search algorithm is particularly effective for large sorted arrays where the cost of
- * performing a linear search on the entire array would be prohibitive.
+ * How it works:
+ *
+ * - Calculate the optimal block size as βn (square root of array length)
+ * - Jump ahead by the block size until the current element is greater than the target
+ * - Perform a linear search backwards within the identified block
+ *
*
*
- * Worst-case performance: O(βN)
- * Best-case performance: O(1)
- * Average performance: O(βN)
- * Worst-case space complexity: O(1)
+ * Example:
+ * Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], Target: 9
+ * Step 1: Jump from index 0 β 3 β 6 (9 < 13, so we found the block)
+ * Step 2: Linear search from index 3 to 6: found 9 at index 4
+ * Result: Index = 4
+ *
+ *
+ * Time Complexity:
+ * - Best-case: O(1) - element found at first position
+ * - Average: O(βn) - optimal block size reduces jumps
+ * - Worst-case: O(βn) - element at end of array or not present
+ *
+ *
+ * Space Complexity: O(1) - only uses a constant amount of extra space
+ *
+ *
+ * Edge Cases:
+ *
+ * - Empty array β returns -1
+ * - Element not present β returns -1
+ * - Single element array
+ *
+ *
+ * Note: Jump Search requires a sorted array. For unsorted arrays, use Linear Search.
+ * Compared to Linear Search (O(n)), Jump Search is faster for large arrays.
+ * Compared to Binary Search (O(log n)), Jump Search is less efficient but may be
+ * preferable when jumping through a linked list or when backward scanning is costly.
*
*
* This class implements the {@link SearchAlgorithm} interface, providing a generic search method
* for any comparable type.
+ *
+ * @see SearchAlgorithm
+ * @see BinarySearch
+ * @see LinearSearch
*/
public class JumpSearch implements SearchAlgorithm {
/**
* Jump Search algorithm implementation.
*
- * @param array the sorted array containing elements
- * @param key the element to be searched
+ * @param array the sorted array containing elements (must be sorted in ascending order)
+ * @param key the element to be searched for
* @return the index of {@code key} if found, otherwise -1
*/
@Override
diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java
index cb483d8dfedc..3f273e167f0a 100644
--- a/src/main/java/com/thealgorithms/searches/LinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java
@@ -1,6 +1,20 @@
+/**
+ * Performs Linear Search on an array.
+ *
+ * Linear search checks each element one by one until the target is found
+ * or the array ends.
+ *
+ * Example:
+ * Input: [2, 4, 6, 8], target = 6
+ * Output: Index = 2
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(1)
+ */
package com.thealgorithms.searches;
import com.thealgorithms.devutils.searches.SearchAlgorithm;
+
/**
* Linear Search is a simple searching algorithm that checks
* each element of the array sequentially until the target
@@ -20,7 +34,6 @@
* @see BinarySearch
* @see SearchAlgorithm
*/
-
public class LinearSearch implements SearchAlgorithm {
/**
@@ -28,15 +41,22 @@ public class LinearSearch implements SearchAlgorithm {
*
* @param array List to be searched
* @param value Key being searched for
- * @return Location of the key
+ * @return Location of the key, -1 if array is null or empty, or key not found
*/
@Override
public > int find(T[] array, T value) {
+
+ if (array == null || array.length == 0 || value == null) {
+ return -1;
+ }
+
for (int i = 0; i < array.length; i++) {
- if (array[i].compareTo(value) == 0) {
+ T currentElement = array[i];
+ if (currentElement != null && currentElement.compareTo(value) == 0) {
return i;
}
}
+
return -1;
}
}
diff --git a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java b/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java
deleted file mode 100644
index 495e2e41bc5b..000000000000
--- a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.thealgorithms.searches;
-
-import com.thealgorithms.devutils.searches.SearchAlgorithm;
-
-/**
- * Binary search is one of the most popular algorithms The algorithm finds the
- * position of a target value within a sorted array
- *
- *
- * Worst-case performance O(log n) Best-case performance O(1) Average
- * performance O(log n) Worst-case space complexity O(1)
- *
- * @author D Sunil (https://github.com/sunilnitdgp)
- * @see SearchAlgorithm
- */
-
-public class PerfectBinarySearch implements SearchAlgorithm {
-
- /**
- * @param array is an array where the element should be found
- * @param key is an element which should be found
- * @param is any comparable type
- * @return index of the element
- */
- @Override
- public > int find(T[] array, T key) {
- return search(array, key, 0, array.length - 1);
- }
-
- /**
- * This method implements the Generic Binary Search iteratively.
- *
- * @param array The array to make the binary search
- * @param key The number you are looking for
- * @return the location of the key, or -1 if not found
- */
- private static > int search(T[] array, T key, int left, int right) {
- while (left <= right) {
- int median = (left + right) >>> 1;
- int comp = key.compareTo(array[median]);
-
- if (comp == 0) {
- return median; // Key found
- }
-
- if (comp < 0) {
- right = median - 1; // Adjust the right bound
- } else {
- left = median + 1; // Adjust the left bound
- }
- }
- return -1; // Key not found
- }
-}
diff --git a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java
index daf0c12c0978..1716e78964ae 100644
--- a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java
@@ -23,28 +23,27 @@ public int find(T[] arr, T target) {
// Recursive binary search function
public int binsear(T[] arr, int left, int right, T target) {
- if (right >= left) {
- int mid = left + (right - left) / 2;
-
- // Compare the element at the middle with the target
- int comparison = arr[mid].compareTo(target);
+ if (right < left) {
+ // Element is not present in the array
+ return -1;
+ }
+ final int mid = left + (right - left) / 2;
- // If the element is equal to the target, return its index
- if (comparison == 0) {
- return mid;
- }
+ // Compare the element at the middle with the target
+ final int comparison = arr[mid].compareTo(target);
- // If the element is greater than the target, search in the left subarray
- if (comparison > 0) {
- return binsear(arr, left, mid - 1, target);
- }
+ // If the element is equal to the target, return its index
+ if (comparison == 0) {
+ return mid;
+ }
- // Otherwise, search in the right subarray
- return binsear(arr, mid + 1, right, target);
+ // If the element is greater than the target, search in the left subarray
+ if (comparison > 0) {
+ return binsear(arr, left, mid - 1, target);
}
- // Element is not present in the array
- return -1;
+ // Otherwise, search in the right subarray
+ return binsear(arr, mid + 1, right, target);
}
public static void main(String[] args) {
diff --git a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java b/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java
deleted file mode 100644
index 6a2a46c2821f..000000000000
--- a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.thealgorithms.searches;
-public final class SortOrderAgnosticBinarySearch {
- private SortOrderAgnosticBinarySearch() {
- }
- public static int find(int[] arr, int key) {
- int start = 0;
- int end = arr.length - 1;
- boolean arrDescending = arr[start] > arr[end]; // checking for Array is in ascending order or descending order.
- while (start <= end) {
- int mid = end - start / 2;
- if (arr[mid] == key) {
- return mid;
- }
- if (arrDescending) { // boolean is true then our array is in descending order
- if (key < arr[mid]) {
- start = mid + 1;
- } else {
- end = mid - 1;
- }
- } else { // otherwise our array is in ascending order
- if (key > arr[mid]) {
- start = mid + 1;
- } else {
- end = mid - 1;
- }
- }
- }
- return -1;
- }
-}
diff --git a/src/main/java/com/thealgorithms/sorts/InsertionSort.java b/src/main/java/com/thealgorithms/sorts/InsertionSort.java
index fdbfd9cd1cfa..1e42f2a61271 100644
--- a/src/main/java/com/thealgorithms/sorts/InsertionSort.java
+++ b/src/main/java/com/thealgorithms/sorts/InsertionSort.java
@@ -33,30 +33,30 @@ public > T[] sort(T[] array) {
}
/**
- * Sorts a subarray of the given array using the standard Insertion Sort algorithm.
+ * Sorts a subarray of the given items using the standard Insertion Sort algorithm.
*
- * @param array The array to be sorted
- * @param lo The starting index of the subarray
- * @param hi The ending index of the subarray (exclusive)
- * @param The type of elements in the array, which must be comparable
- * @return The sorted array
+ * @param items The items to be sorted
+ * @param startIndex The starting index of the subarray
+ * @param endIndex The ending index of the subarray (exclusive)
+ * @param The type of elements in the items, which must be comparable
+ * @return The sorted items
*/
- public > T[] sort(T[] array, final int lo, final int hi) {
- if (array == null || lo >= hi) {
- return array;
+ public > T[] sort(T[] items, final int startIndex, final int endIndex) {
+ if (items == null || startIndex >= endIndex) {
+ return items;
}
- for (int i = lo + 1; i < hi; i++) {
- final T key = array[i];
+ for (int i = startIndex + 1; i < endIndex; i++) {
+ final T key = items[i];
int j = i - 1;
- while (j >= lo && SortUtils.less(key, array[j])) {
- array[j + 1] = array[j];
+ while (j >= startIndex && SortUtils.less(key, items[j])) {
+ items[j + 1] = items[j];
j--;
}
- array[j + 1] = key;
+ items[j + 1] = key;
}
- return array;
+ return items;
}
/**
diff --git a/src/main/java/com/thealgorithms/sorts/MergeSort.java b/src/main/java/com/thealgorithms/sorts/MergeSort.java
index f7a7c8da004d..5db9c48b4f61 100644
--- a/src/main/java/com/thealgorithms/sorts/MergeSort.java
+++ b/src/main/java/com/thealgorithms/sorts/MergeSort.java
@@ -10,7 +10,7 @@
@SuppressWarnings("rawtypes")
class MergeSort implements SortAlgorithm {
- private Comparable[] aux;
+ private Comparable[] tempArray;
/**
* Generic merge sort algorithm.
@@ -26,7 +26,7 @@ class MergeSort implements SortAlgorithm {
*/
@Override
public > T[] sort(T[] unsorted) {
- aux = new Comparable[unsorted.length];
+ tempArray = new Comparable[unsorted.length];
doSort(unsorted, 0, unsorted.length - 1);
return unsorted;
}
@@ -58,17 +58,17 @@ private > void doSort(T[] arr, int left, int right) {
private > void merge(T[] arr, int left, int mid, int right) {
int i = left;
int j = mid + 1;
- System.arraycopy(arr, left, aux, left, right + 1 - left);
+ System.arraycopy(arr, left, tempArray, left, right + 1 - left);
for (int k = left; k <= right; k++) {
if (j > right) {
- arr[k] = (T) aux[i++];
+ arr[k] = (T) tempArray[i++];
} else if (i > mid) {
- arr[k] = (T) aux[j++];
- } else if (less(aux[j], aux[i])) {
- arr[k] = (T) aux[j++];
+ arr[k] = (T) tempArray[j++];
+ } else if (less(tempArray[j], tempArray[i])) {
+ arr[k] = (T) tempArray[j++];
} else {
- arr[k] = (T) aux[i++];
+ arr[k] = (T) tempArray[i++];
}
}
}
diff --git a/src/main/java/com/thealgorithms/sorts/PancakeSort.java b/src/main/java/com/thealgorithms/sorts/PancakeSort.java
index 6079672a1d77..6522aefd7ae3 100644
--- a/src/main/java/com/thealgorithms/sorts/PancakeSort.java
+++ b/src/main/java/com/thealgorithms/sorts/PancakeSort.java
@@ -15,7 +15,7 @@ public > T[] sort(T[] array) {
}
for (int currentSize = 0; currentSize < array.length; currentSize++) {
- int maxIndex = findMaxIndex(array, currentSize);
+ int maxIndex = findIndexOfMax(array, currentSize);
SortUtils.flip(array, maxIndex, array.length - 1 - currentSize);
}
@@ -30,7 +30,7 @@ public > T[] sort(T[] array) {
* @param the type of elements in the array
* @return the index of the maximum element
*/
- private > int findMaxIndex(T[] array, int currentSize) {
+ private > int findIndexOfMax(T[] array, int currentSize) {
T max = array[0];
int maxIndex = 0;
for (int i = 0; i < array.length - currentSize; i++) {
diff --git a/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java
new file mode 100644
index 000000000000..2e9f6863c90a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java
@@ -0,0 +1,67 @@
+package com.thealgorithms.stacks;
+
+import java.util.Stack;
+
+/**
+ * Calculates the stock span for each day in a series of stock prices.
+ *
+ * The span of a price on a given day is the number of consecutive days ending on that day
+ * for which the price was less than or equal to the current day's price.
+ *
+ *
Idea: keep a stack of indices whose prices are strictly greater than the current price.
+ * While processing each day, pop smaller or equal prices because they are part of the current
+ * span. After popping, the nearest greater price left on the stack tells us where the span stops.
+ *
+ *
Time complexity is O(n) because each index is pushed onto the stack once and popped at most
+ * once, so the total number of stack operations grows linearly with the number of prices. This
+ * makes the stack approach efficient because it avoids rechecking earlier days repeatedly, unlike
+ * a naive nested-loop solution that can take O(n^2) time.
+ *
+ *
Example: for prices [100, 80, 60, 70, 60, 75, 85], the spans are
+ * [1, 1, 1, 2, 1, 4, 6].
+ */
+public final class StockSpanProblem {
+ private StockSpanProblem() {
+ }
+
+ /**
+ * Calculates the stock span for each price in the input array.
+ *
+ * @param prices the stock prices
+ * @return the span for each day
+ * @throws IllegalArgumentException if the input array is null
+ */
+ public static int[] calculateSpan(int[] prices) {
+ if (prices == null) {
+ throw new IllegalArgumentException("Input prices cannot be null");
+ }
+
+ int[] spans = new int[prices.length];
+ Stack stack = new Stack<>();
+
+ // Small example:
+ // prices = [100, 80, 60, 70]
+ // spans = [ 1, 1, 1, 2]
+ // When we process 70, we pop 60 because 60 <= 70, so the span becomes 2.
+ //
+ // The stack stores indices of days with prices greater than the current day's price.
+ for (int index = 0; index < prices.length; index++) {
+ // Remove all previous days whose prices are less than or equal to the current price.
+ while (!stack.isEmpty() && prices[stack.peek()] <= prices[index]) {
+ stack.pop();
+ }
+
+ // If the stack is empty, there is no earlier day with a greater price,
+ // so the count will be from day 0 to this day (index + 1).
+ //
+ // Otherwise, the span is the number of days between
+ // the nearest earlier day with a greater price and the current day.
+ spans[index] = stack.isEmpty() ? index + 1 : index - stack.peek();
+
+ // Store the current index as a candidate for future span calculations.
+ stack.push(index);
+ }
+
+ return spans;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/Alphabetical.java b/src/main/java/com/thealgorithms/strings/Alphabetical.java
index ef2974eb427d..37b1fb068b44 100644
--- a/src/main/java/com/thealgorithms/strings/Alphabetical.java
+++ b/src/main/java/com/thealgorithms/strings/Alphabetical.java
@@ -1,32 +1,58 @@
package com.thealgorithms.strings;
+import java.util.Locale;
+
/**
- * Utility class for checking if a string's characters are in alphabetical order.
+ * Utility class for checking whether a string's characters are in non-decreasing
+ * lexicographical order based on Unicode code points (case-insensitive).
+ *
+ * This does NOT implement language-aware alphabetical ordering (collation rules).
+ * It simply compares lowercase Unicode character values.
*
- * Alphabetical order is a system whereby character strings are placed in order
- * based on the position of the characters in the conventional ordering of an
- * alphabet.
+ * Non-letter characters are not allowed and will cause the check to fail.
*
- * Reference: Wikipedia: Alphabetical Order
+ * Reference:
+ * Wikipedia: Alphabetical order
*/
public final class Alphabetical {
+
private Alphabetical() {
}
/**
- * Checks whether the characters in the given string are in alphabetical order.
- * Non-letter characters will cause the check to fail.
+ * Checks whether the characters in the given string are in non-decreasing
+ * lexicographical order (case-insensitive).
+ *
+ * Rules:
+ *
+ * - String must not be null or blank
+ * - All characters must be letters
+ * - Comparison is based on lowercase Unicode values
+ * - Order must be non-decreasing (equal or increasing allowed)
+ *
*
- * @param s the input string
- * @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false}
+ * @param s input string
+ * @return {@code true} if characters are in non-decreasing order, otherwise {@code false}
*/
public static boolean isAlphabetical(String s) {
- s = s.toLowerCase();
- for (int i = 0; i < s.length() - 1; ++i) {
- if (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) {
+ if (s == null || s.isBlank()) {
+ return false;
+ }
+
+ String normalized = s.toLowerCase(Locale.ROOT);
+
+ if (!Character.isLetter(normalized.charAt(0))) {
+ return false;
+ }
+
+ for (int i = 1; i < normalized.length(); i++) {
+ char prev = normalized.charAt(i - 1);
+ char curr = normalized.charAt(i);
+
+ if (!Character.isLetter(curr) || prev > curr) {
return false;
}
}
- return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1));
+ return true;
}
}
diff --git a/src/main/java/com/thealgorithms/strings/KMP.java b/src/main/java/com/thealgorithms/strings/KMP.java
index 07d3b0415006..0317abe6f39a 100644
--- a/src/main/java/com/thealgorithms/strings/KMP.java
+++ b/src/main/java/com/thealgorithms/strings/KMP.java
@@ -1,5 +1,8 @@
package com.thealgorithms.strings;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Implementation of KnuthβMorrisβPratt algorithm Usage: see the main function
* for an example
@@ -8,16 +11,19 @@ public final class KMP {
private KMP() {
}
- // a working example
-
- public static void main(String[] args) {
- final String haystack = "AAAAABAAABA"; // This is the full string
- final String needle = "AAAA"; // This is the substring that we want to find
- kmpMatcher(haystack, needle);
- }
+ /**
+ * find the starting index in string haystack[] that matches the search word P[]
+ *
+ * @param haystack The text to be searched
+ * @param needle The pattern to be searched for
+ * @return A list of starting indices where the pattern is found
+ */
+ public static List kmpMatcher(final String haystack, final String needle) {
+ List occurrences = new ArrayList<>();
+ if (haystack == null || needle == null || needle.isEmpty()) {
+ return occurrences;
+ }
- // find the starting index in string haystack[] that matches the search word P[]
- public static void kmpMatcher(final String haystack, final String needle) {
final int m = haystack.length();
final int n = needle.length();
final int[] pi = computePrefixFunction(needle);
@@ -32,10 +38,11 @@ public static void kmpMatcher(final String haystack, final String needle) {
}
if (q == n) {
- System.out.println("Pattern starts: " + (i + 1 - n));
+ occurrences.add(i + 1 - n);
q = pi[q - 1];
}
}
+ return occurrences;
}
// return the prefix function
diff --git a/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java
new file mode 100644
index 000000000000..b8b10dcf4538
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java
@@ -0,0 +1,79 @@
+package com.thealgorithms.strings;
+
+/**
+ * Kasai's Algorithm for constructing the Longest Common Prefix (LCP) array.
+ *
+ *
+ * The LCP array stores the lengths of the longest common prefixes between
+ * lexicographically adjacent suffixes of a string. Kasai's algorithm computes
+ * this array in O(N) time given the string and its suffix array.
+ *
+ *
+ * @see LCP array - Wikipedia
+ */
+public final class KasaiAlgorithm {
+
+ private KasaiAlgorithm() {
+ }
+
+ /**
+ * Computes the LCP array using Kasai's algorithm.
+ *
+ * @param text the original string
+ * @param suffixArr the suffix array of the string
+ * @return the LCP array of length N, where LCP[i] is the length of the longest
+ * common prefix of the suffixes indexed by suffixArr[i] and suffixArr[i+1].
+ * The last element LCP[N-1] is always 0.
+ * @throws IllegalArgumentException if text or suffixArr is null, or their lengths differ
+ */
+ public static int[] kasai(String text, int[] suffixArr) {
+ if (text == null || suffixArr == null) {
+ throw new IllegalArgumentException("Text and suffix array must not be null.");
+ }
+ int n = text.length();
+ if (suffixArr.length != n) {
+ throw new IllegalArgumentException("Suffix array length must match text length.");
+ }
+ if (n == 0) {
+ return new int[0];
+ }
+
+ // Compute the inverse suffix array
+ // invSuff[i] stores the index of the suffix text.substring(i) in the suffix array
+ int[] invSuff = new int[n];
+ for (int i = 0; i < n; i++) {
+ if (suffixArr[i] < 0 || suffixArr[i] >= n) {
+ throw new IllegalArgumentException("Suffix array contains out-of-bounds index.");
+ }
+ invSuff[suffixArr[i]] = i;
+ }
+
+ int[] lcp = new int[n];
+ int k = 0; // Length of the longest common prefix
+
+ for (int i = 0; i < n; i++) {
+ // Suffix at index i has not a next suffix in suffix array
+ int rank = invSuff[i];
+ if (rank == n - 1) {
+ k = 0;
+ continue;
+ }
+
+ int nextSuffixIndex = suffixArr[rank + 1];
+
+ // Directly match characters to find LCP
+ while (i + k < n && nextSuffixIndex + k < n && text.charAt(i + k) == text.charAt(nextSuffixIndex + k)) {
+ k++;
+ }
+
+ lcp[rank] = k;
+
+ // Delete the starting character from the string
+ if (k > 0) {
+ k--;
+ }
+ }
+
+ return lcp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
index 6808cd50602f..51e8dc6b02c3 100644
--- a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
+++ b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
@@ -13,6 +13,12 @@ private LongestNonRepetitiveSubstring() {
/**
* Finds the length of the longest substring without repeating characters.
*
+ * Uses the sliding window technique with a HashMap to track
+ * the last seen index of each character.
+ *
+ * Time Complexity: O(n), where n is the length of the input string.
+ * Space Complexity: O(min(n, m)), where m is the size of the character set.
+ *
* @param s the input string
* @return the length of the longest non-repetitive substring
*/
diff --git a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java b/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java
deleted file mode 100644
index ca500357ba77..000000000000
--- a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.thealgorithms.strings;
-
-final class LongestPalindromicSubstring {
- private LongestPalindromicSubstring() {
- }
-
- /**
- * Finds the longest palindromic substring in the given string.
- *
- * @param s the input string
- * @return the longest palindromic substring
- */
- public static String longestPalindrome(String s) {
- if (s == null || s.isEmpty()) {
- return "";
- }
- String maxStr = "";
- for (int i = 0; i < s.length(); ++i) {
- for (int j = i; j < s.length(); ++j) {
- if (isValid(s, i, j) && (j - i + 1 > maxStr.length())) {
- maxStr = s.substring(i, j + 1);
- }
- }
- }
- return maxStr;
- }
-
- private static boolean isValid(String s, int lo, int hi) {
- int n = hi - lo + 1;
- for (int i = 0; i < n / 2; ++i) {
- if (s.charAt(lo + i) != s.charAt(hi - i)) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java
new file mode 100644
index 000000000000..87c9278fd4bf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java
@@ -0,0 +1,83 @@
+package com.thealgorithms.strings;
+
+/**
+ * Finds the longest substring that occurs at least twice in a given string.
+ *
+ * Uses the suffix array (via {@link SuffixArray}) and Kasai's algorithm
+ * to build the LCP (Longest Common Prefix) array, then returns the substring
+ * corresponding to the maximum LCP value.
+ *
+ * Time complexity: O(n logΒ² n) for suffix array construction + O(n) for LCP.
+ *
+ * @see Longest repeated substring problem
+ * @see SuffixArray
+ */
+public final class LongestRepeatedSubstring {
+
+ private LongestRepeatedSubstring() {
+ }
+
+ /**
+ * Returns the longest substring that appears at least twice in the given text.
+ *
+ * @param text the input string
+ * @return the longest repeated substring, or an empty string if none exists
+ */
+ public static String longestRepeatedSubstring(String text) {
+ if (text == null || text.length() <= 1) {
+ return "";
+ }
+
+ final int[] suffixArray = SuffixArray.buildSuffixArray(text);
+ final int[] lcp = buildLcpArray(text, suffixArray);
+
+ int maxLen = 0;
+ int maxIdx = 0;
+ for (int i = 0; i < lcp.length; i++) {
+ if (lcp[i] > maxLen) {
+ maxLen = lcp[i];
+ maxIdx = suffixArray[i + 1];
+ }
+ }
+
+ return text.substring(maxIdx, maxIdx + maxLen);
+ }
+
+ /**
+ * Builds the LCP (Longest Common Prefix) array using Kasai's algorithm.
+ *
+ * LCP[i] is the length of the longest common prefix between the suffixes
+ * at positions suffixArray[i] and suffixArray[i+1] in sorted order.
+ *
+ * @param text the original string
+ * @param suffixArray the suffix array of the string
+ * @return the LCP array of length n-1
+ */
+ static int[] buildLcpArray(String text, int[] suffixArray) {
+ final int n = text.length();
+ final int[] rank = new int[n];
+ final int[] lcp = new int[n - 1];
+
+ for (int i = 0; i < n; i++) {
+ rank[suffixArray[i]] = i;
+ }
+
+ int k = 0;
+ for (int i = 0; i < n; i++) {
+ if (rank[i] == n - 1) {
+ k = 0;
+ continue;
+ }
+ final int j = suffixArray[rank[i] + 1];
+ while (i + k < n && j + k < n && text.charAt(i + k) == text.charAt(j + k)) {
+ k++;
+ }
+ lcp[rank[i]] = k;
+ if (k > 0) {
+ k--;
+ }
+ }
+
+ return lcp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java
new file mode 100644
index 000000000000..bd686a0c5ba0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java
@@ -0,0 +1,56 @@
+package com.thealgorithms.strings;
+
+/**
+ * Moves all '#' characters to the end of the given string while preserving
+ * the order of the other characters.
+ *
+ * Example:
+ * Input : "h#e#l#llo"
+ * Output : "helllo###"
+ *
+ * The algorithm works by iterating through the string and collecting
+ * all non-# characters first, then filling the remaining positions
+ * with '#'.
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(n)
+ *
+ * @see Move all special characters to end - GeeksForGeeks
+ */
+public final class MoveHashToEnd {
+
+ /**
+ * Private constructor to prevent instantiation of utility class.
+ */
+ private MoveHashToEnd() {
+ }
+
+ /**
+ * Moves all '#' characters in the input string to the end.
+ *
+ * @param str the input string containing characters and '#'
+ * @return a new string with all '#' characters moved to the end
+ */
+ public static String moveHashToEnd(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+
+ char[] arr = str.toCharArray();
+ int insertPos = 0;
+
+ // Place all non-# characters at the beginning
+ for (char ch : arr) {
+ if (ch != '#') {
+ arr[insertPos++] = ch;
+ }
+ }
+
+ // Fill remaining positions with '#'
+ while (insertPos < arr.length) {
+ arr[insertPos++] = '#';
+ }
+
+ return new String(arr);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/RabinKarp.java b/src/main/java/com/thealgorithms/strings/RabinKarp.java
index bb8df3358453..be17f87c3656 100644
--- a/src/main/java/com/thealgorithms/strings/RabinKarp.java
+++ b/src/main/java/com/thealgorithms/strings/RabinKarp.java
@@ -1,32 +1,30 @@
package com.thealgorithms.strings;
-import java.util.Scanner;
+import java.util.ArrayList;
+import java.util.List;
/**
* @author Prateek Kumar Oraon (https://github.com/prateekKrOraon)
*
- An implementation of Rabin-Karp string matching algorithm
- Program will simply end if there is no match
+ * An implementation of Rabin-Karp string matching algorithm
+ * Program will simply end if there is no match
*/
public final class RabinKarp {
private RabinKarp() {
}
- public static Scanner scanner = null;
- public static final int ALPHABET_SIZE = 256;
+ private static final int ALPHABET_SIZE = 256;
- public static void main(String[] args) {
- scanner = new Scanner(System.in);
- System.out.println("Enter String");
- String text = scanner.nextLine();
- System.out.println("Enter pattern");
- String pattern = scanner.nextLine();
-
- int q = 101;
- searchPat(text, pattern, q);
+ public static List search(String text, String pattern) {
+ return search(text, pattern, 101);
}
- private static void searchPat(String text, String pattern, int q) {
+ public static List search(String text, String pattern, int q) {
+ List occurrences = new ArrayList<>();
+ if (text == null || pattern == null || pattern.isEmpty()) {
+ return occurrences;
+ }
+
int m = pattern.length();
int n = text.length();
int t = 0;
@@ -35,48 +33,42 @@ private static void searchPat(String text, String pattern, int q) {
int j = 0;
int i = 0;
- h = (int) Math.pow(ALPHABET_SIZE, m - 1) % q;
+ if (m > n) {
+ return new ArrayList<>();
+ }
+
+ // h = pow(ALPHABET_SIZE, m-1) % q
+ for (i = 0; i < m - 1; i++) {
+ h = h * ALPHABET_SIZE % q;
+ }
for (i = 0; i < m; i++) {
- // hash value is calculated for each character and then added with the hash value of the
- // next character for pattern as well as the text for length equal to the length of
- // pattern
p = (ALPHABET_SIZE * p + pattern.charAt(i)) % q;
t = (ALPHABET_SIZE * t + text.charAt(i)) % q;
}
for (i = 0; i <= n - m; i++) {
- // if the calculated hash value of the pattern and text matches then
- // all the characters of the pattern is matched with the text of length equal to length
- // of the pattern if all matches then pattern exist in string if not then the hash value
- // of the first character of the text is subtracted and hash value of the next character
- // after the end of the evaluated characters is added
if (p == t) {
- // if hash value matches then the individual characters are matched
for (j = 0; j < m; j++) {
- // if not matched then break out of the loop
if (text.charAt(i + j) != pattern.charAt(j)) {
break;
}
}
- // if all characters are matched then pattern exist in the string
if (j == m) {
- System.out.println("Pattern found at index " + i);
+ occurrences.add(i);
}
}
- // if i 0) {
+ result.deleteCharAt(result.length() - 1);
+ }
+ } else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java
new file mode 100644
index 000000000000..106de304cf40
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java
@@ -0,0 +1,56 @@
+package com.thealgorithms.strings;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class to find the top-k most frequent words.
+ *
+ * Words are ranked by frequency in descending order. For equal frequencies,
+ * words are ranked in lexicographical ascending order.
+ *
+ *
Reference:
+ * https://en.wikipedia.org/wiki/Top-k_problem
+ *
+ */
+public final class TopKFrequentWords {
+ private TopKFrequentWords() {
+ }
+
+ /**
+ * Finds the k most frequent words.
+ *
+ * @param words input array of words
+ * @param k number of words to return
+ * @return list of top-k words ordered by frequency then lexicographical order
+ * @throws IllegalArgumentException if words is null, k is negative, or words contains null
+ */
+ public static List findTopKFrequentWords(String[] words, int k) {
+ if (words == null) {
+ throw new IllegalArgumentException("Input words array cannot be null.");
+ }
+ if (k < 0) {
+ throw new IllegalArgumentException("k cannot be negative.");
+ }
+ if (k == 0 || words.length == 0) {
+ return List.of();
+ }
+
+ Map frequency = new HashMap<>();
+ for (String word : words) {
+ if (word == null) {
+ throw new IllegalArgumentException("Input words cannot contain null values.");
+ }
+ frequency.put(word, frequency.getOrDefault(word, 0) + 1);
+ }
+
+ List candidates = new ArrayList<>(frequency.keySet());
+ candidates.sort(Comparator.comparingInt(frequency::get).reversed().thenComparing(Comparator.naturalOrder()));
+
+ int limit = Math.min(k, candidates.size());
+ return new ArrayList<>(candidates.subList(0, limit));
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java
deleted file mode 100644
index 25a72f379dec..000000000000
--- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.thealgorithms.strings;
-
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.Map;
-
-/**
- * Validates if a given string has valid matching parentheses.
- *
- * A string is considered valid if:
- *
- * - Open brackets are closed by the same type of brackets.
- * - Brackets are closed in the correct order.
- * - Every closing bracket has a corresponding open bracket of the same type.
- *
- *
- * Allowed characters: '(', ')', '{', '}', '[', ']'
- */
-public final class ValidParentheses {
- private ValidParentheses() {
- }
-
- private static final Map BRACKET_PAIRS = Map.of(')', '(', '}', '{', ']', '[');
-
- /**
- * Checks if the input string has valid parentheses.
- *
- * @param s the string containing only bracket characters
- * @return true if valid, false otherwise
- * @throws IllegalArgumentException if the string contains invalid characters or is null
- */
- public static boolean isValid(String s) {
- if (s == null) {
- throw new IllegalArgumentException("Input string cannot be null");
- }
-
- Deque stack = new ArrayDeque<>();
-
- for (char c : s.toCharArray()) {
- if (BRACKET_PAIRS.containsValue(c)) {
- stack.push(c); // opening bracket
- } else if (BRACKET_PAIRS.containsKey(c)) {
- if (stack.isEmpty() || stack.pop() != BRACKET_PAIRS.get(c)) {
- return false;
- }
- } else {
- throw new IllegalArgumentException("Unexpected character: " + c);
- }
- }
-
- return stack.isEmpty();
- }
-}
diff --git a/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java
new file mode 100644
index 000000000000..63dec4846bbc
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java
@@ -0,0 +1,145 @@
+package com.thealgorithms.ciphers;
+
+import java.math.BigInteger;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Unit tests for ElGamalCipher.
+ * Includes property-based testing (homomorphism), probabilistic checks,
+ * and boundary validation.
+ */
+class ElGamalCipherTest {
+
+ private static ElGamalCipher.KeyPair sharedKeys;
+
+ @BeforeAll
+ static void setup() {
+ // Generate 256-bit keys for efficient unit testing
+ sharedKeys = ElGamalCipher.generateKeys(256);
+ }
+
+ @Test
+ @DisplayName("Test Key Generation Validity")
+ void testKeyGeneration() {
+ Assertions.assertNotNull(sharedKeys.p());
+ Assertions.assertNotNull(sharedKeys.g());
+ Assertions.assertNotNull(sharedKeys.x());
+ Assertions.assertNotNull(sharedKeys.y());
+
+ // Verify generator bounds: 1 < g < p
+ Assertions.assertTrue(sharedKeys.g().compareTo(BigInteger.ONE) > 0);
+ Assertions.assertTrue(sharedKeys.g().compareTo(sharedKeys.p()) < 0);
+
+ // Verify private key bounds: 1 < x < p-1
+ Assertions.assertTrue(sharedKeys.x().compareTo(BigInteger.ONE) > 0);
+ Assertions.assertTrue(sharedKeys.x().compareTo(sharedKeys.p().subtract(BigInteger.ONE)) < 0);
+ }
+
+ @Test
+ @DisplayName("Security Check: Probabilistic Encryption")
+ void testSemanticSecurity() {
+ // Encrypting the same message twice MUST yield different ciphertexts
+ // due to the random ephemeral key 'k'.
+ BigInteger message = new BigInteger("123456789");
+
+ ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+ ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+
+ // Check that the ephemeral keys (and thus 'a' components) were different
+ Assertions.assertNotEquals(c1.a(), c2.a(), "Ciphertexts must be randomized (Semantic Security violation)");
+ Assertions.assertNotEquals(c1.b(), c2.b());
+
+ // But both must decrypt to the original message
+ Assertions.assertEquals(ElGamalCipher.decrypt(c1, sharedKeys.x(), sharedKeys.p()), message);
+ Assertions.assertEquals(ElGamalCipher.decrypt(c2, sharedKeys.x(), sharedKeys.p()), message);
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideMessages")
+ @DisplayName("Parameterized Test: Encrypt and Decrypt various messages")
+ void testEncryptDecrypt(String messageStr) {
+ BigInteger message = new BigInteger(messageStr.getBytes());
+
+ // Skip if message exceeds the test key size (256 bits)
+ if (message.compareTo(sharedKeys.p()) >= 0) {
+ return;
+ }
+
+ ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+ BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
+
+ Assertions.assertEquals(message, decrypted, "Decrypted BigInteger must match original");
+ Assertions.assertEquals(messageStr, new String(decrypted.toByteArray()), "Decrypted string must match original");
+ }
+
+ static Stream provideMessages() {
+ return Stream.of("Hello World", "TheAlgorithms", "A", "1234567890", "!@#$%^&*()");
+ }
+
+ @Test
+ @DisplayName("Edge Case: Message equals 0")
+ void testMessageZero() {
+ BigInteger zero = BigInteger.ZERO;
+ ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(zero, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+ BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
+
+ Assertions.assertEquals(zero, decrypted, "Should successfully encrypt/decrypt zero");
+ }
+
+ @Test
+ @DisplayName("Edge Case: Message equals p-1")
+ void testMessageMaxBound() {
+ BigInteger pMinus1 = sharedKeys.p().subtract(BigInteger.ONE);
+ ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(pMinus1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+ BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p());
+
+ Assertions.assertEquals(pMinus1, decrypted, "Should successfully encrypt/decrypt p-1");
+ }
+
+ @Test
+ @DisplayName("Negative Test: Message >= p should fail")
+ void testMessageTooLarge() {
+ BigInteger tooLarge = sharedKeys.p();
+ Assertions.assertThrows(IllegalArgumentException.class, () -> ElGamalCipher.encrypt(tooLarge, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()));
+ }
+
+ @Test
+ @DisplayName("Negative Test: Decrypt with wrong private key")
+ void testWrongKeyDecryption() {
+ BigInteger message = new BigInteger("99999");
+ ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+
+ // Generate a fake private key
+ BigInteger wrongX = sharedKeys.x().add(BigInteger.ONE);
+
+ BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, wrongX, sharedKeys.p());
+
+ Assertions.assertNotEquals(message, decrypted, "Decryption with wrong key must yield incorrect result");
+ }
+
+ @Test
+ @DisplayName("Property Test: Multiplicative Homomorphism")
+ void testHomomorphism() {
+ BigInteger m1 = new BigInteger("50");
+ BigInteger m2 = BigInteger.TEN; // Fix: Replaced new BigInteger("10") with BigInteger.TEN
+
+ ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(m1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+ ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(m2, sharedKeys.p(), sharedKeys.g(), sharedKeys.y());
+
+ // Multiply ciphertexts component-wise: (a1*a2, b1*b2)
+ BigInteger aNew = c1.a().multiply(c2.a()).mod(sharedKeys.p());
+ BigInteger bNew = c1.b().multiply(c2.b()).mod(sharedKeys.p());
+ ElGamalCipher.CipherText cCombined = new ElGamalCipher.CipherText(aNew, bNew);
+
+ BigInteger decrypted = ElGamalCipher.decrypt(cCombined, sharedKeys.x(), sharedKeys.p());
+ BigInteger expected = m1.multiply(m2).mod(sharedKeys.p());
+
+ Assertions.assertEquals(expected, decrypted, "Cipher must satisfy multiplicative homomorphism");
+ }
+}
diff --git a/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java
new file mode 100644
index 000000000000..f919417899db
--- /dev/null
+++ b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java
@@ -0,0 +1,110 @@
+package com.thealgorithms.compression;
+
+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 org.junit.jupiter.api.Test;
+
+class HuffmanCodingTest {
+
+ @Test
+ void testStandardLifecycle() {
+ String input = "efficiency is key";
+ HuffmanCoding huffman = new HuffmanCoding(input);
+
+ String encoded = huffman.encode(input);
+ assertNotNull(encoded);
+ assertTrue(encoded.matches("[01]+"));
+ assertEquals(input, huffman.decode(encoded));
+ }
+
+ @Test
+ void testNullAndEmptyHandling() {
+ HuffmanCoding huffman = new HuffmanCoding("");
+ assertEquals("", huffman.encode(""));
+ assertEquals("", huffman.decode(""));
+
+ HuffmanCoding huffmanNull = new HuffmanCoding(null);
+ assertEquals("", huffmanNull.encode(null));
+ assertEquals("", huffmanNull.decode(null));
+ }
+
+ @Test
+ void testSingleCharacterEdgeCase() {
+ String input = "aaaaa";
+ HuffmanCoding huffman = new HuffmanCoding(input);
+
+ String encoded = huffman.encode(input);
+ assertEquals("00000", encoded);
+ assertEquals(input, huffman.decode(encoded));
+ }
+
+ @Test
+ void testUnicodeAndSpecialCharacters() {
+ // Tests spacing, symbols, non-latin alphabets, and surrogate pairs (emojis)
+ String input = "Hello, World! π\nLine 2: γγγ«γ‘γ―";
+ HuffmanCoding huffman = new HuffmanCoding(input);
+
+ String encoded = huffman.encode(input);
+ assertEquals(input, huffman.decode(encoded));
+ }
+
+ @Test
+ void testFailFastOnUnseenCharacter() {
+ HuffmanCoding huffman = new HuffmanCoding("abc");
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> huffman.encode("abcd") // 'd' was not in the original tree
+ );
+ assertTrue(exception.getMessage().contains("not found in Huffman dictionary"));
+ }
+
+ @Test
+ void testFailFastOnInvalidBinaryCharacter() {
+ HuffmanCoding huffman = new HuffmanCoding("abc");
+ String encoded = huffman.encode("abc");
+
+ // Inject a '2' into the binary stream
+ String corruptedEncoded = encoded + "2";
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(corruptedEncoded));
+ assertTrue(exception.getMessage().contains("contains invalid characters"));
+ }
+
+ @Test
+ void testFailFastOnIncompleteSequence() {
+ HuffmanCoding huffman = new HuffmanCoding("abcd");
+ String encoded = huffman.encode("abc");
+
+ // Truncate the last bit to simulate an incomplete byte/sequence transfer
+ String truncatedEncoded = encoded.substring(0, encoded.length() - 1);
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(truncatedEncoded));
+ assertTrue(exception.getMessage().contains("incomplete sequence"));
+ }
+
+ @Test
+ void testImmutabilityOfDictionary() {
+ HuffmanCoding huffman = new HuffmanCoding("abc");
+ var codes = huffman.getHuffmanCodes();
+
+ assertThrows(UnsupportedOperationException.class, () -> codes.put('z', "0101"));
+ }
+
+ @Test
+ void testStressVolume() {
+ StringBuilder sb = new StringBuilder();
+ // Generate a 100,000 character string
+ for (int i = 0; i < 100000; i++) {
+ sb.append((char) ('a' + (i % 26)));
+ }
+ String largeInput = sb.toString();
+
+ HuffmanCoding huffman = new HuffmanCoding(largeInput);
+ String encoded = huffman.encode(largeInput);
+
+ assertEquals(largeInput, huffman.decode(encoded));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java
index 8fdc93e1ca22..39e3fa0abe77 100644
--- a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java
@@ -255,4 +255,23 @@ public void testCapacityDoubling() {
assertEquals(3, array.getSize());
assertEquals("Charlie", array.get(2));
}
+
+ @Test
+ public void testContains() {
+ DynamicArray array = new DynamicArray<>();
+ array.add(1);
+ array.add(2);
+ array.add(3);
+
+ assertTrue(array.contains(2));
+ assertFalse(array.contains(5));
+ }
+
+ @Test
+ public void testContainsWithNull() {
+ DynamicArray array = new DynamicArray<>();
+ array.add(null);
+
+ assertTrue(array.contains(null));
+ }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
index 5d1733a3e97c..6b6e670a258b 100644
--- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
@@ -1,11 +1,9 @@
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.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
class GenericHashMapUsingArrayTest {
@@ -16,10 +14,10 @@ void testGenericHashmapWhichUsesArrayAndBothKeyAndValueAreStrings() {
map.put("Nepal", "Kathmandu");
map.put("India", "New Delhi");
map.put("Australia", "Sydney");
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals("Kathmandu", map.get("Nepal"));
- assertEquals("Sydney", map.get("Australia"));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals("Kathmandu", map.get("Nepal"));
+ Assertions.assertEquals("Sydney", map.get("Australia"));
}
@Test
@@ -29,12 +27,12 @@ void testGenericHashmapWhichUsesArrayAndKeyIsStringValueIsInteger() {
map.put("Nepal", 25);
map.put("India", 101);
map.put("Australia", 99);
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals(25, map.get("Nepal"));
- assertEquals(99, map.get("Australia"));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals(25, map.get("Nepal"));
+ Assertions.assertEquals(99, map.get("Australia"));
map.remove("Nepal");
- assertFalse(map.containsKey("Nepal"));
+ Assertions.assertFalse(map.containsKey("Nepal"));
}
@Test
@@ -44,11 +42,11 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() {
map.put(34, "Kathmandu");
map.put(46, "New Delhi");
map.put(89, "Sydney");
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals("Sydney", map.get(89));
- assertEquals("Washington DC", map.get(101));
- assertTrue(map.containsKey(46));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals("Sydney", map.get(89));
+ Assertions.assertEquals("Washington DC", map.get(101));
+ Assertions.assertTrue(map.containsKey(46));
}
@Test
@@ -56,7 +54,7 @@ void testRemoveNonExistentKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
map.remove("Nepal"); // Attempting to remove a non-existent key
- assertEquals(1, map.size()); // Size should remain the same
+ Assertions.assertEquals(1, map.size()); // Size should remain the same
}
@Test
@@ -65,8 +63,8 @@ void testRehashing() {
for (int i = 0; i < 20; i++) {
map.put("Key" + i, "Value" + i);
}
- assertEquals(20, map.size()); // Ensure all items were added
- assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash
+ Assertions.assertEquals(20, map.size()); // Ensure all items were added
+ Assertions.assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash
}
@Test
@@ -74,7 +72,7 @@ void testUpdateValueForExistingKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
map.put("USA", "New Washington DC"); // Updating value for existing key
- assertEquals("New Washington DC", map.get("USA"));
+ Assertions.assertEquals("New Washington DC", map.get("USA"));
}
@Test
@@ -83,14 +81,154 @@ void testToStringMethod() {
map.put("USA", "Washington DC");
map.put("Nepal", "Kathmandu");
String expected = "{USA : Washington DC, Nepal : Kathmandu}";
- assertEquals(expected, map.toString());
+ Assertions.assertEquals(expected, map.toString());
}
@Test
void testContainsKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
- assertTrue(map.containsKey("USA"));
- assertFalse(map.containsKey("Nepal"));
+ Assertions.assertTrue(map.containsKey("USA"));
+ Assertions.assertFalse(map.containsKey("Nepal"));
+ }
+
+ // ======= Added tests from the new version =======
+
+ @Test
+ void shouldThrowNullPointerExceptionForNullKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ String nullKey = null; // Use variable to avoid static analysis false positive
+ Assertions.assertThrows(NullPointerException.class, () -> map.put(nullKey, "value"));
+ }
+
+ @Test
+ void shouldStoreNullValueForKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("keyWithNullValue", null);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertNull(map.get("keyWithNullValue"));
+ // Note: containsKey returns false for null values due to implementation
+ Assertions.assertFalse(map.containsKey("keyWithNullValue"));
+ }
+
+ @Test
+ void shouldHandleCollisionWhenKeysHashToSameBucket() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ Integer key1 = 1;
+ Integer key2 = 17;
+ map.put(key1, 100);
+ map.put(key2, 200);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(key1));
+ Assertions.assertEquals(200, map.get(key2));
+ Assertions.assertTrue(map.containsKey(key1));
+ Assertions.assertTrue(map.containsKey(key2));
+ }
+
+ @Test
+ void shouldHandleEmptyStringAsKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("", "valueForEmptyKey");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("valueForEmptyKey", map.get(""));
+ Assertions.assertTrue(map.containsKey(""));
+ }
+
+ @Test
+ void shouldHandleEmptyStringAsValue() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("keyForEmptyValue", "");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("", map.get("keyForEmptyValue"));
+ Assertions.assertTrue(map.containsKey("keyForEmptyValue"));
+ }
+
+ @Test
+ void shouldHandleNegativeIntegerKeys() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(-1, 100);
+ map.put(-100, 200);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(-1));
+ Assertions.assertEquals(200, map.get(-100));
+ Assertions.assertTrue(map.containsKey(-1));
+ Assertions.assertTrue(map.containsKey(-100));
+ }
+
+ @Test
+ void shouldHandleZeroAsKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(0, 100);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals(100, map.get(0));
+ Assertions.assertTrue(map.containsKey(0));
+ }
+
+ @Test
+ void shouldHandleStringWithSpecialCharacters() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("key!@#$%^&*()", "value<>?/\\|");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("value<>?/\\|", map.get("key!@#$%^&*()"));
+ Assertions.assertTrue(map.containsKey("key!@#$%^&*()"));
+ }
+
+ @Test
+ void shouldHandleLongStrings() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ StringBuilder longKey = new StringBuilder();
+ StringBuilder longValue = new StringBuilder();
+ for (int i = 0; i < 1000; i++) {
+ longKey.append("a");
+ longValue.append("b");
+ }
+ String key = longKey.toString();
+ String value = longValue.toString();
+ map.put(key, value);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals(value, map.get(key));
+ Assertions.assertTrue(map.containsKey(key));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"a", "ab", "abc", "test", "longerString"})
+ void shouldHandleKeysOfDifferentLengths(String key) {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(key, "value");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("value", map.get(key));
+ Assertions.assertTrue(map.containsKey(key));
+ }
+
+ @Test
+ void shouldHandleUpdateOnExistingKeyInCollisionBucket() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ Integer key1 = 1;
+ Integer key2 = 17;
+ map.put(key1, 100);
+ map.put(key2, 200);
+ Assertions.assertEquals(2, map.size());
+ map.put(key2, 999);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(key1));
+ Assertions.assertEquals(999, map.get(key2));
+ Assertions.assertTrue(map.containsKey(key1));
+ Assertions.assertTrue(map.containsKey(key2));
+ }
+
+ @Test
+ void shouldHandleExactlyLoadFactorBoundary() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ // Fill exactly to load factor (12 items with capacity 16 and 0.75 load factor)
+ for (int i = 0; i < 12; i++) {
+ map.put(i, i * 10);
+ }
+ Assertions.assertEquals(12, map.size());
+ // Act - This should trigger rehash on 13th item
+ map.put(12, 120);
+ // Assert - Rehash should have happened
+ Assertions.assertEquals(13, map.size());
+ Assertions.assertEquals(120, map.get(12));
+ Assertions.assertTrue(map.containsKey(12));
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java
new file mode 100644
index 000000000000..e3abe15b6a46
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.datastructures.queues;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import org.junit.jupiter.api.Test;
+
+class ReverseQueueRecursionTest {
+ @Test
+ void testReverseMultipleElements() {
+ Queue queue = new LinkedList<>();
+ queue.add(1);
+ queue.add(2);
+ queue.add(3);
+ queue.add(4);
+ ReverseQueueRecursion.reverseQueue(queue);
+ assertEquals(4, queue.poll());
+ assertEquals(3, queue.poll());
+ assertEquals(2, queue.poll());
+ assertEquals(1, queue.poll());
+ assertTrue(queue.isEmpty());
+ }
+
+ @Test
+ void testReverseSingleElement() {
+ Queue queue = new LinkedList<>();
+ queue.add(42);
+ ReverseQueueRecursion.reverseQueue(queue);
+ assertEquals(42, queue.poll());
+ assertTrue(queue.isEmpty());
+ }
+
+ @Test
+ void testReverseEmptyQueue() {
+ Queue queue = new LinkedList<>();
+ ReverseQueueRecursion.reverseQueue(queue);
+ assertTrue(queue.isEmpty());
+ }
+
+ @Test
+ void testReverseStringQueue() {
+ Queue queue = new LinkedList<>();
+ queue.add("A");
+ queue.add("B");
+ queue.add("C");
+ ReverseQueueRecursion.reverseQueue(queue);
+ assertEquals("C", queue.poll());
+ assertEquals("B", queue.poll());
+ assertEquals("A", queue.poll());
+ assertTrue(queue.isEmpty());
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java
new file mode 100644
index 000000000000..db081da2550a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java
@@ -0,0 +1,71 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class SegmentTree2DTest {
+
+ @Test
+ void testInitialEmptyQueries() {
+ SegmentTree2D segmentTree = new SegmentTree2D(4, 4);
+
+ // Initial tree should return 0 for any query
+ assertEquals(0, segmentTree.query(0, 4, 0, 4));
+ assertEquals(0, segmentTree.query(1, 3, 1, 3));
+ }
+
+ @Test
+ void testUpdateAndPointQuery() {
+ SegmentTree2D segmentTree = new SegmentTree2D(5, 5);
+
+ segmentTree.update(2, 3, 10);
+ segmentTree.update(0, 0, 5);
+
+ // Querying single points [row, row+1) x [col, col+1)
+ assertEquals(10, segmentTree.query(2, 3, 3, 4));
+ assertEquals(5, segmentTree.query(0, 1, 0, 1));
+
+ // Empty point should be 0
+ assertEquals(0, segmentTree.query(1, 2, 1, 2));
+ }
+
+ @Test
+ void testSubmatrixQuery() {
+ SegmentTree2D segmentTree = new SegmentTree2D(4, 4);
+
+ // Matrix simulation:
+ // [1, 2, 0, 0]
+ // [3, 4, 0, 0]
+ // [0, 0, 0, 0]
+ // [0, 0, 0, 0]
+ segmentTree.update(0, 0, 1);
+ segmentTree.update(0, 1, 2);
+ segmentTree.update(1, 0, 3);
+ segmentTree.update(1, 1, 4);
+
+ // Top-left 2x2 sum: 1+2+3+4 = 10
+ assertEquals(10, segmentTree.query(0, 2, 0, 2));
+
+ // First row sum: 1+2 = 3
+ assertEquals(3, segmentTree.query(0, 1, 0, 4));
+
+ // Second column sum: 2+4 = 6
+ assertEquals(6, segmentTree.query(0, 4, 1, 2));
+ }
+
+ @Test
+ void testUpdateOverwriting() {
+ SegmentTree2D segmentTree = new SegmentTree2D(3, 3);
+
+ segmentTree.update(1, 1, 5);
+ assertEquals(5, segmentTree.query(1, 2, 1, 2));
+
+ // Overwrite the same point
+ segmentTree.update(1, 1, 20);
+ assertEquals(20, segmentTree.query(1, 2, 1, 2));
+
+ // Full matrix sum should just be this point
+ assertEquals(20, segmentTree.query(0, 3, 0, 3));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
index 38784228d68e..b25fd796b112 100644
--- a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
+++ b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
@@ -16,14 +16,6 @@ public void testBuildLocation() {
assertEquals(4.0, point.y);
}
- @Test
- public void testCreateLocation() {
- ClosestPair cp = new ClosestPair(5);
- ClosestPair.Location[] locations = cp.createLocation(5);
- assertNotNull(locations);
- assertEquals(5, locations.length);
- }
-
@Test
public void testXPartition() {
ClosestPair cp = new ClosestPair(5);
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java
new file mode 100644
index 000000000000..17ff3ec728dc
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.dynamicprogramming;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class OptimalBinarySearchTreeTest {
+
+ @ParameterizedTest
+ @MethodSource("validTestCases")
+ void testFindOptimalCost(int[] keys, int[] frequencies, long expectedCost) {
+ assertEquals(expectedCost, OptimalBinarySearchTree.findOptimalCost(keys, frequencies));
+ }
+
+ private static Stream validTestCases() {
+ return Stream.of(Arguments.of(new int[] {}, new int[] {}, 0L), Arguments.of(new int[] {15}, new int[] {9}, 9L), Arguments.of(new int[] {10, 12}, new int[] {34, 50}, 118L), Arguments.of(new int[] {20, 10, 30}, new int[] {50, 34, 8}, 134L),
+ Arguments.of(new int[] {12, 10, 20, 42, 25, 37}, new int[] {8, 34, 50, 3, 40, 30}, 324L), Arguments.of(new int[] {1, 2, 3}, new int[] {0, 0, 0}, 0L));
+ }
+
+ @ParameterizedTest
+ @MethodSource("crossCheckTestCases")
+ void testFindOptimalCostAgainstBruteForce(int[] keys, int[] frequencies) {
+ assertEquals(bruteForceOptimalCost(keys, frequencies), OptimalBinarySearchTree.findOptimalCost(keys, frequencies));
+ }
+
+ private static Stream crossCheckTestCases() {
+ return Stream.of(Arguments.of(new int[] {3, 1, 2}, new int[] {4, 2, 6}), Arguments.of(new int[] {5, 2, 8, 6}, new int[] {3, 7, 1, 4}), Arguments.of(new int[] {9, 4, 11, 2}, new int[] {1, 8, 2, 5}));
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidTestCases")
+ void testFindOptimalCostInvalidInput(int[] keys, int[] frequencies) {
+ assertThrows(IllegalArgumentException.class, () -> OptimalBinarySearchTree.findOptimalCost(keys, frequencies));
+ }
+
+ private static Stream invalidTestCases() {
+ return Stream.of(Arguments.of(null, new int[] {}), Arguments.of(new int[] {}, null), Arguments.of(new int[] {1, 2}, new int[] {3}), Arguments.of(new int[] {1, 1}, new int[] {2, 3}), Arguments.of(new int[] {1, 2}, new int[] {3, -1}));
+ }
+
+ private static long bruteForceOptimalCost(int[] keys, int[] frequencies) {
+ int[][] sortedNodes = new int[keys.length][2];
+ for (int index = 0; index < keys.length; index++) {
+ sortedNodes[index][0] = keys[index];
+ sortedNodes[index][1] = frequencies[index];
+ }
+ Arrays.sort(sortedNodes, java.util.Comparator.comparingInt(node -> node[0]));
+
+ int[] sortedFrequencies = new int[sortedNodes.length];
+ for (int index = 0; index < sortedNodes.length; index++) {
+ sortedFrequencies[index] = sortedNodes[index][1];
+ }
+
+ return bruteForceOptimalCost(sortedFrequencies, 0, sortedFrequencies.length - 1, 1);
+ }
+
+ private static long bruteForceOptimalCost(int[] frequencies, int start, int end, int depth) {
+ if (start > end) {
+ return 0L;
+ }
+
+ long minimumCost = Long.MAX_VALUE;
+ for (int root = start; root <= end; root++) {
+ long currentCost = (long) depth * frequencies[root] + bruteForceOptimalCost(frequencies, start, root - 1, depth + 1) + bruteForceOptimalCost(frequencies, root + 1, end, depth + 1);
+ minimumCost = Math.min(minimumCost, currentCost);
+ }
+ return minimumCost;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java
new file mode 100644
index 000000000000..9f60df51b65f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.geometry;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.awt.geom.Point2D;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
+class LineIntersectionTest {
+
+ @Test
+ void testCrossingSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(4, 4);
+ Point q1 = new Point(0, 4);
+ Point q2 = new Point(4, 0);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testParallelSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(3, 3);
+ Point q1 = new Point(0, 1);
+ Point q2 = new Point(3, 4);
+
+ assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testTouchingAtEndpoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(4, 0);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testCollinearOverlapHasNoUniquePoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(4, 4);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(6, 6);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testCollinearDisjointSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(3, 3);
+ Point q2 = new Point(5, 5);
+
+ assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testCollinearSegmentsTouchingAtEndpointHaveUniquePoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(4, 4);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testVerticalAndHorizontalCrossingSegments() {
+ Point p1 = new Point(2, 0);
+ Point p2 = new Point(2, 5);
+ Point q1 = new Point(0, 3);
+ Point q2 = new Point(4, 3);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(3.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/AccountMergeTest.java b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java
new file mode 100644
index 000000000000..291be677d894
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java
@@ -0,0 +1,61 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class AccountMergeTest {
+
+ @Test
+ void testMergeAccountsWithSharedEmails() {
+ List> accounts = List.of(List.of("abc", "abc@mail.com", "abx@mail.com"), List.of("abc", "abc@mail.com", "aby@mail.com"), List.of("Mary", "mary@mail.com"), List.of("John", "johnnybravo@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+
+ List> expected = List.of(List.of("John", "johnnybravo@mail.com"), List.of("Mary", "mary@mail.com"), List.of("abc", "abc@mail.com", "abx@mail.com", "aby@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testAccountsWithSameNameButNoSharedEmailStaySeparate() {
+ List> accounts = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+ List> expected = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testEmptyInput() {
+ assertEquals(List.of(), AccountMerge.mergeAccounts(List.of()));
+ }
+
+ @Test
+ void testNullInput() {
+ assertEquals(List.of(), AccountMerge.mergeAccounts(null));
+ }
+
+ @Test
+ void testTransitiveMergeAndDuplicateEmails() {
+ List> accounts = List.of(List.of("A", "a1@mail.com", "a2@mail.com"), List.of("A", "a2@mail.com", "a3@mail.com"), List.of("A", "a3@mail.com", "a4@mail.com", "a4@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+
+ List> expected = List.of(List.of("A", "a1@mail.com", "a2@mail.com", "a3@mail.com", "a4@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testAccountsWithNoEmailsArePreserved() {
+ List> accounts = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+ List> expected = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
+
+ assertEquals(expected, merged);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java
new file mode 100644
index 000000000000..8608bfb2dfc9
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java
@@ -0,0 +1,207 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link TarjanBridges}.
+ *
+ * Tests cover a wide range of graph configurations including simple graphs,
+ * cycles, trees, disconnected components, multigraph-like structures, and
+ * various edge cases to ensure correct bridge detection.
+ */
+class TarjanBridgesTest {
+
+ /**
+ * Helper to build a symmetric adjacency list for an undirected graph.
+ */
+ private static List> buildGraph(int vertexCount, int[][] edges) {
+ List> adj = new ArrayList<>();
+ for (int i = 0; i < vertexCount; i++) {
+ adj.add(new ArrayList<>());
+ }
+ for (int[] edge : edges) {
+ adj.get(edge[0]).add(edge[1]);
+ adj.get(edge[1]).add(edge[0]);
+ }
+ return adj;
+ }
+
+ /**
+ * Sorts bridges for deterministic comparison.
+ */
+ private static void sortBridges(List bridges) {
+ bridges.sort(Comparator.comparingInt((int[] a) -> a[0]).thenComparingInt(a -> a[1]));
+ }
+
+ @Test
+ void testSimpleGraphWithOneBridge() {
+ // Graph: 0-1-2-3 where 1-2 is the only bridge
+ // 0---1---2---3
+ // | |
+ // +-------+ (via 0-2 would make cycle, but not here)
+ // Actually: 0-1 in a cycle with 0-1, and 2-3 in a cycle with 2-3
+ // Let's use: 0--1--2 (linear chain). All edges are bridges.
+ List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}});
+ List bridges = TarjanBridges.findBridges(3, adj);
+ sortBridges(bridges);
+ assertEquals(2, bridges.size());
+ assertEquals(0, bridges.get(0)[0]);
+ assertEquals(1, bridges.get(0)[1]);
+ assertEquals(1, bridges.get(1)[0]);
+ assertEquals(2, bridges.get(1)[1]);
+ }
+
+ @Test
+ void testCycleGraphHasNoBridges() {
+ // Graph: 0-1-2-0 (triangle). No bridges.
+ List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}, {2, 0}});
+ List bridges = TarjanBridges.findBridges(3, adj);
+ assertTrue(bridges.isEmpty());
+ }
+
+ @Test
+ void testTreeGraphAllEdgesAreBridges() {
+ // Tree: 0
+ // / \
+ // 1 2
+ // / \
+ // 3 4
+ List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {1, 3}, {1, 4}});
+ List bridges = TarjanBridges.findBridges(5, adj);
+ assertEquals(4, bridges.size());
+ }
+
+ @Test
+ void testGraphWithMixedBridgesAndCycles() {
+ // Graph:
+ // 0---1
+ // | |
+ // 3---2---4---5
+ // |
+ // 6
+ // Cycle: 0-1-2-3-0 (no bridges within)
+ // Bridges: 2-4, 4-5, 5-6
+ List> adj = buildGraph(7, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {2, 4}, {4, 5}, {5, 6}});
+ List bridges = TarjanBridges.findBridges(7, adj);
+ sortBridges(bridges);
+ assertEquals(3, bridges.size());
+ assertEquals(2, bridges.get(0)[0]);
+ assertEquals(4, bridges.get(0)[1]);
+ assertEquals(4, bridges.get(1)[0]);
+ assertEquals(5, bridges.get(1)[1]);
+ assertEquals(5, bridges.get(2)[0]);
+ assertEquals(6, bridges.get(2)[1]);
+ }
+
+ @Test
+ void testDisconnectedGraphWithBridges() {
+ // Component 1: 0-1 (bridge)
+ // Component 2: 2-3-4-2 (cycle, no bridges)
+ List> adj = buildGraph(5, new int[][] {{0, 1}, {2, 3}, {3, 4}, {4, 2}});
+ List bridges = TarjanBridges.findBridges(5, adj);
+ assertEquals(1, bridges.size());
+ assertEquals(0, bridges.get(0)[0]);
+ assertEquals(1, bridges.get(0)[1]);
+ }
+
+ @Test
+ void testSingleVertex() {
+ List> adj = buildGraph(1, new int[][] {});
+ List bridges = TarjanBridges.findBridges(1, adj);
+ assertTrue(bridges.isEmpty());
+ }
+
+ @Test
+ void testTwoVerticesWithOneEdge() {
+ List> adj = buildGraph(2, new int[][] {{0, 1}});
+ List bridges = TarjanBridges.findBridges(2, adj);
+ assertEquals(1, bridges.size());
+ assertEquals(0, bridges.get(0)[0]);
+ assertEquals(1, bridges.get(0)[1]);
+ }
+
+ @Test
+ void testEmptyGraph() {
+ List> adj = buildGraph(0, new int[][] {});
+ List bridges = TarjanBridges.findBridges(0, adj);
+ assertTrue(bridges.isEmpty());
+ }
+
+ @Test
+ void testIsolatedVertices() {
+ // 5 vertices, no edges β all isolated
+ List> adj = buildGraph(5, new int[][] {});
+ List bridges = TarjanBridges.findBridges(5, adj);
+ assertTrue(bridges.isEmpty());
+ }
+
+ @Test
+ void testLargeCycleNoBridges() {
+ // Cycle: 0-1-2-3-4-5-6-7-0
+ int n = 8;
+ int[][] edges = new int[n][2];
+ for (int i = 0; i < n; i++) {
+ edges[i] = new int[] {i, (i + 1) % n};
+ }
+ List> adj = buildGraph(n, edges);
+ List bridges = TarjanBridges.findBridges(n, adj);
+ assertTrue(bridges.isEmpty());
+ }
+
+ @Test
+ void testComplexGraphWithMultipleCyclesAndBridges() {
+ // Two cycles connected by a single bridge edge:
+ // Cycle A: 0-1-2-0
+ // Cycle B: 3-4-5-3
+ // Bridge: 2-3
+ List> adj = buildGraph(6, new int[][] {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {2, 3}});
+ List bridges = TarjanBridges.findBridges(6, adj);
+ assertEquals(1, bridges.size());
+ assertEquals(2, bridges.get(0)[0]);
+ assertEquals(3, bridges.get(0)[1]);
+ }
+
+ @Test
+ void testNegativeVertexCountThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(-1, new ArrayList<>()));
+ }
+
+ @Test
+ void testNullAdjacencyListThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(3, null));
+ }
+
+ @Test
+ void testMismatchedAdjacencyListSizeThrowsException() {
+ List> adj = buildGraph(2, new int[][] {{0, 1}});
+ assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(5, adj));
+ }
+
+ @Test
+ void testStarGraphAllEdgesAreBridges() {
+ // Star graph: center vertex 0 connected to 1, 2, 3, 4
+ List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {0, 3}, {0, 4}});
+ List bridges = TarjanBridges.findBridges(5, adj);
+ assertEquals(4, bridges.size());
+ }
+
+ @Test
+ void testBridgeBetweenTwoCycles() {
+ // Two squares connected by one bridge:
+ // Square 1: 0-1-2-3-0
+ // Square 2: 4-5-6-7-4
+ // Bridge: 3-4
+ List> adj = buildGraph(8, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {3, 4}});
+ List bridges = TarjanBridges.findBridges(8, adj);
+ assertEquals(1, bridges.size());
+ assertEquals(3, bridges.get(0)[0]);
+ assertEquals(4, bridges.get(0)[1]);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/AreaTest.java b/src/test/java/com/thealgorithms/maths/AreaTest.java
index b28afb85fbc3..1c2fe53ff3f3 100644
--- a/src/test/java/com/thealgorithms/maths/AreaTest.java
+++ b/src/test/java/com/thealgorithms/maths/AreaTest.java
@@ -16,6 +16,11 @@ void testSurfaceAreaCube() {
assertEquals(6.0, Area.surfaceAreaCube(1));
}
+ @Test
+ void testSurfaceAreaCuboid() {
+ assertEquals(214.0, Area.surfaceAreaCuboid(5, 6, 7));
+ }
+
@Test
void testSurfaceAreaSphere() {
assertEquals(12.566370614359172, Area.surfaceAreaSphere(1));
@@ -70,6 +75,12 @@ void surfaceAreaCone() {
void testAllIllegalInput() {
assertAll(()
-> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCube(0)),
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(0, 1, 2)),
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 0, 2)),
+ ()
+ -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 2, 0)),
()
-> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaSphere(0)),
()
diff --git a/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java
new file mode 100644
index 000000000000..02e964b53771
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class ComplexNumberMultiplyTest {
+
+ @Test
+ void testExample() {
+ assertEquals("0+2i", ComplexNumberMultiply.multiply("1+1i", "1+1i"));
+ }
+
+ @Test
+ void testNegative() {
+ assertEquals("0+-2i", ComplexNumberMultiply.multiply("1+-1i", "1+-1i"));
+ }
+
+ @Test
+ void testZero() {
+ assertEquals("0+0i", ComplexNumberMultiply.multiply("0+0i", "5+3i"));
+ }
+
+ @Test
+ void testInvalidFormat() {
+ assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply("1+1", "1+1i"));
+ }
+
+ @Test
+ void testNullInput() {
+ assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply(null, "1+1i"));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/CorrelationTest.java b/src/test/java/com/thealgorithms/maths/CorrelationTest.java
new file mode 100644
index 000000000000..96867d56ad5e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/CorrelationTest.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test class for Correlation class
+ */
+public class CorrelationTest {
+
+ public static final double DELTA = 1e-9;
+
+ // Regular correlation test
+ public void testCorrelationFirst() {
+ double[] x = {1, 2, 3, 4};
+ double[] y = {7, 1, 4, 9};
+ int n = 4;
+ assertEquals(0.3319700011, Correlation.correlation(x, y, n), DELTA);
+ }
+
+ // Regular correlation test (zero correlation)
+ public void testCorrelationSecond() {
+ double[] x = {1, 2, 3, 4};
+ double[] y = {5, 0, 9, 2};
+ int n = 4;
+ assertEquals(0, Correlation.correlation(x, y, n), DELTA);
+ }
+
+ // Correlation with a constant variable is taken to be zero
+ public void testCorrelationConstant() {
+ double[] x = {1, 2, 3};
+ double[] y = {4, 4, 4};
+ int n = 3;
+ assertEquals(0, Correlation.correlation(x, y, n), DELTA);
+ }
+
+ // Linear dependence gives correlation 1
+ public void testCorrelationLinearDependence() {
+ double[] x = {1, 2, 3, 4};
+ double[] y = {6, 8, 10, 12};
+ int n = 4;
+ assertEquals(1, Correlation.correlation(x, y, n), DELTA);
+ }
+
+ // Inverse linear dependence gives correlation -1
+ public void testCorrelationInverseLinearDependence() {
+ double[] x = {1, 2, 3, 4, 5};
+ double[] y = {18, 15, 12, 9, 6};
+ int n = 5;
+ assertEquals(-1, Correlation.correlation(x, y, n), DELTA);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/FactorialTest.java b/src/test/java/com/thealgorithms/maths/FactorialTest.java
index 3ff7097b8113..a393136696e1 100644
--- a/src/test/java/com/thealgorithms/maths/FactorialTest.java
+++ b/src/test/java/com/thealgorithms/maths/FactorialTest.java
@@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.math.BigInteger;
import org.junit.jupiter.api.Test;
public class FactorialTest {
@@ -16,9 +17,9 @@ public void testWhenInvalidInoutProvidedShouldThrowException() {
@Test
public void testCorrectFactorialCalculation() {
- assertEquals(1, Factorial.factorial(0));
- assertEquals(1, Factorial.factorial(1));
- assertEquals(120, Factorial.factorial(5));
- assertEquals(3628800, Factorial.factorial(10));
+ assertEquals(BigInteger.ONE, Factorial.factorial(0));
+ assertEquals(BigInteger.ONE, Factorial.factorial(1));
+ assertEquals(BigInteger.valueOf(120), Factorial.factorial(5));
+ assertEquals(BigInteger.valueOf(3628800), Factorial.factorial(10));
}
}
diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java
index deee0a931910..853fdbea3963 100644
--- a/src/test/java/com/thealgorithms/maths/MeansTest.java
+++ b/src/test/java/com/thealgorithms/maths/MeansTest.java
@@ -172,6 +172,53 @@ void testHarmonicMeanWithLinkedList() {
assertEquals(expected, Means.harmonic(numbers), EPSILON);
}
+ // ========== Quadratic Mean Tests ==========
+
+ @Test
+ void testQuadraticMeanThrowsExceptionForEmptyList() {
+ List numbers = new ArrayList<>();
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.quadratic(numbers));
+ assertTrue(exception.getMessage().contains("Empty list"));
+ }
+
+ @Test
+ void testQuadraticMeanSingleNumber() {
+ LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5));
+ assertEquals(2.5, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanTwoNumbers() {
+ List numbers = Arrays.asList(1.0, 7.0);
+ assertEquals(5.0, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanMultipleNumbers() {
+ Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 3.0, 7.5, 10.0));
+ double expected = Math.sqrt(34.5);
+ assertEquals(expected, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanThreeNumbers() {
+ List numbers = Arrays.asList(3.0, 6.0, 9.0);
+ double expected = Math.sqrt(42.0);
+ assertEquals(expected, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanIdenticalNumbers() {
+ List numbers = Arrays.asList(5.0, 5.0, 5.0);
+ assertEquals(5.0, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanWithLinkedList() {
+ LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 5.0, 11.0));
+ assertEquals(7.0, Means.quadratic(numbers), EPSILON);
+ }
+
// ========== Additional Edge Case Tests ==========
@Test
@@ -198,21 +245,25 @@ void testAllMeansConsistencyForIdenticalValues() {
double arithmetic = Means.arithmetic(numbers);
double geometric = Means.geometric(numbers);
double harmonic = Means.harmonic(numbers);
+ double quadratic = Means.quadratic(numbers);
assertEquals(7.5, arithmetic, EPSILON);
assertEquals(7.5, geometric, EPSILON);
assertEquals(7.5, harmonic, EPSILON);
+ assertEquals(7.5, quadratic, EPSILON);
}
@Test
void testMeansRelationship() {
- // For positive numbers, harmonic mean β€ geometric mean β€ arithmetic mean
+ // For positive numbers, harmonic mean β€ geometric mean β€ arithmetic mean β€ quadratic mean
List numbers = Arrays.asList(2.0, 4.0, 8.0);
double arithmetic = Means.arithmetic(numbers);
double geometric = Means.geometric(numbers);
double harmonic = Means.harmonic(numbers);
+ double quadratic = Means.quadratic(numbers);
assertTrue(harmonic <= geometric, "Harmonic mean should be β€ geometric mean");
assertTrue(geometric <= arithmetic, "Geometric mean should be β€ arithmetic mean");
+ assertTrue(arithmetic <= quadratic, "Arithmetic mean should be β€ quadratic mean");
}
}
diff --git a/src/test/java/com/thealgorithms/maths/VolumeTest.java b/src/test/java/com/thealgorithms/maths/VolumeTest.java
index af882eef7563..c0b02d6ba28e 100644
--- a/src/test/java/com/thealgorithms/maths/VolumeTest.java
+++ b/src/test/java/com/thealgorithms/maths/VolumeTest.java
@@ -35,5 +35,14 @@ public void volume() {
/* test frustum */
assertEquals(359.188760060433, Volume.volumeFrustumOfCone(3, 5, 7));
+
+ /* test pyramid frustum */
+ assertEquals(140.0, Volume.volumeFrustumOfPyramid(6, 24, 10));
+
+ /* test torus */
+ assertEquals(39.47841760435743, Volume.volumeTorus(2, 1));
+
+ /* test ellipsoid */
+ assertEquals(25.1327412287183459, Volume.volumeEllipsoid(3, 2, 1));
}
}
diff --git a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java b/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java
deleted file mode 100644
index 3b657e441b1c..000000000000
--- a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.thealgorithms.others;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import com.thealgorithms.dynamicprogramming.NewManShanksPrime;
-import org.junit.jupiter.api.Test;
-
-public class NewManShanksPrimeTest {
-
- @Test
- void testOne() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(1, 1));
- }
-
- @Test
- void testTwo() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(2, 3));
- }
-
- @Test
- void testThree() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(3, 7));
- }
-
- @Test
- void testFour() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(4, 17));
- }
-
- @Test
- void testFive() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(5, 41));
- }
-
- @Test
- void testSix() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(6, 99));
- }
-
- @Test
- void testSeven() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(7, 239));
- }
-
- @Test
- void testEight() {
- assertTrue(NewManShanksPrime.nthManShanksPrime(8, 577));
- }
-}
diff --git a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java b/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java
deleted file mode 100644
index 669f928cd247..000000000000
--- a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.thealgorithms.others.cn;
-
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-public class HammingDistanceTest {
- @Test
- public void checkForDifferentBits() {
- int answer = HammingDistance.compute("000", "011");
- Assertions.assertThat(answer).isEqualTo(2);
- }
-
- /*
-
- 1 0 1 0 1
- 1 1 1 1 0
- ----------
- 0 1 0 1 1
-
-
- */
- @Test
- public void checkForDifferentBitsLength() {
- int answer = HammingDistance.compute("10101", "11110");
- Assertions.assertThat(answer).isEqualTo(3);
- }
-
- @Test
- public void checkForSameBits() {
- String someBits = "111";
- int answer = HammingDistance.compute(someBits, someBits);
- Assertions.assertThat(answer).isEqualTo(0);
- }
-
- @Test
- public void checkForLongDataBits() {
- int answer = HammingDistance.compute("10010101101010000100110100", "00110100001011001100110101");
- Assertions.assertThat(answer).isEqualTo(7);
- }
-
- @Test
- public void mismatchDataBits() {
- Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("100010", "00011"); });
-
- Assertions.assertThat(ex.getMessage()).contains("must have the same length");
- }
-
- @Test
- public void mismatchDataBits2() {
- Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1", "11"); });
-
- Assertions.assertThat(ex.getMessage()).contains("must have the same length");
- }
-
- @Test
- public void checkForLongDataBitsSame() {
- String someBits = "10010101101010000100110100";
- int answer = HammingDistance.compute(someBits, someBits);
- Assertions.assertThat(answer).isEqualTo(0);
- }
-
- @Test
- public void checkForEmptyInput() {
- String someBits = "";
- int answer = HammingDistance.compute(someBits, someBits);
- Assertions.assertThat(answer).isEqualTo(0);
- }
-
- @Test
- public void checkForInputOfLength1() {
- String someBits = "0";
- int answer = HammingDistance.compute(someBits, someBits);
- Assertions.assertThat(answer).isEqualTo(0);
- }
-
- @Test
- public void computeThrowsExceptionWhenInputsAreNotBitStrs() {
- Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1A", "11"); });
-
- Assertions.assertThat(ex.getMessage()).contains("must be a binary string");
- }
-}
diff --git a/src/test/java/com/thealgorithms/physics/RelativityTest.java b/src/test/java/com/thealgorithms/physics/RelativityTest.java
new file mode 100644
index 000000000000..44c17bdbd40f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/RelativityTest.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.physics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the Relativity utility class.
+ */
+final class RelativityTest {
+
+ // A small tolerance (delta) for comparing floating-point numbers
+ private static final double DELTA = 1e-6;
+ private static final double C = Relativity.SPEED_OF_LIGHT;
+
+ @Test
+ @DisplayName("Test the gamma parameter")
+ void testGamma() {
+ double myGamma = Relativity.gamma(0.6 * C);
+ assertEquals(1.25, myGamma, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test the length contraction")
+ void testLengthContraction() {
+ double myLength = Relativity.lengthContraction(5.0, 0.8 * C);
+ assertEquals(3.0, myLength, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test the time dilation")
+ void testTimeDilation() {
+ double myTime = Relativity.timeDilation(4.0, 0.6 * C);
+ assertEquals(5.0, myTime, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test the velocity addition in the same direction")
+ void testVelocityAdditionSameDirection() {
+ double myVelocity = Relativity.velocityAddition(0.8 * C, 0.75 * C);
+ assertEquals(0.125 * C, myVelocity, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test the velocity addition in different directions")
+ void testVelocityAdditionDifferentDirections() {
+ double myVelocity = Relativity.velocityAddition(0.8 * C, -0.75 * C);
+ assertEquals(0.96875 * C, myVelocity, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test the velocity addition with the speed of light")
+ void testVelocityAdditionWithSpeedOfLight() {
+ double myVelocity = Relativity.velocityAddition(C, 0.7 * C);
+ assertEquals(C, myVelocity, DELTA);
+ }
+
+ @Test
+ @DisplayName("Test invalid inputs throw exception")
+ void testInvalidOrbitalVelocityInputs() {
+ assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(1.2 * C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(-C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(-1.0, 0.6 * C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(1.0, 1.5 * C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(-5.0, -0.8 * C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(5.0, C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(0.3 * C, -C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(1.4 * C, 0.2 * C));
+ assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(-0.4 * C, 1.2 * C));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java
new file mode 100644
index 000000000000..12072318ac74
--- /dev/null
+++ b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.prefixsum;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link RangeSumQuery}.
+ */
+class RangeSumQueryTest {
+
+ @Test
+ void testBasicExample() {
+ int[] nums = {1, 2, 3, 4, 5};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertEquals(6, RangeSumQuery.sumRange(prefixSum, 0, 2)); // 1+2+3
+ assertEquals(9, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2+3+4
+ assertEquals(15, RangeSumQuery.sumRange(prefixSum, 0, 4)); // 1+2+3+4+5
+ assertEquals(12, RangeSumQuery.sumRange(prefixSum, 2, 4)); // 3+4+5
+ }
+
+ @Test
+ void testSingleElement() {
+ int[] nums = {7};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertEquals(7, RangeSumQuery.sumRange(prefixSum, 0, 0));
+ }
+
+ @Test
+ void testAllZeros() {
+ int[] nums = {0, 0, 0, 0};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertEquals(0, RangeSumQuery.sumRange(prefixSum, 0, 3));
+ assertEquals(0, RangeSumQuery.sumRange(prefixSum, 1, 2));
+ }
+
+ @Test
+ void testNegativeNumbers() {
+ int[] nums = {-1, 2, -3, 4};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertEquals(-2, RangeSumQuery.sumRange(prefixSum, 0, 2)); // -1+2-3
+ assertEquals(3, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2-3+4
+ }
+
+ @Test
+ void testEmptyArrayThrowsException() {
+ int[] nums = {};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 0, 0));
+ }
+
+ @Test
+ void testNullArrayThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.buildPrefixSum(null));
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(null, 0, 0));
+ }
+
+ @Test
+ void testInvalidIndicesThrowsException() {
+ int[] nums = {1, 2, 3};
+ int[] prefixSum = RangeSumQuery.buildPrefixSum(nums);
+
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, -1, 2));
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 1, 5));
+ assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 2, 1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java
new file mode 100644
index 000000000000..68f85b713046
--- /dev/null
+++ b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.prefixsum;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link SubarraySumEqualsK}.
+ */
+class SubarraySumEqualsKTest {
+
+ @Test
+ void testBasicExample() {
+ int[] nums = {1, 1, 1};
+ int k = 2;
+ assertEquals(2, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testWithNegativeNumbers() {
+ int[] nums = {1, -1, 0};
+ int k = 0;
+ assertEquals(3, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testSingleElementEqualToK() {
+ int[] nums = {5};
+ int k = 5;
+ assertEquals(1, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testSingleElementNotEqualToK() {
+ int[] nums = {5};
+ int k = 3;
+ assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testAllZeros() {
+ int[] nums = {0, 0, 0};
+ int k = 0;
+ assertEquals(6, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testEmptyArray() {
+ int[] nums = {};
+ int k = 0;
+ assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k));
+ }
+
+ @Test
+ void testNullArrayThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> SubarraySumEqualsK.countSubarrays(null, 0));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java
index 42669eb03bb4..f0a2686d3e4b 100644
--- a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java
+++ b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java
@@ -1,14 +1,31 @@
package com.thealgorithms.puzzlesandgames;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
public class TowerOfHanoiTest {
+ @ParameterizedTest
+ @MethodSource("diskCountAndMoveCount")
+ void testMoveCountMatchesFormula(int disks, int expectedMoves) {
+ List result = new ArrayList<>();
+ TowerOfHanoi.shift(disks, "A", "B", "C", result);
+ assertEquals(expectedMoves, result.size());
+ }
+
+ private static Stream diskCountAndMoveCount() {
+ return Stream.of(Arguments.of(1, 1), Arguments.of(2, 3), Arguments.of(3, 7), Arguments.of(4, 15), Arguments.of(5, 31), Arguments.of(10, 1023));
+ }
+
@Test
public void testHanoiWithOneDisc() {
List result = new ArrayList<>();
@@ -39,6 +56,15 @@ public void testHanoiWithThreeDiscs() {
assertEquals(expected, result);
}
+ @Test
+ public void testHanoiWithDifferentPoles() {
+ List result = new ArrayList<>();
+ TowerOfHanoi.shift(2, "X", "Y", "Z", result);
+
+ List expected = List.of("Move 1 from X to Y", "Move 2 from X to Z", "Move 1 from Y to Z");
+ assertEquals(expected, result);
+ }
+
@Test
public void testHanoiWithZeroDiscs() {
List result = new ArrayList<>();
@@ -47,4 +73,10 @@ public void testHanoiWithZeroDiscs() {
// There should be no moves if there are 0 discs
assertTrue(result.isEmpty());
}
+
+ @Test
+ public void testHanoiWithNegativeDiscsThrows() {
+ List result = new ArrayList<>();
+ assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.shift(-1, "Pole1", "Pole2", "Pole3", result));
+ }
}
diff --git a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java
index bd4620a7fa7d..00bed165734e 100644
--- a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java
@@ -105,4 +105,90 @@ void testBinarySearchLargeArray() {
int expectedIndex = 9999; // Index of the last element
assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the last element should be 9999.");
}
+
+ /**
+ * Test for binary search with null array.
+ */
+ @Test
+ void testBinarySearchNullArray() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = null;
+ int key = 5; // Key to search
+ int expectedIndex = -1; // Key not found
+ assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in a null array.");
+ }
+
+ /**
+ * Test for binary search with duplicate elements.
+ */
+ @Test
+ void testBinarySearchWithDuplicates() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = {1, 2, 2, 2, 3};
+ int key = 2; // Element present multiple times
+
+ int result = binarySearch.find(array, key);
+ assertEquals(2, array[result], "The returned index should contain the searched element.");
+ }
+
+ /**
+ * Test for binary search where all elements are the same.
+ */
+ @Test
+ void testBinarySearchAllElementsSame() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = {5, 5, 5, 5, 5};
+ int key = 5; // All elements match
+
+ int result = binarySearch.find(array, key);
+ assertEquals(5, array[result], "The returned index should contain the searched element.");
+ }
+
+ /**
+ * Test for binary search with negative numbers.
+ */
+ @Test
+ void testBinarySearchNegativeNumbers() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = {-10, -5, 0, 5, 10};
+ int key = -5; // Element present
+ int expectedIndex = 1; // Index of the element
+ assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 1.");
+ }
+
+ /**
+ * Test for binary search when key is smaller than all elements.
+ */
+ @Test
+ void testBinarySearchKeySmallerThanAll() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = {10, 20, 30};
+ int key = 5; // Smaller than all elements
+ int expectedIndex = -1; // Key not found
+ assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array.");
+ }
+
+ /**
+ * Test for binary search when key is larger than all elements.
+ */
+ @Test
+ void testBinarySearchKeyLargerThanAll() {
+ BinarySearch binarySearch = new BinarySearch();
+ Integer[] array = {10, 20, 30};
+ int key = 40; // Larger than all elements
+ int expectedIndex = -1; // Key not found
+ assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array.");
+ }
+
+ /**
+ * Test for binary search with String array.
+ */
+ @Test
+ void testBinarySearchStrings() {
+ BinarySearch binarySearch = new BinarySearch();
+ String[] array = {"apple", "banana", "cherry", "date"};
+ String key = "cherry"; // Element present
+ int expectedIndex = 2; // Index of the element
+ assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 2.");
+ }
}
diff --git a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
index b2e121ac1ba0..f291610b298b 100644
--- a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
@@ -87,6 +87,26 @@ void testBinarySearchEmptyArray() {
assertEquals(-1, binarySearch.find(array, key), "The element should not be found in an empty array.");
}
+ /**
+ * Test for binary search with a null array.
+ */
+ @Test
+ void testBinarySearchNullArray() {
+ IterativeBinarySearch binarySearch = new IterativeBinarySearch();
+ Integer key = 1;
+ assertEquals(-1, binarySearch.find(null, key), "The element should not be found in a null array.");
+ }
+
+ /**
+ * Test for binary search with a null key.
+ */
+ @Test
+ void testBinarySearchNullKey() {
+ IterativeBinarySearch binarySearch = new IterativeBinarySearch();
+ Integer[] array = {1, 2, 4, 8, 16};
+ assertEquals(-1, binarySearch.find(array, null), "A null search key should return -1.");
+ }
+
/**
* Test for binary search on a large array.
*/
diff --git a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java
index 534c2a4487b2..c0d82489209f 100644
--- a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java
+++ b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java
@@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Random;
import org.junit.jupiter.api.Test;
class LinearSearchThreadTest {
@@ -62,10 +63,11 @@ void testSearcherEmptySegment() throws InterruptedException {
void testSearcherRandomNumbers() throws InterruptedException {
int size = 200;
int[] array = new int[size];
+ Random random = new Random();
for (int i = 0; i < size; i++) {
- array[i] = (int) (Math.random() * 100);
+ array[i] = random.nextInt(100);
}
- int target = array[(int) (Math.random() * size)]; // Randomly select a target that is present
+ final int target = array[random.nextInt(size)]; // Randomly select a target that is present
Searcher searcher = new Searcher(array, 0, size, target);
searcher.start();
searcher.join();
diff --git a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java
deleted file mode 100644
index 6eab20f45467..000000000000
--- a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.thealgorithms.searches;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * @author D Sunil (https://github.com/sunilnitdgp)
- * @see PerfectBinarySearch
- */
-public class PerfectBinarySearchTest {
-
- @Test
- public void testIntegerBinarySearch() {
- Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- PerfectBinarySearch binarySearch = new PerfectBinarySearch<>();
-
- // Test cases for elements present in the array
- assertEquals(0, binarySearch.find(array, 1)); // First element
- assertEquals(4, binarySearch.find(array, 5)); // Middle element
- assertEquals(9, binarySearch.find(array, 10)); // Last element
- assertEquals(6, binarySearch.find(array, 7)); // Element in the middle
-
- // Test cases for elements not in the array
- assertEquals(-1, binarySearch.find(array, 0)); // Element before the array
- assertEquals(-1, binarySearch.find(array, 11)); // Element after the array
- assertEquals(-1, binarySearch.find(array, 100)); // Element not in the array
- }
-
- @Test
- public void testStringBinarySearch() {
- String[] array = {"apple", "banana", "cherry", "date", "fig"};
- PerfectBinarySearch binarySearch = new PerfectBinarySearch<>();
-
- // Test cases for elements not in the array
- assertEquals(-1, binarySearch.find(array, "apricot")); // Element not in the array
- assertEquals(-1, binarySearch.find(array, "bananaa")); // Element not in the array
-
- // Test cases for elements present in the array
- assertEquals(0, binarySearch.find(array, "apple")); // First element
- assertEquals(2, binarySearch.find(array, "cherry")); // Middle element
- assertEquals(4, binarySearch.find(array, "fig")); // Last element
- }
-}
diff --git a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java
deleted file mode 100644
index e2917733d1d9..000000000000
--- a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.thealgorithms.searches;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-public class SortOrderAgnosticBinarySearchTest {
-
- @Test
- public void testAscending() {
- int[] arr = {1, 2, 3, 4, 5}; // for ascending order.
- int target = 2;
- int ans = SortOrderAgnosticBinarySearch.find(arr, target);
- int excepted = 1;
- assertEquals(excepted, ans);
- }
-
- @Test
- public void testDescending() {
- int[] arr = {5, 4, 3, 2, 1}; // for descending order.
- int target = 2;
- int ans = SortOrderAgnosticBinarySearch.find(arr, target);
- int excepted = 3;
- assertEquals(excepted, ans);
- }
-}
diff --git a/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java
new file mode 100644
index 000000000000..2e4ea74691da
--- /dev/null
+++ b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.stacks;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class StockSpanProblemTest {
+
+ @ParameterizedTest
+ @MethodSource("validTestCases")
+ void testCalculateSpan(int[] prices, int[] expectedSpans) {
+ assertArrayEquals(expectedSpans, StockSpanProblem.calculateSpan(prices));
+ }
+
+ private static Stream validTestCases() {
+ return Stream.of(Arguments.of(new int[] {10, 4, 5, 90, 120, 80}, new int[] {1, 1, 2, 4, 5, 1}), Arguments.of(new int[] {100, 50, 60, 70, 80, 90}, new int[] {1, 1, 2, 3, 4, 5}), Arguments.of(new int[] {5, 4, 3, 2, 1}, new int[] {1, 1, 1, 1, 1}),
+ Arguments.of(new int[] {1, 2, 3, 4, 5}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {10, 20, 30, 40, 50}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {100, 80, 60, 70, 60, 75, 85}, new int[] {1, 1, 1, 2, 1, 4, 6}),
+ Arguments.of(new int[] {7, 7, 7, 7}, new int[] {1, 2, 3, 4}), Arguments.of(new int[] {}, new int[] {}), Arguments.of(new int[] {42}, new int[] {1}));
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidTestCases")
+ void testCalculateSpanInvalidInput(int[] prices) {
+ assertThrows(IllegalArgumentException.class, () -> StockSpanProblem.calculateSpan(prices));
+ }
+
+ private static Stream invalidTestCases() {
+ return Stream.of(Arguments.of((int[]) null));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java
index 7b41e11ef22f..0c7d7e4701cf 100644
--- a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java
+++ b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java
@@ -1,15 +1,45 @@
package com.thealgorithms.strings;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
-public class AlphabeticalTest {
+@DisplayName("Alphabetical.isAlphabetical()")
+class AlphabeticalTest {
- @ParameterizedTest(name = "\"{0}\" β Expected: {1}")
- @CsvSource({"'abcdefghijklmno', true", "'abcdxxxyzzzz', true", "'123a', false", "'abcABC', false", "'abcdefghikjlmno', false", "'aBC', true", "'abc', true", "'xyzabc', false", "'abcxyz', true", "'', false", "'1', false"})
- void testIsAlphabetical(String input, boolean expected) {
- assertEquals(expected, Alphabetical.isAlphabetical(input));
+ static Stream testCases() {
+ // Workaround for SpotBugs false positive (NAB_NEEDLESS_BOOLEAN_CONSTANT_CONVERSION)
+ // due to JUnit Arguments.of(Object...) auto-boxing
+ return Stream.of(arguments("", Boolean.FALSE, "Should return false for empty string"), arguments(" ", Boolean.FALSE, "Should return false for blank string"), arguments("a1b2", Boolean.FALSE, "Should return false when string contains numbers"),
+ arguments("abc!DEF", Boolean.FALSE, "Should return false when string contains symbols"), arguments("#abc", Boolean.FALSE, "Should return false when first character is not a letter"), arguments("abc", Boolean.TRUE, "Should return true for non-decreasing order"),
+ arguments("aBcD", Boolean.TRUE, "Should return true for mixed case increasing sequence"), arguments("a", Boolean.TRUE, "Should return true for single letter"), arguments("'", Boolean.FALSE, "Should return false for single symbol"),
+ arguments("aabbcc", Boolean.TRUE, "Should return true for repeated letters"), arguments("cba", Boolean.FALSE, "Should return false when order decreases"), arguments("abzba", Boolean.FALSE, "Should return false when middle breaks order"));
+ }
+
+ private void assertAlphabetical(String input, boolean expected, String message) {
+ // Arrange & Act
+ boolean result = Alphabetical.isAlphabetical(input);
+
+ // Assert
+ assertEquals(expected, result, message);
+ }
+
+ @Test
+ @DisplayName("Should return false for null input")
+ void nullInputTest() {
+ assertAlphabetical(null, false, "Should return false for null input");
+ }
+
+ @ParameterizedTest(name = "{2}")
+ @MethodSource("testCases")
+ @DisplayName("Alphabetical cases")
+ void isAlphabeticalTest(String input, boolean expected, String message) {
+ assertAlphabetical(input, expected, message);
}
}
diff --git a/src/test/java/com/thealgorithms/strings/KMPTest.java b/src/test/java/com/thealgorithms/strings/KMPTest.java
new file mode 100644
index 000000000000..9fa5f398d420
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/KMPTest.java
@@ -0,0 +1,29 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+public class KMPTest {
+
+ @Test
+ public void testNullInputs() {
+ assertEquals(List.of(), KMP.kmpMatcher(null, "A"));
+ assertEquals(List.of(), KMP.kmpMatcher("A", null));
+ assertEquals(List.of(), KMP.kmpMatcher(null, null));
+ }
+
+ @Test
+ public void testKMPMatcher() {
+ assertEquals(List.of(0, 1), KMP.kmpMatcher("AAAAABAAABA", "AAAA"));
+ assertEquals(List.of(0, 3), KMP.kmpMatcher("ABCABC", "ABC"));
+ assertEquals(List.of(10), KMP.kmpMatcher("ABABDABACDABABCABAB", "ABABCABAB"));
+ assertEquals(List.of(), KMP.kmpMatcher("ABCDE", "FGH"));
+ assertEquals(List.of(), KMP.kmpMatcher("A", "AA"));
+ assertEquals(List.of(0, 1, 2), KMP.kmpMatcher("AAA", "A"));
+ assertEquals(List.of(0), KMP.kmpMatcher("A", "A"));
+ assertEquals(List.of(), KMP.kmpMatcher("", "A"));
+ assertEquals(List.of(), KMP.kmpMatcher("A", ""));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java
new file mode 100644
index 000000000000..c22cc77df18a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class KasaiAlgorithmTest {
+
+ @Test
+ public void testKasaiBanana() {
+ String text = "banana";
+ // Suffixes:
+ // 0: banana
+ // 1: anana
+ // 2: nana
+ // 3: ana
+ // 4: na
+ // 5: a
+ //
+ // Sorted Suffixes:
+ // 5: a
+ // 3: ana
+ // 1: anana
+ // 0: banana
+ // 4: na
+ // 2: nana
+ int[] suffixArr = {5, 3, 1, 0, 4, 2};
+
+ int[] expectedLcp = {1, 3, 0, 0, 2, 0};
+
+ assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr));
+ }
+
+ @Test
+ public void testKasaiAaaa() {
+ String text = "aaaa";
+ // Sorted Suffixes:
+ // 3: a
+ // 2: aa
+ // 1: aaa
+ // 0: aaaa
+ int[] suffixArr = {3, 2, 1, 0};
+ int[] expectedLcp = {1, 2, 3, 0};
+
+ assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr));
+ }
+
+ @Test
+ public void testKasaiEmptyString() {
+ assertArrayEquals(new int[0], KasaiAlgorithm.kasai("", new int[0]));
+ }
+
+ @Test
+ public void testKasaiSingleChar() {
+ assertArrayEquals(new int[] {0}, KasaiAlgorithm.kasai("A", new int[] {0}));
+ }
+
+ @Test
+ public void testKasaiNullTextOrSuffixArray() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai(null, new int[] {0}));
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", null));
+ }
+
+ @Test
+ public void testKasaiInvalidSuffixArrayLength() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {0, 1}));
+ }
+
+ @Test
+ public void testKasaiInvalidSuffixArrayIndex() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {1})); // Out of bounds
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {-1})); // Out of bounds
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java
deleted file mode 100644
index aa13c0f4a474..000000000000
--- a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.thealgorithms.strings;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.stream.Stream;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-class LongestPalindromicSubstringTest {
-
- @ParameterizedTest
- @MethodSource("provideTestCasesForLongestPalindrome")
- void testLongestPalindrome(String input, String expected) {
- assertEquals(expected, LongestPalindromicSubstring.longestPalindrome(input));
- }
-
- private static Stream provideTestCasesForLongestPalindrome() {
- return Stream.of(Arguments.of("babad", "bab"), Arguments.of("cbbd", "bb"), Arguments.of("a", "a"), Arguments.of("", ""), Arguments.of("abc", "a"), Arguments.of(null, ""), Arguments.of("aaaaa", "aaaaa"));
- }
-}
diff --git a/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java
new file mode 100644
index 000000000000..366f6863340d
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class LongestRepeatedSubstringTest {
+
+ @ParameterizedTest(name = "\"{0}\" -> \"{1}\"")
+ @MethodSource("provideTestCases")
+ void testLongestRepeatedSubstring(String input, String expected) {
+ assertEquals(expected, LongestRepeatedSubstring.longestRepeatedSubstring(input));
+ }
+
+ private static Stream provideTestCases() {
+ return Stream.of(Arguments.of("banana", "ana"), Arguments.of("abcabc", "abc"), Arguments.of("aaaa", "aaa"), Arguments.of("abcd", ""), Arguments.of("a", ""), Arguments.of("", ""), Arguments.of(null, ""), Arguments.of("aab", "a"), Arguments.of("aa", "a"), Arguments.of("mississippi", "issi"));
+ }
+
+ @ParameterizedTest(name = "\"{0}\" -> LCP={1}")
+ @MethodSource("provideLcpTestCases")
+ void testBuildLcpArray(String input, int[] expectedLcp) {
+ int[] suffixArray = SuffixArray.buildSuffixArray(input);
+ assertArrayEquals(expectedLcp, LongestRepeatedSubstring.buildLcpArray(input, suffixArray));
+ }
+
+ private static Stream provideLcpTestCases() {
+ return Stream.of(Arguments.of("banana", new int[] {1, 3, 0, 0, 2}), Arguments.of("ab", new int[] {0}));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java
new file mode 100644
index 000000000000..016f72ff9d7f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class MoveHashToEndTest {
+
+ @Test
+ void testBasicCase() {
+ assertEquals("helllo###", MoveHashToEnd.moveHashToEnd("h#e#l#llo"));
+ }
+
+ @Test
+ void testNoHash() {
+ assertEquals("hello", MoveHashToEnd.moveHashToEnd("hello"));
+ }
+
+ @Test
+ void testAllHashes() {
+ assertEquals("###", MoveHashToEnd.moveHashToEnd("###"));
+ }
+
+ @Test
+ void testHashAtEnd() {
+ assertEquals("hello#", MoveHashToEnd.moveHashToEnd("hello#"));
+ }
+
+ @Test
+ void testHashAtStart() {
+ assertEquals("hello#", MoveHashToEnd.moveHashToEnd("#hello"));
+ }
+
+ @Test
+ void testEmptyString() {
+ assertEquals("", MoveHashToEnd.moveHashToEnd(""));
+ }
+
+ @Test
+ void testNullInput() {
+ assertNull(MoveHashToEnd.moveHashToEnd(null));
+ }
+
+ @Test
+ void testSingleHash() {
+ assertEquals("#", MoveHashToEnd.moveHashToEnd("#"));
+ }
+
+ @Test
+ void testSingleNonHashChar() {
+ assertEquals("a", MoveHashToEnd.moveHashToEnd("a"));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/RabinKarpTest.java b/src/test/java/com/thealgorithms/strings/RabinKarpTest.java
new file mode 100644
index 000000000000..6dfd099e9bca
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/RabinKarpTest.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+public class RabinKarpTest {
+
+ @Test
+ public void testNullInputs() {
+ assertEquals(List.of(), RabinKarp.search(null, "A"));
+ assertEquals(List.of(), RabinKarp.search("A", null));
+ assertEquals(List.of(), RabinKarp.search(null, null));
+ }
+
+ @Test
+ public void testHashCollision() {
+ // 'a' = 97. (char)198 % 101 = 97.
+ // For length 1, h = 1. p = 97. t = 198 % 101 = 97.
+ // Collision occurs, loop checks characters: 198 != 97, breaks.
+ char collisionChar = (char) 198;
+ String text = String.valueOf(collisionChar);
+ String pattern = "a";
+ assertEquals(List.of(), RabinKarp.search(text, pattern));
+ }
+
+ @Test
+ public void testSearchWithCustomQ() {
+ // Using a different prime
+ assertEquals(List.of(0, 1), RabinKarp.search("AAAA", "AAA", 13));
+ }
+
+ @Test
+ public void testRabinKarpSearch() {
+ assertEquals(List.of(0, 1), RabinKarp.search("AAAAABAAABA", "AAAA"));
+ assertEquals(List.of(0, 3), RabinKarp.search("ABCABC", "ABC"));
+ assertEquals(List.of(10), RabinKarp.search("ABABDABACDABABCABAB", "ABABCABAB"));
+ assertEquals(List.of(), RabinKarp.search("ABCDE", "FGH"));
+ assertEquals(List.of(), RabinKarp.search("A", "AA"));
+ assertEquals(List.of(0, 1, 2), RabinKarp.search("AAA", "A"));
+ assertEquals(List.of(0), RabinKarp.search("A", "A"));
+ assertEquals(List.of(), RabinKarp.search("", "A"));
+ assertEquals(List.of(), RabinKarp.search("A", ""));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java
new file mode 100644
index 000000000000..3beb2e83399b
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class RemoveStarsTest {
+
+ @Test
+ void testExampleCase() {
+ assertEquals("lecoe", RemoveStars.removeStars("leet**cod*e"));
+ }
+
+ @Test
+ void testAllStars() {
+ assertEquals("", RemoveStars.removeStars("abc***"));
+ }
+
+ @Test
+ void testNoStars() {
+ assertEquals("hello", RemoveStars.removeStars("hello"));
+ }
+
+ @Test
+ void testSingleCharacter() {
+ assertEquals("", RemoveStars.removeStars("a*"));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java
new file mode 100644
index 000000000000..42b2d04ff265
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class TopKFrequentWordsTest {
+
+ @ParameterizedTest
+ @MethodSource("validTestCases")
+ void testFindTopKFrequentWords(String[] words, int k, List expected) {
+ assertEquals(expected, TopKFrequentWords.findTopKFrequentWords(words, k));
+ }
+
+ static Stream validTestCases() {
+ return Stream.of(Arguments.of(new String[] {"i", "love", "leetcode", "i", "love", "coding"}, 2, List.of("i", "love")), Arguments.of(new String[] {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4, List.of("the", "is", "sunny", "day")),
+ Arguments.of(new String[] {"bbb", "aaa", "bbb", "aaa", "ccc"}, 2, List.of("aaa", "bbb")), Arguments.of(new String[] {"one", "two", "three"}, 10, List.of("one", "three", "two")), Arguments.of(new String[] {}, 3, List.of()), Arguments.of(new String[] {"x", "x", "y"}, 0, List.of()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidTestCases")
+ void testFindTopKFrequentWordsInvalidInput(String[] words, int k) {
+ assertThrows(IllegalArgumentException.class, () -> TopKFrequentWords.findTopKFrequentWords(words, k));
+ }
+
+ static Stream invalidTestCases() {
+ return Stream.of(Arguments.of((String[]) null, 1), Arguments.of(new String[] {"a", null, "b"}, 2), Arguments.of(new String[] {"a"}, -1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
deleted file mode 100644
index 411b11e743b8..000000000000
--- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.thealgorithms.strings;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
-
-public class ValidParenthesesTest {
-
- @ParameterizedTest(name = "Input: \"{0}\" β Expected: {1}")
- @CsvSource({"'()', true", "'()[]{}', true", "'(]', false", "'{[]}', true", "'([{}])', true", "'([)]', false", "'', true", "'(', false", "')', false", "'{{{{}}}}', true", "'[({})]', true", "'[(])', false", "'[', false", "']', false", "'()()()()', true", "'(()', false", "'())', false",
- "'{[()()]()}', true"})
- void
- testIsValid(String input, boolean expected) {
- assertEquals(expected, ValidParentheses.isValid(input));
- }
-
- @Test
- void testNullInputThrows() {
- IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(null));
- assertEquals("Input string cannot be null", ex.getMessage());
- }
-
- @ParameterizedTest(name = "Input: \"{0}\" β throws IllegalArgumentException")
- @CsvSource({"'a'", "'()a'", "'[123]'", "'{hello}'", "'( )'", "'\t'", "'\n'", "'@#$%'"})
- void testInvalidCharactersThrow(String input) {
- IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(input));
- assertTrue(ex.getMessage().startsWith("Unexpected character"));
- }
-}