distanceAndU = unprocessed.iterator().next();
- unprocessed.remove(distanceAndU);
- int u = distanceAndU.getRight();
- processed[u] = true;
-
- for (int v = 0; v < vertexCount; v++) {
- if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) {
- unprocessed.remove(Pair.of(distances[v], v));
- distances[v] = distances[u] + graph[u][v];
- unprocessed.add(Pair.of(distances[v], v));
- }
- }
- }
-
- return distances;
- }
-}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java
new file mode 100644
index 000000000000..3ce8696f55ff
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java
@@ -0,0 +1,114 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Multi-source Breadth-First Search (BFS) implementation for the Rotting Oranges problem.
+ *
+ * Algorithm explanation:
+ * https://en.wikipedia.org/wiki/Breadth-first_search
+ *
+ *
Problem reference:
+ * https://leetcode.com/problems/rotting-oranges/
+ *
+ *
Given a grid where:
+ *
+ * - 0 represents an empty cell
+ * - 1 represents a fresh orange
+ * - 2 represents a rotten orange
+ *
+ *
+ * Returns the minimum number of minutes required for all fresh oranges
+ * to become rotten. Returns {@code -1} if it is impossible.
+ *
+ *
Time Complexity: O(m Γ n)
+ *
Space Complexity: O(m Γ n)
+ */
+public class RottingOranges {
+
+ private static final int[] DEL_ROW = {-1, 0, 1, 0};
+ private static final int[] DEL_COL = {0, 1, 0, -1};
+
+ private static final class Cell {
+ private final int row;
+ private final int col;
+ private final int minute;
+
+ Cell(int row, int col, int minute) {
+ this.row = row;
+ this.col = col;
+ this.minute = minute;
+ }
+ }
+
+ /**
+ * Executes the Rotting Oranges algorithm.
+ *
+ * @param grid the input grid
+ * @return minimum minutes required to rot all fresh oranges,
+ * or -1 if impossible
+ */
+ public int run(int[][] grid) {
+
+ if (grid == null || grid.length == 0 || grid[0].length == 0) {
+ return 0;
+ }
+
+ int rows = grid.length;
+ int cols = grid[0].length;
+
+ // Create a copy so original input is not modified
+ int[][] copy = new int[rows][cols];
+
+ for (int i = 0; i < rows; i++) {
+ copy[i] = grid[i].clone();
+ }
+
+ Queue queue = new LinkedList<>();
+ int freshOranges = 0;
+
+ // Find all rotten oranges and count fresh oranges
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < cols; col++) {
+
+ if (copy[row][col] == 2) {
+ queue.offer(new Cell(row, col, 0));
+ } else if (copy[row][col] == 1) {
+ freshOranges++;
+ }
+ }
+ }
+
+ if (freshOranges == 0) {
+ return 0;
+ }
+
+ int rottedFresh = 0;
+ int minutes = 0;
+
+ // Multi-source BFS
+ while (!queue.isEmpty()) {
+
+ Cell current = queue.poll();
+
+ minutes = Math.max(minutes, current.minute);
+
+ for (int i = 0; i < 4; i++) {
+
+ int newRow = current.row + DEL_ROW[i];
+ int newCol = current.col + DEL_COL[i];
+
+ if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && copy[newRow][newCol] == 1) {
+
+ copy[newRow][newCol] = 2;
+ rottedFresh++;
+
+ queue.offer(new Cell(newRow, newCol, current.minute + 1));
+ }
+ }
+ }
+
+ return rottedFresh == freshOranges ? minutes : -1;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
index 252b06ea59b0..4400a97d8128 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md
@@ -2,6 +2,8 @@
A hash map organizes data so you can quickly look up values for a given key.
+> Note: The term βhash mapβ refers to the data structure concept, while `HashMap` refers specifically to Javaβs implementation.
+
## Strengths:
- **Fast lookups**: Lookups take O(1) time on average.
- **Flexible keys**: Most data types can be used for keys, as long as they're hashable.
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
new file mode 100644
index 000000000000..0ee788db2ff9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.datastructures.lists;
+
+/**
+ * Returns the middle node of a singly linked list using the two-pointer technique.
+ *
+ * The {@code slow} pointer advances by one node per iteration while {@code fast} advances by two.
+ * When {@code fast == null} or {@code fast.next == null}, {@code slow} points to the middle node.
+ * For even-length lists, this returns the second middle node.
+ *
+ * This method does not modify the input list.
+ *
+ * Reference: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
+ *
+ * Complexity:
+ *
+ * - Time: {@code O(n)}
+ * - Space: {@code O(1)}
+ *
+ */
+public final class MiddleOfLinkedList {
+
+ private MiddleOfLinkedList() {
+ }
+
+ /**
+ * Returns the middle node of the list.
+ *
+ * @param head the head of the singly linked list; may be {@code null}
+ * @return the middle node (second middle for even-sized lists), or {@code null} if {@code head} is {@code null}
+ */
+ public static SinglyLinkedListNode middleNode(final SinglyLinkedListNode head) {
+ if (head == null) {
+ return null;
+ }
+
+ SinglyLinkedListNode slow = head;
+ SinglyLinkedListNode fast = head;
+
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+
+ return slow;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
similarity index 84%
rename from src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
rename to src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
index c81476eaec32..7bb16921b9ef 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.datastructures.lists;
import java.util.Stack;
@@ -9,6 +9,13 @@
*
* See more:
* https://www.geeksforgeeks.org/function-to-check-if-a-singly-linked-list-is-palindrome/
+ *
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
*/
@SuppressWarnings("rawtypes")
public final class PalindromeSinglyLinkedList {
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
new file mode 100644
index 000000000000..200c636ce1ab
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.datastructures.lists;
+
+import java.util.Objects;
+
+/**
+ * A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy.
+ * When an element is searched, it is automatically moved to the head of the list
+ * to optimize subsequent lookups.
+ *
+ * @param the type of elements held in this list
+ */
+public class SelfOrganizingLinkedList {
+
+ /**
+ * Node structure for the self-organizing linked list.
+ *
+ * @param the type of element held in this node
+ */
+ private static class Node {
+ E value;
+ Node next;
+
+ Node(E value) {
+ this.value = value;
+ this.next = null;
+ }
+ }
+
+ private Node head;
+ private int size;
+
+ public SelfOrganizingLinkedList() {
+ this.size = 0;
+ this.head = null;
+ }
+
+ /**
+ * Inserts a new value at the end of the list.
+ *
+ * @param value the element to add
+ */
+ public void insert(E value) {
+ Node newNode = new Node<>(value);
+ if (head == null) {
+ head = newNode;
+ } else {
+ Node temp = head;
+ while (temp.next != null) {
+ temp = temp.next;
+ }
+ temp.next = newNode;
+ }
+ size++;
+ }
+
+ /**
+ * Searches for a value in the list.
+ * If found, moves the node to the front (head) of the list.
+ *
+ * @param key the value to search for
+ * @return true if the element is present, false otherwise
+ */
+ public boolean search(E key) {
+ if (head == null) {
+ return false;
+ }
+ // If the key is already at the head, no pointers need to be rewired
+ if (Objects.equals(head.value, key)) {
+ return true;
+ }
+
+ Node prev = head;
+ Node curr = head.next;
+
+ while (curr != null && !Objects.equals(curr.value, key)) {
+ prev = curr;
+ curr = curr.next;
+ }
+
+ if (curr == null) {
+ return false;
+ }
+
+ // Unlink curr from its current position and move it to head
+ prev.next = curr.next;
+ curr.next = head;
+ head = curr;
+ return true;
+ }
+
+ /** Gets the current head value of the list. */
+ public E getHeadValue() {
+ return head != null ? head.value : null;
+ }
+
+ /** Returns the size of the list. */
+ public int getSize() {
+ return size;
+ }
+
+ /** Returns true if the list contains no elements. */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+}
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/queues/ThreadSafeQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java
new file mode 100644
index 000000000000..a943b0028974
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java
@@ -0,0 +1,186 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @brief Thread-safe bounded queue implementation using ReentrantLock and Condition variables
+ * @details A blocking queue that supports multiple producers and consumers.
+ * Uses a circular buffer internally with lock-based synchronization to ensure
+ * thread safety. Producers block when the queue is full, and consumers block
+ * when the queue is empty.
+ * @see Producer-Consumer Problem
+ */
+public class ThreadSafeQueue {
+
+ private final Object[] buffer;
+ private final int capacity;
+ private int head;
+ private int tail;
+ private int count;
+ private final ReentrantLock lock;
+ private final Condition notFull;
+ private final Condition notEmpty;
+
+ /**
+ * @brief Constructs a ThreadSafeQueue with the specified capacity
+ * @param capacity the maximum number of elements the queue can hold
+ * @throws IllegalArgumentException if capacity is less than or equal to zero
+ */
+ public ThreadSafeQueue(int capacity) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Capacity must be greater than zero.");
+ }
+ this.capacity = capacity;
+ this.buffer = new Object[capacity];
+ this.head = 0;
+ this.tail = 0;
+ this.count = 0;
+ this.lock = new ReentrantLock();
+ this.notFull = lock.newCondition();
+ this.notEmpty = lock.newCondition();
+ }
+
+ /**
+ * @brief Adds an element to the tail of the queue, blocking if full
+ * @param item the element to add
+ * @throws InterruptedException if the thread is interrupted while waiting
+ * @throws IllegalArgumentException if the item is null
+ */
+ public void enqueue(T item) throws InterruptedException {
+ if (item == null) {
+ throw new IllegalArgumentException("Cannot enqueue null item.");
+ }
+
+ lock.lock();
+ try {
+ while (count == capacity) {
+ notFull.await();
+ }
+ buffer[tail] = item;
+ tail = (tail + 1) % capacity;
+ count++;
+ notEmpty.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Removes and returns the element at the head of the queue, blocking if empty
+ * @return the element at the head of the queue
+ * @throws InterruptedException if the thread is interrupted while waiting
+ */
+ @SuppressWarnings("unchecked")
+ public T dequeue() throws InterruptedException {
+ lock.lock();
+ try {
+ while (count == 0) {
+ notEmpty.await();
+ }
+ T item = (T) buffer[head];
+ buffer[head] = null;
+ head = (head + 1) % capacity;
+ count--;
+ notFull.signalAll();
+ return item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Adds an element to the tail of the queue without blocking
+ * @param item the element to add
+ * @return true if the element was added, false if the queue was full
+ * @throws IllegalArgumentException if the item is null
+ */
+ public boolean offer(T item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Cannot enqueue null item.");
+ }
+
+ lock.lock();
+ try {
+ if (count == capacity) {
+ return false;
+ }
+ buffer[tail] = item;
+ tail = (tail + 1) % capacity;
+ count++;
+ notEmpty.signalAll();
+ return true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Removes and returns the element at the head without blocking
+ * @return the element at the head, or null if the queue is empty
+ */
+ @SuppressWarnings("unchecked")
+ public T poll() {
+ lock.lock();
+ try {
+ if (count == 0) {
+ return null;
+ }
+ T item = (T) buffer[head];
+ buffer[head] = null;
+ head = (head + 1) % capacity;
+ count--;
+ notFull.signalAll();
+ return item;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Returns the number of elements in the queue
+ * @return the current size of the queue
+ */
+ public int size() {
+ lock.lock();
+ try {
+ return count;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Checks if the queue is empty
+ * @return true if the queue contains no elements
+ */
+ public boolean isEmpty() {
+ lock.lock();
+ try {
+ return count == 0;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Checks if the queue is full
+ * @return true if the queue has reached its capacity
+ */
+ public boolean isFull() {
+ lock.lock();
+ try {
+ return count == capacity;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @brief Returns the maximum capacity of the queue
+ * @return the capacity
+ */
+ public int capacity() {
+ return capacity;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
similarity index 99%
rename from src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java
rename to src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
index 236a23205180..ed67f9ae3394 100644
--- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.tree;
+package com.thealgorithms.datastructures.trees;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
index 57b3edc163ca..af6acb0cbb2b 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
@@ -8,13 +8,18 @@ public class SegmentTree {
/* Constructor which takes the size of the array and the array as a parameter*/
public SegmentTree(int n, int[] arr) {
+ if (arr == null) {
+ throw new IllegalArgumentException("Input array must not be null");
+ }
+ if (n <= 0 || n > arr.length) {
+ throw new IllegalArgumentException("Size must be in the range [1, " + arr.length + "], but was " + n);
+ }
this.n = n;
int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
int segSize = 2 * (int) Math.pow(2, x) - 1;
this.segTree = new int[segSize];
this.arr = arr;
- this.n = n;
constructTree(arr, 0, n - 1, 0);
}
@@ -47,7 +52,8 @@ private void updateTree(int start, int end, int index, int diff, int segIndex) {
/* A function to update the value at a particular index*/
public void update(int index, int value) {
- if (index < 0 || index > n) {
+ // Valid positions are 0..n-1; index == n is out of bounds and must not reach arr[index].
+ if (index < 0 || index >= n) {
return;
}
@@ -73,7 +79,8 @@ private int getSumTree(int start, int end, int qStart, int qEnd, int segIndex) {
/* A function to query the sum of the subarray [start...end]*/
public int getSum(int start, int end) {
- if (start < 0 || end > n || start > end) {
+ // The last queryable position is n-1, so end == n is an out of range query.
+ if (start < 0 || end >= n || start > end) {
return 0;
}
return getSumTree(0, n - 1, start, end, 0);
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/others/Implementing_auto_completing_features_using_trie.java b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java
similarity index 98%
rename from src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java
rename to src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java
index 7a1a7aadd805..624e3d65bfc1 100644
--- a/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java
@@ -1,8 +1,8 @@
-package com.thealgorithms.others;
+package com.thealgorithms.datastructures.trees;
// Java Program to implement Auto-Complete
// Feature using Trie
-class Trieac {
+class TrieAutocomplete {
// Alphabet size (# of symbols)
public static final int ALPHABET_SIZE = 26;
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java
new file mode 100644
index 000000000000..6feaa6f35048
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java
@@ -0,0 +1,235 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Wavelet Tree is a highly efficient data structure used to store sequences
+ * and answer queries like rank, select, and quantile in O(log(max_val - min_val)) time.
+ * This structure is particularly useful in competitive programming and text compression.
+ */
+public class WaveletTree {
+
+ private class Node {
+ int low;
+ int high;
+ Node left;
+ Node right;
+ List leftCount; // Prefix sums of elements going to the left child
+
+ /**
+ * Recursively constructs the tree nodes by partitioning the array.
+ *
+ * @param arr the subarray for the current node
+ * @param low the minimum possible value in the current node
+ * @param high the maximum possible value in the current node
+ */
+ Node(int[] arr, int low, int high) {
+ this.low = low;
+ this.high = high;
+
+ if (low == high) {
+ return;
+ }
+
+ int mid = low + (high - low) / 2;
+ leftCount = new ArrayList<>(arr.length + 1);
+ leftCount.add(0);
+
+ List leftArr = new ArrayList<>();
+ List rightArr = new ArrayList<>();
+
+ for (int x : arr) {
+ if (x <= mid) {
+ leftArr.add(x);
+ leftCount.add(leftCount.get(leftCount.size() - 1) + 1);
+ } else {
+ rightArr.add(x);
+ leftCount.add(leftCount.get(leftCount.size() - 1));
+ }
+ }
+
+ if (!leftArr.isEmpty()) {
+ this.left = new Node(leftArr.stream().mapToInt(i -> i).toArray(), low, mid);
+ }
+ if (!rightArr.isEmpty()) {
+ this.right = new Node(rightArr.stream().mapToInt(i -> i).toArray(), mid + 1, high);
+ }
+ }
+ }
+
+ private Node root;
+ private final int n;
+
+ /**
+ * Constructs a Wavelet Tree from the given array.
+ * The min and max values are determined dynamically from the array.
+ *
+ * @param arr the input array
+ */
+ public WaveletTree(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ this.n = 0;
+ return;
+ }
+ this.n = arr.length;
+ int min = arr[0];
+ int max = arr[0];
+ for (int x : arr) {
+ if (x < min) {
+ min = x;
+ }
+ if (x > max) {
+ max = x;
+ }
+ }
+ root = new Node(arr, min, max);
+ }
+
+ /**
+ * Constructs a Wavelet Tree from the given array with specific min and max values.
+ *
+ * @param arr the input array
+ * @param minValue the minimum possible value
+ * @param maxValue the maximum possible value
+ */
+ public WaveletTree(int[] arr, int minValue, int maxValue) {
+ if (arr == null || arr.length == 0) {
+ this.n = 0;
+ return;
+ }
+ this.n = arr.length;
+ root = new Node(arr, minValue, maxValue);
+ }
+
+ /**
+ * How many times does the number x appear in the array from index 0 to i (inclusive)?
+ *
+ * @param x the number to search for
+ * @param i the end index (0-based, inclusive)
+ * @return the number of occurrences of x in arr[0...i]
+ */
+ public int rank(int x, int i) {
+ if (root == null || x < root.low || x > root.high || i < 0) {
+ return 0;
+ }
+ // If i is out of bounds, cap it at n - 1
+ int endIdx = Math.min(i, n - 1);
+ return rank(root, x, endIdx + 1);
+ }
+
+ private int rank(Node node, int x, int count) {
+ if (node == null || count == 0) {
+ return 0;
+ }
+ if (node.low == node.high) {
+ return count;
+ }
+ int mid = node.low + (node.high - node.low) / 2;
+ int leftC = node.leftCount.get(count);
+ if (x <= mid) {
+ return rank(node.left, x, leftC);
+ } else {
+ return rank(node.right, x, count - leftC);
+ }
+ }
+
+ /**
+ * What is the 0-based index of the k-th occurrence of the number x in the array?
+ *
+ * @param x the number to search for
+ * @param k the occurrence count (1-based)
+ * @return the 0-based index in the original array, or -1 if x occurs less than k times
+ */
+ public int select(int x, int k) {
+ if (root == null || x < root.low || x > root.high || k <= 0) {
+ return -1;
+ }
+ if (rank(x, n - 1) < k) {
+ return -1;
+ }
+ return select(root, x, k);
+ }
+
+ private int select(Node node, int x, int k) {
+ if (node.low == node.high) {
+ return k - 1; // 0-based index within the imaginary array at the leaf
+ }
+ int mid = node.low + (node.high - node.low) / 2;
+ if (x <= mid) {
+ int posInLeft = select(node.left, x, k);
+ return binarySearchLeft(node.leftCount, posInLeft + 1);
+ } else {
+ int posInRight = select(node.right, x, k);
+ return binarySearchRight(node.leftCount, posInRight + 1);
+ }
+ }
+
+ private int binarySearchLeft(List prefixSums, int k) {
+ int l = 1;
+ int r = prefixSums.size() - 1;
+ int ans = -1;
+ while (l <= r) {
+ int mid = l + (r - l) / 2;
+ if (prefixSums.get(mid) >= k) {
+ ans = mid;
+ r = mid - 1;
+ } else {
+ l = mid + 1;
+ }
+ }
+ return ans == -1 ? -1 : ans - 1; // Convert to 0-based index
+ }
+
+ private int binarySearchRight(List prefixSums, int k) {
+ int l = 1;
+ int r = prefixSums.size() - 1;
+ int ans = -1;
+ while (l <= r) {
+ int mid = l + (r - l) / 2;
+ if (mid - prefixSums.get(mid) >= k) {
+ ans = mid;
+ r = mid - 1;
+ } else {
+ l = mid + 1;
+ }
+ }
+ return ans == -1 ? -1 : ans - 1; // Convert to 0-based index
+ }
+
+ /**
+ * If you sort the subarray from index left to right, what would be the k-th smallest element?
+ * This query is also commonly known as the quantile query.
+ *
+ * @param left the start index of the subarray (0-based, inclusive)
+ * @param right the end index of the subarray (0-based, inclusive)
+ * @param k the rank of the smallest element (1-based, e.g., k=1 is the minimum)
+ * @return the k-th smallest element in the subarray, or -1 if invalid parameters
+ */
+ public int kthSmallest(int left, int right, int k) {
+ if (root == null || left > right || left < 0 || k < 1 || k > right - left + 1) {
+ return -1;
+ }
+ return kthSmallest(root, left, right, k);
+ }
+
+ private int kthSmallest(Node node, int left, int right, int k) {
+ if (node.low == node.high) {
+ return node.low;
+ }
+
+ int countLeftInLMinus1 = (left == 0) ? 0 : node.leftCount.get(left);
+ int countLeftInR = node.leftCount.get(right + 1);
+ int elementsToLeft = countLeftInR - countLeftInLMinus1;
+
+ if (k <= elementsToLeft) {
+ int newL = countLeftInLMinus1;
+ int newR = countLeftInR - 1;
+ return kthSmallest(node.left, newL, newR, k);
+ } else {
+ int newL = left - countLeftInLMinus1;
+ int newR = right - countLeftInR;
+ return kthSmallest(node.right, newL, newR, k - elementsToLeft);
+ }
+ }
+}
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/DigitDP.java b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java
new file mode 100644
index 000000000000..7dae7603fedc
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java
@@ -0,0 +1,111 @@
+package com.thealgorithms.dynamicprogramming;
+import java.util.Arrays;
+
+/**
+ * A generalized template for the Digit Dynamic Programming (Digit DP)
+ * technique.
+ * Digit DP is used to count numbers within a range [L, R] that satisfy specific
+ * digit properties.
+ * This specific implementation demonstrates counting the numbers whose digit
+ * sum equals a target value.
+ *
+ *
+ * Example:
+ * countRangeWithDigitSum(1, 100, 5) returns 6 (numbers: 5, 14, 23, 32, 41, 50)
+ */
+public final class DigitDP {
+
+ // Maximum theoretical digit sum for a 64-bit signed long integer (9 * 19 digits
+ // = 171)
+ private static final int MAX_DIGIT_SUM = 171;
+
+ private DigitDP() {
+ // Prevent instantiation for utility/algorithm template class
+ }
+
+ /**
+ * Counts how many numbers in the range [L, R] have a digit sum equal to the
+ * target.
+ *
+ * @param l The lower bound of the range (inclusive).
+ * @param r The upper bound of the range (inclusive).
+ * @param target The exact sum of digits required.
+ * @return The count of valid integers.
+ */
+ public static long countRangeWithDigitSum(long l, long r, int target) {
+ if (l > r || target < 0 || target > MAX_DIGIT_SUM) {
+ return 0;
+ }
+ long countR = countWithDigitSum(r, target);
+ long countLMinus1 = countWithDigitSum(l - 1, target);
+ return countR - countLMinus1;
+ }
+
+ private static long countWithDigitSum(long number, int target) {
+ if (number < 0) {
+ return 0;
+ }
+ String numStr = Long.toString(number);
+ int length = numStr.length();
+
+ // dp[index][current_sum][tight]
+ long[][][] dp = new long[length][MAX_DIGIT_SUM + 1][2];
+ for (long[][] row : dp) {
+ for (long[] col : row) {
+ Arrays.fill(col, -1);
+ }
+ }
+
+ return solve(0, 0, 1, numStr, target, dp);
+ }
+
+ /**
+ * Recursive memoized function to explore digit placements.
+ *
+ * Time Complexity: O(number_of_digits * target_sum * 10)
+ * Space Complexity: O(number_of_digits * target_sum * 2)
+ *
+ * @param index Current digit position from left to right (most significant
+ * first).
+ * @param currentSum Cumulative sum of digits chosen so far.
+ * @param tight Flag indicating if current prefix matches the original
+ * number boundary.
+ * @param numStr String representation of the upper ceiling limit.
+ * @param target The exact required sum of digits.
+ * @param dp Memoization matrix cache table.
+ * @return Total valid combinations from the current state configuration.
+ */
+ private static long solve(int index, int currentSum, int tight, String numStr, int target, long[][][] dp) {
+ // Base case: If we have processed all digits
+ if (index == numStr.length()) {
+ return currentSum == target ? 1 : 0;
+ }
+
+ // Return memoized state if already evaluated
+ if (dp[index][currentSum][tight] != -1) {
+ return dp[index][currentSum][tight];
+ }
+
+ long ans = 0;
+ // Determine the maximum limit for the current position digit
+ int limit = (tight == 1) ? (numStr.charAt(index) - '0') : 9;
+
+ // Iterate through all possible valid digits for this position
+ for (int digit = 0; digit <= limit; digit++) {
+ int nextSum = currentSum + digit;
+
+ // Optimization: If the digit sum exceeds the target, prune branch
+ if (nextSum > target) {
+ continue;
+ }
+
+ // Next state remains tight only if current state is tight and we place the
+ // exact limit digit
+ int nextTight = (tight == 1 && digit == limit) ? 1 : 0;
+ ans += solve(index + 1, nextSum, nextTight, numStr, target, dp);
+ }
+
+ dp[index][currentSum][tight] = ans;
+ return ans;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
index 0d6aff2bbef3..df158112d233 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
@@ -1,10 +1,22 @@
package com.thealgorithms.dynamicprogramming;
-
import java.util.HashMap;
import java.util.Map;
-
/**
- * @author Varun Upadhyay (https://github.com/varunu28)
+ * Collection of Dynamic Programming techniques to solve for the n-th Fibonacci number.
+ *
+ * This file showcases Top-Down Memoization ({@code fibMemo}), Bottom-Up Tabulation ({@code fibBotUp}),
+ * and Space-Optimized Iteration ({@code fibOptimized}).
+ *
+ * For alternative structural paradigms, mathematical formulas, or verification steps, see:
+ *
+ * - {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
+ * - {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
+ * - {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
+ * - {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
+ * - {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
+ * * @author Varun Upadhyay (https://github.com/varunu28)
*/
public final class Fibonacci {
private Fibonacci() {
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
index 0b40d4559341..5c8a6a953f83 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
@@ -1,58 +1,42 @@
package com.thealgorithms.dynamicprogramming;
/**
- * Algorithm explanation
- * https://www.educative.io/edpresso/longest-palindromic-subsequence-algorithm
+ * Longest Palindromic Subsequence algorithm.
+ * A palindromic subsequence is a subsequence that reads the same forwards and backwards.
+ * This implementation finds the longest such subsequence by computing the LCS of the
+ * original string and its reverse.
+ *
+ * @see Wikipedia
*/
public final class LongestPalindromicSubsequence {
private LongestPalindromicSubsequence() {
}
- public static void main(String[] args) {
- String a = "BBABCBCAB";
- String b = "BABCBAB";
-
- String aLPS = lps(a);
- String bLPS = lps(b);
-
- System.out.println(a + " => " + aLPS);
- System.out.println(b + " => " + bLPS);
- }
-
- public static String lps(String original) throws IllegalArgumentException {
- StringBuilder reverse = new StringBuilder(original);
- reverse = reverse.reverse();
- return recursiveLPS(original, reverse.toString());
+ /**
+ * Returns the longest palindromic subsequence of the given string.
+ *
+ * @param original the input string
+ * @return the longest palindromic subsequence
+ * @throws IllegalArgumentException if the input string is null
+ */
+ public static String lps(String original) {
+ if (original == null) {
+ throw new IllegalArgumentException("Input string must not be null");
+ }
+ String reverse = new StringBuilder(original).reverse().toString();
+ return recursiveLPS(original, reverse);
}
private static String recursiveLPS(String original, String reverse) {
- String bestResult = "";
-
- // no more chars, then return empty
- if (original.length() == 0 || reverse.length() == 0) {
- bestResult = "";
- } else {
- // if the last chars match, then remove it from both strings and recur
- if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) {
- String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1));
-
- bestResult = reverse.charAt(reverse.length() - 1) + bestSubResult;
- } else {
- // otherwise (1) ignore the last character of reverse, and recur on original and
- // updated reverse again (2) ignore the last character of original and recur on the
- // updated original and reverse again then select the best result from these two
- // subproblems.
-
- String bestSubResult1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1));
- String bestSubResult2 = recursiveLPS(original.substring(0, original.length() - 1), reverse);
- if (bestSubResult1.length() > bestSubResult2.length()) {
- bestResult = bestSubResult1;
- } else {
- bestResult = bestSubResult2;
- }
- }
+ if (original.isEmpty() || reverse.isEmpty()) {
+ return "";
}
-
- return bestResult;
+ if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) {
+ String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1));
+ return reverse.charAt(reverse.length() - 1) + bestSubResult;
+ }
+ String sub1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1));
+ String sub2 = recursiveLPS(original.substring(0, original.length() - 1), reverse);
+ return sub1.length() >= sub2.length() ? sub1 : sub2;
}
}
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..86f0a24b6c1c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java
@@ -0,0 +1,127 @@
+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.
+ *
+ * Each account is expected to be a list where the first element is the user name and the
+ * remaining elements are email addresses. Accounts that share at least one email are merged into a
+ * single record.
+ */
+public final class AccountMerge {
+ private AccountMerge() {
+ // Utility class; do not instantiate.
+ }
+
+ /**
+ * Merges accounts that share one or more email addresses.
+ *
+ * The returned list is sorted by account owner name, then by the first email address when
+ * multiple merged groups have the same owner name. Within each merged account, emails are
+ * returned in lexicographic order.
+ *
+ * @param accounts a list of accounts where each entry contains a user name followed by emails
+ * @return merged accounts, or an empty list when {@code accounts} is null or empty
+ */
+ 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;
+ }
+
+ /**
+ * Lightweight union-find structure with path compression and union by rank.
+ */
+ 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/greedyalgorithms/CoinChange.java b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java
index 8054581d21d7..5f9f6080d0e1 100644
--- a/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java
@@ -6,10 +6,30 @@
// Problem Link : https://en.wikipedia.org/wiki/Change-making_problem
+/**
+ * The Coin Change problem finds the minimum number of coins needed
+ * to make a given amount using a greedy approach.
+ *
+ * Note: This greedy approach works optimally for standard coin systems
+ * (like Indian currency), but may not work for all arbitrary coin sets.
+ * For arbitrary denominations, dynamic programming is preferred.
+ *
+ * @see Change-making problem
+ */
public final class CoinChange {
private CoinChange() {
}
- // Function to solve the coin change problem
+
+ /**
+ * Returns the list of coins used to make the given amount
+ * using a greedy algorithm with standard denominations.
+ *
+ * Time Complexity: O(n log n) where n is the number of coin denominations
+ * Space Complexity: O(n)
+ *
+ * @param amount the total amount to make change for
+ * @return list of coins used to make the amount
+ */
public static ArrayList coinChangeProblem(int amount) {
// Define an array of coin denominations in descending order
Integer[] coins = {1, 2, 5, 10, 20, 50, 100, 500, 2000};
diff --git a/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java
new file mode 100644
index 000000000000..134d4eee8c4c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java
@@ -0,0 +1,102 @@
+package com.thealgorithms.machinelearning;
+
+/**
+ * A simple Linear Regression model implemented from scratch using Gradient Descent.
+ *
+ * @see Linear Regression (Wikipedia)
+ * @author Vraj Prajapati (Rosander0)
+ */
+public class LinearRegression {
+ private double m; // Slope (weight)
+ private double b; // Y-intercept (bias)
+ private final double learningRate;
+ private final int epochs;
+
+ /**
+ * Constructs a Linear Regression model with the given hyperparameters.
+ *
+ * @param learningRate controls the step size during gradient descent
+ * @param epochs the number of iterations to train the model
+ */
+ public LinearRegression(double learningRate, int epochs) {
+ this.learningRate = learningRate;
+ this.epochs = epochs;
+ this.m = 0.0;
+ this.b = 0.0;
+ }
+
+ /**
+ * Trains the model on the provided dataset using batch gradient descent.
+ *
+ * @param x the input feature values
+ * @param y the corresponding target values
+ * @throws IllegalArgumentException if the arrays are null, empty, or of differing lengths
+ */
+ public void fit(double[] x, double[] y) {
+ if (x == null || y == null || x.length != y.length || x.length == 0) {
+ throw new IllegalArgumentException("X and Y must be non-null, non-empty, and of the same length.");
+ }
+
+ int n = x.length;
+
+ for (int epoch = 0; epoch < epochs; epoch++) {
+ double mGradient = 0;
+ double bGradient = 0;
+
+ // Calculate gradients across the entire dataset
+ for (int i = 0; i < n; i++) {
+ double prediction = (m * x[i]) + b;
+ double error = prediction - y[i];
+
+ // Partial derivatives of the Mean Squared Error cost function
+ mGradient += error * x[i];
+ bGradient += error;
+ }
+
+ // Average the gradients and update the parameters
+ m -= 2.0 / n * mGradient * learningRate;
+ b -= 2.0 / n * bGradient * learningRate;
+ }
+ }
+
+ /**
+ * Predicts the output for a given input x.
+ *
+ * @param x the input value
+ * @return the predicted output
+ */
+ public double predict(double x) {
+ return (m * x) + b;
+ }
+
+ /**
+ * Calculates the Mean Squared Error of the model against a dataset.
+ *
+ * @param x the input feature values
+ * @param y the corresponding target values
+ * @return the mean squared error
+ */
+ public double calculateMSE(double[] x, double[] y) {
+ double totalSquaredError = 0;
+ int n = x.length;
+ for (int i = 0; i < n; i++) {
+ double error = predict(x[i]) - y[i];
+ totalSquaredError += error * error;
+ }
+ return totalSquaredError / n;
+ }
+
+ /**
+ * @return the learned slope of the regression line
+ */
+ public double getSlope() {
+ return m;
+ }
+
+ /**
+ * @return the learned y-intercept of the regression line
+ */
+ public double getIntercept() {
+ return b;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java b/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java
new file mode 100644
index 000000000000..43e7e555814c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java
@@ -0,0 +1,143 @@
+package com.thealgorithms.machinelearning;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Multinomial Naive Bayes classifier.
+ *
+ * Suited to discrete, count-based features (e.g. word frequencies in text
+ * classification). Class priors and feature likelihoods are estimated from
+ * training data with Laplace (add-alpha) smoothing to avoid zero
+ * probabilities for unseen feature/class combinations. Predictions are made
+ * by comparing summed log-probabilities across classes, which avoids the
+ * numerical underflow that repeated multiplication of small probabilities
+ * would cause.
+ *
+ * Reference:
+ * Naive Bayes classifier
+ *
+ * @author Vraj Prajapati(Rosander0)
+ */
+public final class MultinomialNaiveBayesClassifier {
+
+ private final double alpha;
+ private final Map logPriors;
+ private final Map logLikelihoods;
+ private int numFeatures;
+
+ /**
+ * Constructs a classifier with the given Laplace smoothing parameter.
+ *
+ * @param alpha smoothing constant; must be greater than 0. A value of 1.0
+ * corresponds to standard Laplace smoothing.
+ */
+ public MultinomialNaiveBayesClassifier(double alpha) {
+ if (alpha <= 0) {
+ throw new IllegalArgumentException("alpha must be greater than 0");
+ }
+ this.alpha = alpha;
+ this.logPriors = new HashMap<>();
+ this.logLikelihoods = new HashMap<>();
+ }
+
+ /** Constructs a classifier using the standard Laplace smoothing constant of 1.0. */
+ public MultinomialNaiveBayesClassifier() {
+ this(1.0);
+ }
+
+ /**
+ * Fits the classifier on the given feature matrix and labels.
+ *
+ * @param features training samples, each row a vector of non-negative
+ * feature counts
+ * @param labels class label for each row of {@code features}
+ */
+ public void fit(double[][] features, int[] labels) {
+ if (features.length == 0 || features.length != labels.length) {
+ throw new IllegalArgumentException("features and labels must be non-empty and of equal length");
+ }
+ logPriors.clear();
+ logLikelihoods.clear();
+ numFeatures = features[0].length;
+
+ Map classCounts = new HashMap<>();
+ Map featureSums = new HashMap<>();
+ Map totalFeatureCount = new HashMap<>();
+
+ for (int i = 0; i < features.length; i++) {
+ int label = labels[i];
+ classCounts.merge(label, 1, Integer::sum);
+ double[] sums = featureSums.computeIfAbsent(label, k -> new double[numFeatures]);
+ double total = totalFeatureCount.getOrDefault(label, 0.0);
+ for (int j = 0; j < numFeatures; j++) {
+ sums[j] += features[i][j];
+ total += features[i][j];
+ }
+ totalFeatureCount.put(label, total);
+ }
+
+ int totalSamples = features.length;
+ for (Map.Entry entry : featureSums.entrySet()) {
+ int label = entry.getKey();
+ double[] sums = entry.getValue();
+ int count = classCounts.getOrDefault(label, 0);
+ double total = totalFeatureCount.getOrDefault(label, 0.0);
+
+ logPriors.put(label, Math.log((double) count / totalSamples));
+
+ double denom = total + alpha * numFeatures;
+ double[] logLikelihood = new double[numFeatures];
+ for (int j = 0; j < numFeatures; j++) {
+ logLikelihood[j] = Math.log((sums[j] + alpha) / denom);
+ }
+ logLikelihoods.put(label, logLikelihood);
+ }
+ }
+
+ /**
+ * Predicts the most likely class for a single sample.
+ *
+ * @param sample feature vector of non-negative counts
+ * @return the predicted class label
+ */
+ public int predict(double[] sample) {
+ if (logPriors.isEmpty()) {
+ throw new IllegalStateException("classifier has not been fitted");
+ }
+ if (sample.length != numFeatures) {
+ throw new IllegalArgumentException("sample length must match training feature count");
+ }
+
+ int bestLabel = -1;
+ double bestScore = Double.NEGATIVE_INFINITY;
+
+ for (Map.Entry entry : logLikelihoods.entrySet()) {
+ int label = entry.getKey();
+ double[] logLikelihood = entry.getValue();
+ double score = logPriors.getOrDefault(label, Double.NEGATIVE_INFINITY);
+ for (int j = 0; j < numFeatures; j++) {
+ score += sample[j] * logLikelihood[j];
+ }
+ if (score > bestScore) {
+ bestScore = score;
+ bestLabel = label;
+ }
+ }
+ return bestLabel;
+ }
+
+ /**
+ * Predicts class labels for a batch of samples.
+ *
+ * @param samples feature vectors of non-negative counts
+ * @return predicted class label for each row of {@code samples}
+ */
+ public int[] predict(double[][] samples) {
+ int[] predictions = new int[samples.length];
+ for (int i = 0; i < samples.length; i++) {
+ predictions[i] = predict(samples[i]);
+ }
+ return predictions;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
index 1b9575a330dd..aab6fe0f426d 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
@@ -1,7 +1,5 @@
package com.thealgorithms.maths;
-import java.util.Arrays;
-
public final class AbsoluteMin {
private AbsoluteMin() {
}
@@ -13,14 +11,17 @@ private AbsoluteMin() {
* @return The absolute min value
*/
public static int getMinValue(int... numbers) {
- if (numbers.length == 0) {
- throw new IllegalArgumentException("Numbers array cannot be empty");
+ if (numbers == null || numbers.length == 0) {
+ throw new IllegalArgumentException("Numbers array cannot be empty or null");
}
- var absMinWrapper = new Object() { int value = numbers[0]; };
-
- Arrays.stream(numbers).skip(1).filter(number -> Math.abs(number) <= Math.abs(absMinWrapper.value)).forEach(number -> absMinWrapper.value = Math.min(absMinWrapper.value, number));
-
- return absMinWrapper.value;
+ long absMin = numbers[0];
+ for (int i = 1; i < numbers.length; i++) {
+ long current = numbers[i];
+ if (Math.abs(current) < Math.abs(absMin) || (Math.abs(current) == Math.abs(absMin) && current < absMin)) {
+ absMin = current;
+ }
+ }
+ return (int) absMin;
}
}
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/BellNumbers.java b/src/main/java/com/thealgorithms/maths/BellNumbers.java
new file mode 100644
index 000000000000..d4dc1014f48b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/BellNumbers.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.maths;
+
+/**
+ * The Bell numbers count the number of partitions of a set.
+ * The n-th Bell number is the number of ways a set of n elements can be partitioned
+ * into nonempty subsets.
+ *
+ *
+ * This implementation uses the Bell Triangle (Aitken's array) method.
+ * Time Complexity: O(n^2)
+ * Space Complexity: O(n^2)
+ *
+ *
+ * @author Chahat Sandhu, singhc7
+ * @see Bell Number (Wikipedia)
+ */
+public final class BellNumbers {
+
+ private BellNumbers() {
+ }
+
+ /**
+ * Calculates the n-th Bell number using the Bell Triangle.
+ *
+ * @param n the index of the Bell number (must be non-negative)
+ * @return the n-th Bell number
+ * @throws IllegalArgumentException if n is negative or n > 25
+ */
+ public static long compute(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be non-negative");
+ }
+ if (n == 0) {
+ return 1;
+ }
+ if (n > 25) {
+ throw new IllegalArgumentException("n must be <= 25. For larger n, use BigInteger implementation.");
+ }
+
+ // We use a 2D array to visualize the Bell Triangle
+ long[][] bellTriangle = new long[n + 1][n + 1];
+
+ // Base case: The triangle starts with 1
+ bellTriangle[0][0] = 1;
+
+ for (int i = 1; i <= n; i++) {
+ // Rule 1: The first number in a new row is the LAST number of the previous row
+ bellTriangle[i][0] = bellTriangle[i - 1][i - 1];
+
+ // Rule 2: Fill the rest of the row by adding the previous neighbor and the upper-left neighbor
+ for (int j = 1; j <= i; j++) {
+ bellTriangle[i][j] = bellTriangle[i][j - 1] + bellTriangle[i - 1][j - 1];
+ }
+ }
+
+ // The Bell number B_n is the first number in the n-th row
+ return bellTriangle[n][0];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/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/DisariumNumber.java b/src/main/java/com/thealgorithms/maths/DisariumNumber.java
new file mode 100644
index 000000000000..0196d0797bcc
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/DisariumNumber.java
@@ -0,0 +1,37 @@
+package com.thealgorithms.maths;
+
+/**
+ * Disarium number is a number where the sum of its digits powered
+ * with their respective positions is equal to the number itself.
+ * Example: 135 = 1^1 + 3^2 + 5^3 = 1 + 9 + 125 = 135
+ *
+ * @see Disarium Number
+ */
+public final class DisariumNumber {
+
+ private DisariumNumber() {
+ }
+
+ /**
+ * Checks if a number is a Disarium number.
+ *
+ * @param number the number to check (must be positive)
+ * @return true if number is Disarium, false otherwise
+ * @throws IllegalArgumentException if number is not positive
+ */
+ public static boolean isDisarium(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Input must be a positive integer.");
+ }
+ int digits = String.valueOf(number).length();
+ int temp = number;
+ int sum = 0;
+ while (temp > 0) {
+ int lastDigit = temp % 10;
+ sum += (int) Math.pow(lastDigit, digits);
+ digits--;
+ temp /= 10;
+ }
+ return sum == number;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java
new file mode 100644
index 000000000000..cd1c9205b328
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.maths;
+
+/**
+ * Distance Between Two Points in 2D Space.
+ *
+ * This class provides a method to calculate the Euclidean distance between two points in a
+ * two-dimensional plane.
+ *
+ * Formula: d = sqrt((x2 - x1)^2 + (y2 - y1)^2)
+ *
+ * Reference: https://en.wikipedia.org/wiki/Euclidean_distance
+ */
+public final class DistanceBetweenTwoPoints {
+
+ private DistanceBetweenTwoPoints() {
+ // Utility class; prevent instantiation
+ }
+
+ /**
+ * Calculate the Euclidean distance between two points.
+ *
+ * @param x1 x-coordinate of the first point
+ * @param y1 y-coordinate of the first point
+ * @param x2 x-coordinate of the second point
+ * @param y2 y-coordinate of the second point
+ * @return Euclidean distance between the two points
+ */
+ public static double calculate(final double x1, final double y1, final double x2, final double y2) {
+ final double deltaX = x2 - x1;
+ final double deltaY = y2 - y1;
+ return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/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/FibonacciJavaStreams.java b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
index 84390860ccc4..8a93580a4b72 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
@@ -6,9 +6,23 @@
import java.util.stream.Stream;
/**
- * @author: caos321
- * @date: 14 October 2021 (Thursday)
+ * Calculates Fibonacci numbers using a functional programming paradigm with Java Streams.
+ *
+ * This specific implementation uses {@link java.util.stream.Stream#iterate} and reductions to generate terms.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * - {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
+ * - {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
+ * - {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
+ * - {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
+ * - {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
+ * * @author caos321
+ * @date 14 October 2021 (Thursday)
*/
+
public final class FibonacciJavaStreams {
private FibonacciJavaStreams() {
}
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
index de23a4305c3f..f19e3a6969c5 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
@@ -1,9 +1,20 @@
package com.thealgorithms.maths;
-
import java.math.BigInteger;
-
/**
* This class provides methods for calculating Fibonacci numbers using BigInteger for large values of 'n'.
+ *
+ * This specific implementation uses an Iterative approach (Loop) with {@code O(n)} time complexity
+ * and {@code O(1)} space complexity.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * - {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
+ * - {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
+ * - {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
+ * - {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
+ * - {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
*/
public final class FibonacciLoop {
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
index 781275d3130d..6bdc2d6ed9fb 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
@@ -4,6 +4,18 @@
* Fibonacci: 0 1 1 2 3 5 8 13 21 ...
* This code checks Fibonacci Numbers up to 45th number.
* Other checks fail because of 'long'-type overflow.
+ *
+ * This class serves as a verification utility rather than a generation algorithm.
+ *
+ * For approaches that actively compute the n-th Fibonacci number, see:
+ *
+ * - {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
+ * - {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
+ * - {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
+ * - {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
+ * - {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
*/
public final class FibonacciNumberCheck {
private FibonacciNumberCheck() {
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
index 4df37a40f541..eca6379b93bc 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
@@ -3,6 +3,18 @@
/**
* This class provides methods for calculating Fibonacci numbers using Binet's formula.
* Binet's formula is based on the golden ratio and allows computing Fibonacci numbers efficiently.
+ *
+ * This specific implementation provides a closed-form solution with an expected {@code O(1)} time complexity.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * - {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
+ * - {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
+ * - {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
+ * - {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
+ * - {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
*
* @see Binet's formula on Wikipedia
*/
diff --git a/src/main/java/com/thealgorithms/maths/FriendlyNumber.java b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java
new file mode 100644
index 000000000000..900ce89295a4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java
@@ -0,0 +1,49 @@
+package com.thealgorithms.maths;
+
+/**
+ * Two numbers are Friendly if they share the same abundancy index,
+ * which is the ratio of the sum of divisors to the number itself.
+ * Example: 6 and 28 are friendly because sigma(6)/6 = 2 and sigma(28)/28 = 2
+ *
+ * @see
+ * Wikipedia: Friendly Number
+ *
+ * @author Vraj Prajapati @Rosander0
+ */
+public final class FriendlyNumber {
+
+ private FriendlyNumber() {
+ // Utility class
+ }
+
+ private static int sumOfDivisors(final int number) {
+ int sum = 0;
+ final int root = (int) Math.sqrt(number);
+ for (int i = 1; i <= root; i++) {
+ if (number % i == 0) {
+ sum += i;
+ final int other = number / i;
+ if (other != i) {
+ sum += other;
+ }
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Checks whether two numbers are Friendly Numbers.
+ *
+ * @param a First number (must be positive)
+ * @param b Second number (must be positive)
+ * @return true if a and b are friendly numbers, false otherwise
+ */
+ public static boolean areFriendly(final int a, final int b) {
+ if (a <= 0 || b <= 0) {
+ return false;
+ }
+ final long sigmaA = sumOfDivisors(a);
+ final long sigmaB = sumOfDivisors(b);
+ return sigmaA * b == sigmaB * a;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java
new file mode 100644
index 000000000000..f4f2e23c3932
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+/**
+ * The Jacobsthal Sequence is a sequence of integers defined by the recurrence relation:
+ * J(n) = J(n-1) + 2*J(n-2) with initial values J(0) = 0, J(1) = 1.
+ * Example: 0, 1, 1, 3, 5, 11, 21, 43, 85, 171, 341...
+ *
+ * @see
+ * Wikipedia: Jacobsthal Number
+ */
+public final class JacobsthalNumber {
+
+ private JacobsthalNumber() {
+ // Utility class
+ }
+
+ /**
+ * Calculates the nth term of the Jacobsthal Sequence.
+ *
+ * @param n the index of the sequence (must be non-negative)
+ * @return the nth term of the Jacobsthal Sequence
+ */
+ public static long jacobsthal(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative!");
+ }
+ if (n == 0) {
+ return 0;
+ }
+ if (n == 1) {
+ return 1;
+ }
+ long a = 0;
+ long b = 1;
+ long result = 0;
+ for (int i = 2; i <= n; i++) {
+ result = b + 2 * a;
+ a = b;
+ b = result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
similarity index 94%
rename from src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
rename to src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
index a3ca8d6f6db8..4a79b4298fc4 100644
--- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
+++ b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.maths;
import java.util.ArrayList;
import java.util.List;
@@ -23,6 +23,12 @@
*
* @see OEIS A016026 - Smallest base in which
* n is palindromic
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
* @author TheAlgorithms Contributors
*/
public final class LowestBasePalindrome {
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/PadovanSequence.java b/src/main/java/com/thealgorithms/maths/PadovanSequence.java
new file mode 100644
index 000000000000..51e7d2441b15
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/PadovanSequence.java
@@ -0,0 +1,43 @@
+package com.thealgorithms.maths;
+
+/**
+ * The Padovan Sequence is a sequence of integers defined by the recurrence relation:
+ * P(n) = P(n-2) + P(n-3) with initial values P(0) = P(1) = P(2) = 1.
+ * Example: 1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37...
+ *
+ * @see
+ * Wikipedia: Padovan Sequence
+ * @author Vraj Prajapati (@Rosander0)
+ */
+public final class PadovanSequence {
+
+ private PadovanSequence() {
+ // Utility class
+ }
+
+ /**
+ * Calculates the nth term of the Padovan Sequence.
+ *
+ * @param n the index of the sequence (must be non-negative)
+ * @return the nth term of the Padovan Sequence
+ */
+ public static long padovan(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative. Received: " + n);
+ }
+ if (n <= 2) {
+ return 1;
+ }
+ long a = 1;
+ long b = 1;
+ long c = 1;
+ long result = 0;
+ for (int i = 3; i <= n; i++) {
+ result = a + b;
+ a = b;
+ b = c;
+ c = result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java
index a22d63897b37..9543f83332a7 100644
--- a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java
+++ b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java
@@ -1,5 +1,16 @@
package com.thealgorithms.maths;
+/**
+ * A class to check if a given number is a palindrome.
+ * A palindromic number is a number that remains the same when its digits are reversed.
+ *
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ */
public final class PalindromeNumber {
private PalindromeNumber() {
}
diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/maths/PalindromePrime.java
similarity index 73%
rename from src/main/java/com/thealgorithms/misc/PalindromePrime.java
rename to src/main/java/com/thealgorithms/maths/PalindromePrime.java
index 164e957a9d12..21b76acefee8 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java
+++ b/src/main/java/com/thealgorithms/maths/PalindromePrime.java
@@ -1,8 +1,19 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.maths;
import java.util.ArrayList;
import java.util.List;
+/**
+ * A class to check and generate palindromic prime numbers.
+ * A palindromic prime is a prime number that is also a palindromic number.
+ *
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromeNumber
+ */
public final class PalindromePrime {
private PalindromePrime() {
}
diff --git a/src/main/java/com/thealgorithms/maths/PerfectSquare.java b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
index e9318bd7d805..aec43062121a 100644
--- a/src/main/java/com/thealgorithms/maths/PerfectSquare.java
+++ b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
@@ -15,6 +15,9 @@ private PerfectSquare() {
* false
*/
public static boolean isPerfectSquare(final int number) {
+ if (number < 0) {
+ return false;
+ }
final int sqrt = (int) Math.sqrt(number);
return sqrt * sqrt == number;
}
@@ -27,6 +30,9 @@ public static boolean isPerfectSquare(final int number) {
* {@code false}
*/
public static boolean isPerfectSquareUsingPow(long number) {
+ if (number < 0) {
+ return false;
+ }
long a = (long) Math.pow(number, 1.0 / 2);
return a * a == number;
}
diff --git a/src/main/java/com/thealgorithms/maths/PerrinNumber.java b/src/main/java/com/thealgorithms/maths/PerrinNumber.java
new file mode 100644
index 000000000000..cee45a1c5538
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/PerrinNumber.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+/**
+ * The Perrin Sequence is a sequence of integers defined by the recurrence relation:
+ * P(n) = P(n-2) + P(n-3) with initial values P(0) = 3, P(1) = 0, P(2) = 2.
+ * Example: 3, 0, 2, 3, 2, 5, 5, 7, 10, 12, 17, 22, 29, 39, 51...
+ *
+ * Note: The Perrin Sequence uses the same recurrence relation as the Padovan Sequence
+ * but has different initial values.
+ *
+ * @see
+ * Wikipedia: Perrin Number
+ * @see PadovanSequence
+ */
+public final class PerrinNumber {
+
+ private PerrinNumber() {
+ // Utility class
+ }
+
+ /**
+ * Calculates the nth term of the Perrin Sequence.
+ *
+ * @param n the index of the sequence (must be non-negative)
+ * @return the nth term of the Perrin Sequence
+ */
+ public static long perrin(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative!");
+ }
+ if (n == 0) {
+ return 3;
+ }
+ if (n == 1) {
+ return 0;
+ }
+ if (n == 2) {
+ return 2;
+ }
+ long a = 3;
+ long b = 0;
+ long c = 2;
+ long result = 0;
+ for (int i = 3; i <= n; i++) {
+ result = a + b;
+ a = b;
+ b = c;
+ c = result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
index 3d4e4eff0f03..ec1785a916c7 100644
--- a/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
@@ -31,27 +31,28 @@ public static int mobius(int number) {
throw new IllegalArgumentException("Number must be greater than zero.");
}
- if (number == 1) {
- // return 1 if number passed is less or is 1
- return 1;
- }
-
int primeFactorCount = 0;
+ int remaining = number;
- for (int i = 1; i <= number; i++) {
- // find prime factors of number
- if (number % i == 0 && PrimeCheck.isPrime(i)) {
- // check if number is divisible by square of prime factor
- if (number % (i * i) == 0) {
- // if number is divisible by square of prime factor
+ /* Divide out every prime factor in turn. Trial division only has to run up to the square
+ root of the remaining value, and the multiplication is widened to long so that the bound
+ does not overflow for numbers close to Integer.MAX_VALUE. */
+ for (int factor = 2; (long) factor * factor <= remaining; factor++) {
+ if (remaining % factor == 0) {
+ remaining /= factor;
+ if (remaining % factor == 0) {
+ // number is divisible by the square of this prime factor
return 0;
}
- /*increment primeFactorCount by 1
- if number is not divisible by square of found prime factor*/
primeFactorCount++;
}
}
+ /* Whatever is left is either 1 or a single prime factor larger than the square root. */
+ if (remaining > 1) {
+ primeFactorCount++;
+ }
+
return (primeFactorCount % 2 == 0) ? 1 : -1;
}
}
diff --git a/src/main/java/com/thealgorithms/maths/SociableNumber.java b/src/main/java/com/thealgorithms/maths/SociableNumber.java
new file mode 100644
index 000000000000..9ce644e61dfc
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/SociableNumber.java
@@ -0,0 +1,67 @@
+package com.thealgorithms.maths;
+
+/**
+ * Sociable numbers are natural numbers that form a cyclic sequence where the
+ * sum of proper divisors of each number equals the next number in the sequence,
+ * with the sequence eventually returning to the starting number.
+ * Amicable numbers are a special case of sociable numbers with a cycle length of 2.
+ * Example: (12496, 14288, 15472, 14536, 14264) is a sociable cycle of length 5.
+ *
+ * @author Vraj Prajapati (@Rosander0)
+ * @see Wikipedia: Sociable Number
+ * @see AmicableNumber
+ */
+public final class SociableNumber {
+
+ private SociableNumber() {
+ // Utility class
+ }
+
+ /**
+ * Calculates the sum of proper divisors of a number
+ * (all divisors excluding the number itself).
+ *
+ * @param number the number to calculate proper divisors sum for
+ * @return sum of proper divisors, or 0 if number is less than or equal to 1
+ */
+ static int sumOfProperDivisors(final int number) {
+ if (number <= 1) {
+ return 0;
+ }
+ int sum = 1; // 1 is a proper divisor of every number > 1
+ final int root = (int) Math.sqrt(number);
+ for (int i = 2; i <= root; i++) {
+ if (number % i == 0) {
+ final int other = number / i;
+ sum += i;
+ if (other != i) {
+ sum += other;
+ }
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Checks whether a number is part of a sociable cycle of a given length.
+ * Starting from the given number, it follows the chain of proper divisor
+ * sums and checks if it returns to the starting number in exactly cycleLength steps.
+ *
+ * @param number the starting number (must be positive)
+ * @param cycleLength the expected cycle length (must be greater than 1)
+ * @return true if the number is part of a sociable cycle of given length, false otherwise
+ */
+ public static boolean isSociable(final int number, final int cycleLength) {
+ if (number <= 0 || cycleLength <= 1) {
+ return false;
+ }
+ int current = number;
+ for (int i = 0; i < cycleLength; i++) {
+ current = sumOfProperDivisors(current);
+ if (current == number) {
+ return i == cycleLength - 1;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/SumOfSquares.java b/src/main/java/com/thealgorithms/maths/SumOfSquares.java
index c050d5a75f7b..77acbcc2a609 100644
--- a/src/main/java/com/thealgorithms/maths/SumOfSquares.java
+++ b/src/main/java/com/thealgorithms/maths/SumOfSquares.java
@@ -5,7 +5,6 @@
* Find minimum number of perfect squares that sum to given number
*
* @see Lagrange's Four Square Theorem
- * @author BEASTSHRIRAM
*/
public final class SumOfSquares {
@@ -16,10 +15,15 @@ private SumOfSquares() {
/**
* Find minimum number of perfect squares that sum to n
*
- * @param n the target number
+ * @param n the target number (must be non-negative)
* @return minimum number of squares needed
+ * @throws IllegalArgumentException if n is negative
*/
public static int minSquares(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative");
+ }
+
if (isPerfectSquare(n)) {
return 1;
}
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/matrix/QRDecomposition.java b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java
new file mode 100644
index 000000000000..45f56bc14729
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java
@@ -0,0 +1,149 @@
+package com.thealgorithms.matrix;
+
+/**
+ * @brief Implementation of QR Decomposition using the Gram-Schmidt process
+ * @details Decomposes a matrix A into an orthogonal matrix Q and an upper
+ * triangular matrix R such that A = Q * R. The Gram-Schmidt process
+ * orthogonalizes the columns of A to produce Q, and R is computed as Q^T * A.
+ * This decomposition is useful for solving linear least squares problems,
+ * eigenvalue computations, and numerical stability in linear algebra.
+ * @see QR Decomposition
+ */
+public final class QRDecomposition {
+
+ private QRDecomposition() {
+ }
+
+ /**
+ * A helper class to store both Q and R matrices
+ */
+ public static class QR {
+ private final double[][] q;
+ private final double[][] r;
+
+ QR(double[][] q, double[][] r) {
+ this.q = q;
+ this.r = r;
+ }
+
+ public double[][] getQ() {
+ return q;
+ }
+
+ public double[][] getR() {
+ return r;
+ }
+ }
+
+ /**
+ * @brief Performs QR decomposition on a matrix using the Gram-Schmidt process
+ * @param matrix the input matrix (m x n)
+ * @return QR object containing orthogonal matrix Q (m x n) and upper triangular matrix R (n x n)
+ * @throws IllegalArgumentException if the matrix is null, empty, or has invalid rows
+ */
+ public static QR decompose(double[][] matrix) {
+ validateInputMatrix(matrix);
+
+ int m = matrix.length;
+ int n = matrix[0].length;
+
+ double[][] q = new double[m][n];
+ double[][] r = new double[n][n];
+
+ for (int j = 0; j < n; j++) {
+ double[] v = getColumn(matrix, j);
+
+ for (int i = 0; i < j; i++) {
+ double[] qi = getColumn(q, i);
+ r[i][j] = dotProduct(qi, v);
+ v = subtractVectors(v, scalarMultiply(qi, r[i][j]));
+ }
+
+ r[j][j] = norm(v);
+ if (r[j][j] == 0) {
+ throw new ArithmeticException("Matrix is rank deficient. Cannot perform QR decomposition.");
+ }
+ double[] qj = scalarMultiply(v, 1.0 / r[j][j]);
+ setColumn(q, j, qj);
+ }
+
+ return new QR(q, r);
+ }
+
+ private static double[] getColumn(double[][] matrix, int col) {
+ int m = matrix.length;
+ double[] column = new double[m];
+ for (int i = 0; i < m; i++) {
+ column[i] = matrix[i][col];
+ }
+ return column;
+ }
+
+ private static void setColumn(double[][] matrix, int col, double[] column) {
+ for (int i = 0; i < matrix.length; i++) {
+ matrix[i][col] = column[i];
+ }
+ }
+
+ private static double dotProduct(double[] a, double[] b) {
+ double sum = 0;
+ for (int i = 0; i < a.length; i++) {
+ sum += a[i] * b[i];
+ }
+ return sum;
+ }
+
+ private static double[] subtractVectors(double[] a, double[] b) {
+ double[] result = new double[a.length];
+ for (int i = 0; i < a.length; i++) {
+ result[i] = a[i] - b[i];
+ }
+ return result;
+ }
+
+ private static double[] scalarMultiply(double[] v, double scalar) {
+ double[] result = new double[v.length];
+ for (int i = 0; i < v.length; i++) {
+ result[i] = v[i] * scalar;
+ }
+ return result;
+ }
+
+ private static double norm(double[] v) {
+ return Math.sqrt(dotProduct(v, v));
+ }
+
+ private static void validateInputMatrix(double[][] matrix) {
+ if (matrix == null) {
+ throw new IllegalArgumentException("The input matrix cannot be null");
+ }
+ if (matrix.length == 0) {
+ throw new IllegalArgumentException("The input matrix cannot be empty");
+ }
+ if (!hasValidRows(matrix)) {
+ throw new IllegalArgumentException("The input matrix cannot have null or empty rows");
+ }
+ if (isJaggedMatrix(matrix)) {
+ throw new IllegalArgumentException("The input matrix cannot be jagged");
+ }
+ }
+
+ private static boolean hasValidRows(double[][] matrix) {
+ for (double[] row : matrix) {
+ if (row == null || row.length == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isJaggedMatrix(double[][] matrix) {
+ int numColumns = matrix[0].length;
+ for (double[] row : matrix) {
+ if (row.length != numColumns) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/Dijkstra.java b/src/main/java/com/thealgorithms/others/Dijkstra.java
deleted file mode 100644
index a379100a2f3b..000000000000
--- a/src/main/java/com/thealgorithms/others/Dijkstra.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package com.thealgorithms.others;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.NavigableSet;
-import java.util.TreeSet;
-/**
- * Dijkstra's algorithm,is a graph search algorithm that solves the
- * single-source shortest path problem for a graph with nonnegative edge path
- * costs, producing a shortest path tree.
- *
- *
- * NOTE: The inputs to Dijkstra's algorithm are a directed and weighted graph
- * consisting of 2 or more nodes, generally represented by an adjacency matrix
- * or list, and a start node.
- *
- *
- * Original source of code:
- * https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Java Also most of the
- * comments are from RosettaCode.
- */
-public final class Dijkstra {
- private Dijkstra() {
- }
-
- private static final Graph.Edge[] GRAPH = {
- // Distance from node "a" to node "b" is 7.
- // In the current Graph there is no way to move the other way (e,g, from "b" to "a"),
- // a new edge would be needed for that
- new Graph.Edge("a", "b", 7),
- new Graph.Edge("a", "c", 9),
- new Graph.Edge("a", "f", 14),
- new Graph.Edge("b", "c", 10),
- new Graph.Edge("b", "d", 15),
- new Graph.Edge("c", "d", 11),
- new Graph.Edge("c", "f", 2),
- new Graph.Edge("d", "e", 6),
- new Graph.Edge("e", "f", 9),
- };
- private static final String START = "a";
- private static final String END = "e";
-
- /**
- * main function Will run the code with "GRAPH" that was defined above.
- */
- public static void main(String[] args) {
- Graph g = new Graph(GRAPH);
- g.dijkstra(START);
- g.printPath(END);
- // g.printAllPaths();
- }
-}
-
-class Graph {
-
- // mapping of vertex names to Vertex objects, built from a set of Edges
-
- private final Map graph;
-
- /**
- * One edge of the graph (only used by Graph constructor)
- */
- public static class Edge {
-
- public final String v1;
- public final String v2;
- public final int dist;
-
- Edge(String v1, String v2, int dist) {
- this.v1 = v1;
- this.v2 = v2;
- this.dist = dist;
- }
- }
-
- /**
- * One vertex of the graph, complete with mappings to neighbouring vertices
- */
- public static class Vertex implements Comparable {
-
- public final String name;
- // MAX_VALUE assumed to be infinity
- public int dist = Integer.MAX_VALUE;
- public Vertex previous = null;
- public final Map neighbours = new HashMap<>();
-
- Vertex(String name) {
- this.name = name;
- }
-
- private void printPath() {
- if (this == this.previous) {
- System.out.printf("%s", this.name);
- } else if (this.previous == null) {
- System.out.printf("%s(unreached)", this.name);
- } else {
- this.previous.printPath();
- System.out.printf(" -> %s(%d)", this.name, this.dist);
- }
- }
-
- public int compareTo(Vertex other) {
- if (dist == other.dist) {
- return name.compareTo(other.name);
- }
-
- return Integer.compare(dist, other.dist);
- }
-
- @Override
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (object == null || getClass() != object.getClass()) {
- return false;
- }
- if (!super.equals(object)) {
- return false;
- }
-
- Vertex vertex = (Vertex) object;
-
- if (dist != vertex.dist) {
- return false;
- }
- if (name != null ? !name.equals(vertex.name) : vertex.name != null) {
- return false;
- }
- if (previous != null ? !previous.equals(vertex.previous) : vertex.previous != null) {
- return false;
- }
- return neighbours != null ? neighbours.equals(vertex.neighbours) : vertex.neighbours == null;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- result = 31 * result + (name != null ? name.hashCode() : 0);
- result = 31 * result + dist;
- result = 31 * result + (previous != null ? previous.hashCode() : 0);
- result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "(" + name + ", " + dist + ")";
- }
- }
-
- /**
- * Builds a graph from a set of edges
- */
- Graph(Edge[] edges) {
- graph = new HashMap<>(edges.length);
-
- // one pass to find all vertices
- for (Edge e : edges) {
- if (!graph.containsKey(e.v1)) {
- graph.put(e.v1, new Vertex(e.v1));
- }
- if (!graph.containsKey(e.v2)) {
- graph.put(e.v2, new Vertex(e.v2));
- }
- }
-
- // another pass to set neighbouring vertices
- for (Edge e : edges) {
- graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
- // graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an
- // undirected graph
- }
- }
-
- /**
- * Runs dijkstra using a specified source vertex
- */
- public void dijkstra(String startName) {
- if (!graph.containsKey(startName)) {
- System.err.printf("Graph doesn't contain start vertex \"%s\"%n", startName);
- return;
- }
- final Vertex source = graph.get(startName);
- NavigableSet q = new TreeSet<>();
-
- // set-up vertices
- for (Vertex v : graph.values()) {
- v.previous = v == source ? source : null;
- v.dist = v == source ? 0 : Integer.MAX_VALUE;
- q.add(v);
- }
-
- dijkstra(q);
- }
-
- /**
- * Implementation of dijkstra's algorithm using a binary heap.
- */
- private void dijkstra(final NavigableSet q) {
- Vertex u;
- Vertex v;
- while (!q.isEmpty()) {
- // vertex with shortest distance (first iteration will return source)
- u = q.pollFirst();
- if (u.dist == Integer.MAX_VALUE) {
- break; // we can ignore u (and any other remaining vertices) since they are
- // unreachable
- }
- // look at distances to each neighbour
- for (Map.Entry a : u.neighbours.entrySet()) {
- v = a.getKey(); // the neighbour in this iteration
-
- final int alternateDist = u.dist + a.getValue();
- if (alternateDist < v.dist) { // shorter path to neighbour found
- q.remove(v);
- v.dist = alternateDist;
- v.previous = u;
- q.add(v);
- }
- }
- }
- }
-
- /**
- * Prints a path from the source to the specified vertex
- */
- public void printPath(String endName) {
- if (!graph.containsKey(endName)) {
- System.err.printf("Graph doesn't contain end vertex \"%s\"%n", endName);
- return;
- }
-
- graph.get(endName).printPath();
- System.out.println();
- }
-
- /**
- * Prints the path from the source to every vertex (output order is not
- * guaranteed)
- */
- public void printAllPaths() {
- for (Vertex v : graph.values()) {
- v.printPath();
- System.out.println();
- }
- }
-}
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/DifferenceArray.java b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java
new file mode 100644
index 000000000000..1be55039cff0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java
@@ -0,0 +1,87 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * Implements the Difference Array algorithm.
+ *
+ *
+ * The Difference Array is an auxiliary data structure that enables efficient range update operations.
+ * It is based on the mathematical concept of Finite Differences.
+ *
+ *
+ *
+ * Key Operations:
+ *
+ * - Range Update (Add value to [L, R]): O(1)
+ * - Reconstruction (Prefix Sum): O(N)
+ *
+ *
+ *
+ * @see Finite Difference (Wikipedia)
+ * @see Prefix Sum (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class DifferenceArray {
+
+ private final long[] differenceArray;
+ private final int n;
+
+ /**
+ * Initializes the Difference Array from a given integer array.
+ *
+ * @param inputArray The initial array. Cannot be null or empty.
+ * @throws IllegalArgumentException if the input array is null or empty.
+ */
+ public DifferenceArray(int[] inputArray) {
+ if (inputArray == null || inputArray.length == 0) {
+ throw new IllegalArgumentException("Input array cannot be null or empty.");
+ }
+ this.n = inputArray.length;
+ // Size n + 1 allows for branchless updates at the right boundary (r + 1).
+ this.differenceArray = new long[n + 1];
+ initializeDifferenceArray(inputArray);
+ }
+
+ private void initializeDifferenceArray(int[] inputArray) {
+ differenceArray[0] = inputArray[0];
+ for (int i = 1; i < n; i++) {
+ differenceArray[i] = inputArray[i] - inputArray[i - 1];
+ }
+ }
+
+ /**
+ * Adds a value to all elements in the range [l, r].
+ *
+ *
+ * This method uses a branchless approach by allocating an extra element at the end
+ * of the array, avoiding the conditional check for the right boundary.
+ *
+ *
+ * @param l The starting index (inclusive).
+ * @param r The ending index (inclusive).
+ * @param val The value to add.
+ * @throws IllegalArgumentException if the range is invalid.
+ */
+ public void update(int l, int r, int val) {
+ if (l < 0 || r >= n || l > r) {
+ throw new IllegalArgumentException(String.format("Invalid range: [%d, %d] for array of size %d", l, r, n));
+ }
+
+ differenceArray[l] += val;
+ differenceArray[r + 1] -= val;
+ }
+
+ /**
+ * Reconstructs the final array using prefix sums.
+ *
+ * @return The resulting array after all updates. Returns long[] to handle potential overflows.
+ */
+ public long[] getResultArray() {
+ long[] result = new long[n];
+ result[0] = differenceArray[0];
+
+ for (int i = 1; i < n; i++) {
+ result[i] = differenceArray[i] + result[i - 1];
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java
new file mode 100644
index 000000000000..47f6366e2924
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * A class that implements the Prefix Sum algorithm.
+ *
+ * Prefix Sum is a technique used to preprocess an array such that
+ * range sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N) time.
+ *
+ * This implementation uses a long array for the prefix sums to prevent
+ * integer overflow when the sum of elements exceeds Integer.MAX_VALUE.
+ *
+ * @see Prefix Sum (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum {
+
+ private final long[] prefixSums;
+
+ /**
+ * Constructor to preprocess the input array.
+ *
+ * @param array The input integer array.
+ * @throws IllegalArgumentException if the array is null.
+ */
+ public PrefixSum(int[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("Input array cannot be null");
+ }
+ this.prefixSums = new long[array.length + 1];
+ this.prefixSums[0] = 0;
+
+ for (int i = 0; i < array.length; i++) {
+ // Automatically promotes int to long during addition
+ this.prefixSums[i + 1] = this.prefixSums[i] + array[i];
+ }
+ }
+
+ /**
+ * Calculates the sum of elements in the range [left, right].
+ * Indices are 0-based.
+ *
+ * @param left The starting index (inclusive).
+ * @param right The ending index (inclusive).
+ * @return The sum of elements from index left to right as a long.
+ * @throws IndexOutOfBoundsException if indices are out of valid range.
+ */
+ public long sumRange(int left, int right) {
+ if (left < 0 || right >= prefixSums.length - 1 || left > right) {
+ throw new IndexOutOfBoundsException("Invalid range indices");
+ }
+ return prefixSums[right + 1] - prefixSums[left];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
new file mode 100644
index 000000000000..9c168bc6bcc4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * A class that implements the 2D Prefix Sum algorithm.
+ *
+ * 2D Prefix Sum is a technique used to preprocess a 2D matrix such that
+ * sub-matrix sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N*M) time.
+ *
+ * This implementation uses a long array for the prefix sums to prevent
+ * integer overflow.
+ *
+ * @see Summed-area table (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum2D {
+
+ private final long[][] prefixSums;
+
+ /**
+ * Constructor to preprocess the input matrix.
+ *
+ * @param matrix The input integer matrix.
+ * @throws IllegalArgumentException if the matrix is null or empty.
+ */
+ public PrefixSum2D(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
+ throw new IllegalArgumentException("Input matrix cannot be null or empty");
+ }
+
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ this.prefixSums = new long[rows + 1][cols + 1];
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ // P[i+1][j+1] = current + above + left - diagonal_overlap
+ this.prefixSums[i + 1][j + 1] = matrix[i][j] + this.prefixSums[i][j + 1] + this.prefixSums[i + 1][j] - this.prefixSums[i][j];
+ }
+ }
+ }
+
+ /**
+ * Calculates the sum of the sub-matrix defined by (row1, col1) to (row2, col2).
+ * Indices are 0-based.
+ *
+ * @param row1 Top row index.
+ * @param col1 Left column index.
+ * @param row2 Bottom row index.
+ * @param col2 Right column index.
+ * @return The sum of the sub-matrix.
+ * @throws IndexOutOfBoundsException if indices are invalid.
+ */
+ public long sumRegion(int row1, int col1, int row2, int col2) {
+ if (row1 < 0 || row2 >= prefixSums.length - 1 || row2 < row1) {
+ throw new IndexOutOfBoundsException("Invalid row indices");
+ }
+ if (col1 < 0 || col2 >= prefixSums[0].length - 1 || col2 < col1) {
+ throw new IndexOutOfBoundsException("Invalid column indices");
+ }
+
+ return prefixSums[row2 + 1][col2 + 1] - prefixSums[row1][col2 + 1] - prefixSums[row2 + 1][col1] + prefixSums[row1][col1];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/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/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
index 9bc6da2f7443..404adfdf0cf1 100644
--- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
+++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
@@ -1,16 +1,36 @@
package com.thealgorithms.recursion;
-/*
- The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones,
- starting with 0 and 1.
- NUMBER 0 1 2 3 4 5 6 7 8 9 10 ...
- FIBONACCI 0 1 1 2 3 5 8 13 21 34 55 ...
-*/
-
+/**
+ * The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones,
+ * starting with 0 and 1.
+ *
+ * Example:
+ * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...
+ *
+ *
+ * This specific implementation demonstrates a Naive Recursive approach with {@code O(2^n)} time complexity.
+ * For more performant variations or different programming paradigms, see:
+ *
+ * - {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
+ * - {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming variants (Memoization / Bottom-Up)
+ * - {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
+ * - {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
+ * - {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
+ * - {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
+ *
+ */
public final class FibonacciSeries {
private FibonacciSeries() {
throw new UnsupportedOperationException("Utility class");
}
+
+ /**
+ * Calculates the nth term in the Fibonacci sequence using recursion.
+ *
+ * @param n the position in the Fibonacci sequence (must be non-negative)
+ * @return the nth Fibonacci number
+ * @throws IllegalArgumentException if n is negative
+ */
public static int fibonacci(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be a non-negative integer");
diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java
index bedad1667f33..ca873fc6eafa 100644
--- a/src/main/java/com/thealgorithms/searches/BinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java
@@ -3,12 +3,32 @@
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
+ * Binary Search Algorithm Implementation
*
- *
- * Worst-case performance O(log n) Best-case performance O(1) Average
- * performance O(log n) Worst-case space complexity O(1)
+ * Binary search is one of the most efficient searching algorithms for finding a target element
+ * in a SORTED array. It works by repeatedly dividing the search space in half, eliminating half of
+ * the remaining elements in each step.
+ *
+ * IMPORTANT: This algorithm ONLY works correctly if the input array is sorted in ascending
+ * order.
+ *
+ * Algorithm Overview: 1. Start with the entire array (left = 0, right = array.length - 1) 2.
+ * Calculate the middle index 3. Compare the middle element with the target: - If middle element
+ * equals target: Found! Return the index - If middle element is less than target: Search the right
+ * half - If middle element is greater than target: Search the left half 4. Repeat until element is
+ * found or search space is exhausted
+ *
+ * Performance Analysis: - Best-case time complexity: O(1) - Element found at middle on first
+ * try - Average-case time complexity: O(log n) - Most common scenario - Worst-case time
+ * complexity: O(log n) - Element not found or at extreme end - Space complexity: O(1) - Only uses
+ * a constant amount of extra space
+ *
+ * Example Walkthrough: Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] Target: 7
+ *
+ * Step 1: left=0, right=9, mid=4, array[4]=9 (9 > 7, search left half) Step 2: left=0,
+ * right=3, mid=1, array[1]=3 (3 < 7, search right half) Step 3: left=2, right=3, mid=2,
+ * array[2]=5 (5 < 7, search right half) Step 4: left=3, right=3, mid=3, array[3]=7 (Found!
+ * Return index 3)
*
* @author Varun Upadhyay (https://github.com/varunu28)
* @author Podshivalov Nikita (https://github.com/nikitap492)
@@ -18,38 +38,96 @@
class BinarySearch 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
+ * Generic method to perform binary search on any comparable type. This is the main entry point
+ * for binary search operations.
+ *
+ * Example Usage:
+ *
+ * Integer[] numbers = {1, 3, 5, 7, 9, 11};
+ * int result = new BinarySearch().find(numbers, 7);
+ * // result will be 3 (index of element 7)
+ *
+ * int notFound = new BinarySearch().find(numbers, 4);
+ * // notFound will be -1 (element 4 does not exist)
+ *
+ *
+ * @param The type of elements in the array (must be Comparable)
+ * @param array The sorted array to search in (MUST be sorted in ascending order)
+ * @param key The element to search for
+ * @return The index of the key if found, -1 if not found or if array is null/empty
*/
@Override
public > int find(T[] array, T key) {
+ // 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);
}
/**
- * This method implements the Generic Binary Search
+ * Core recursive implementation of binary search algorithm. This method divides the problem
+ * into smaller subproblems recursively.
+ *
+ * How it works:
+ *
+ * - Calculate the middle index to avoid integer overflow
+ * - Check if middle element matches the target
+ * - If not, recursively search either left or right half
+ * - Base case: left > right means element not found
+ *
+ *
+ * Time Complexity: O(log n) because we halve the search space each time.
+ * Space Complexity: O(log n) due to recursive call stack.
*
- * @param array The array to make the binary search
- * @param key The number you are looking for
- * @param left The lower bound
- * @param right The upper bound
- * @return the location of the key
+ * @param The type of elements (must be Comparable)
+ * @param array The sorted array to search in
+ * @param key The element we're looking for
+ * @param left The leftmost index of current search range (inclusive)
+ * @param right The rightmost index of current search range (inclusive)
+ * @return The index where key is located, or -1 if not found
*/
private > int search(T[] array, T key, int left, int right) {
+ // Base case: Search space is exhausted
+ // This happens when left pointer crosses right pointer
if (right < left) {
- return -1; // this means that the key not found
+ return -1; // Key not found in the array
}
- // find median
- int median = (left + right) >>> 1;
+
+ // Calculate middle index
+ // Using (left + right) / 2 could cause integer overflow for large arrays
+ // So we use: left + (right - left) / 2 which is mathematically equivalent
+ // but prevents overflow
+ int median = (left + right) >>> 1; // Unsigned right shift is faster division by 2
+
+ // Get the value at middle position for comparison
int comp = key.compareTo(array[median]);
+ // Case 1: Found the target element at middle position
if (comp == 0) {
- return median;
- } else if (comp < 0) {
+ return median; // Return the index where element was found
+ }
+ // Case 2: Target is smaller than middle element
+ // This means if target exists, it must be in the LEFT half
+ else if (comp < 0) {
+ // Recursively search the left half
+ // New search range: [left, median - 1]
return search(array, key, left, median - 1);
- } else {
+ }
+ // Case 3: Target is greater than middle element
+ // This means if target exists, it must be in the RIGHT half
+ else {
+ // Recursively search the right half
+ // New search range: [median + 1, right]
return search(array, key, median + 1, right);
}
}
diff --git a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
index 9187dcbc2f4b..e666b9148aaa 100644
--- a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
+++ b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
@@ -46,6 +46,9 @@ public > int find(T[] array, T key) {
range = range * 2;
}
- return Arrays.binarySearch(array, range / 2, Math.min(range, array.length), key);
+ // The candidate block is the inclusive index range [range / 2, range], so the
+ // exclusive upper bound handed to binarySearch has to be range + 1.
+ final int index = Arrays.binarySearch(array, range / 2, Math.min(range + 1, array.length), key);
+ return index >= 0 ? index : -1;
}
}
diff --git a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
index 78dac0f0a712..fa91cd14a1af 100644
--- a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
+++ b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
@@ -69,7 +69,7 @@ public > int find(T[] array, T key) {
}
}
- if (fibMinus1 == 1 && array[offset + 1] == key) {
+ if (fibMinus1 == 1 && offset + 1 < n && array[offset + 1].compareTo(key) == 0) {
return offset + 1;
}
diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
index 3ac6be25bf53..52dbc0b7e2c5 100644
--- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
+++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
@@ -1,3 +1,4 @@
+
package com.thealgorithms.searches;
/**
@@ -35,9 +36,12 @@ public int find(int[] array, int key) {
// Since array is sorted, an element present
// in array must be in range defined by corner
while (start <= end && key >= array[start] && key <= array[end]) {
+ if (array[start] == array[end]) {
+ return start;
+ }
// Probing the position with keeping
// uniform distribution in mind.
- int pos = start + (((end - start) / (array[end] - array[start])) * (key - array[start]));
+ int pos = start + (int) (((long) (end - start) * (key - array[start])) / ((long) array[end] - array[start]));
// Condition of target found
if (array[pos] == key) {
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..4253f4182db1 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
@@ -42,7 +73,7 @@ public > int find(T[] array, T key) {
int limit = blockSize;
// Jumping ahead to find the block where the key may be located
while (limit < length && key.compareTo(array[limit]) > 0) {
- limit = Math.min(limit + blockSize, length - 1);
+ limit += blockSize;
}
// Perform linear search within the identified block
diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java
index c7b70edb5112..c5f6e6ba9776 100644
--- a/src/main/java/com/thealgorithms/searches/LinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java
@@ -1,37 +1,75 @@
+
package com.thealgorithms.searches;
import com.thealgorithms.devutils.searches.SearchAlgorithm;
/**
- * Linear search is the easiest search algorithm It works with sorted and
- * unsorted arrays (an binary search works only with sorted array) This
- * algorithm just compares all elements of an array to find a value
+ * Linear Search is a simple searching algorithm that checks
+ * each element of the array sequentially until the target
+ * value is found or the array ends.
+ *
+ * It works for both sorted and unsorted arrays.
+ *
+ * How it works step-by-step:
+ *
+ * - Start from the first element of the array.
+ * - Compare the current element with the target value.
+ * - If they match, return the current index.
+ * - If they don't match, move to the next element.
+ * - Repeat until the element is found or the array ends.
+ * - If not found, return -1.
+ *
+ *
+ * Example:
+ *
+ * Input array: [5, 3, 8, 1, 9]
+ * Target: 8
+ *
+ * Step 1: Compare 5 with 8 β no match, move on
+ * Step 2: Compare 3 with 8 β no match, move on
+ * Step 3: Compare 8 with 8 β match found at index 2!
+ *
+ * Output: 2
*
- *
- * Worst-case performance O(n) Best-case performance O(1) Average performance
- * O(n) Worst-case space complexity
+ * If target = 7:
+ * Output: -1 (not found)
+ *
+ * Time Complexity:
+ * - Best case: O(1) - target is the first element
+ * - Average case: O(n) - target is somewhere in the middle
+ * - Worst case: O(n) - target is last or not present
*
- * @author Varun Upadhyay (https://github.com/varunu28)
- * @author Podshivalov Nikita (https://github.com/nikitap492)
+ * Space Complexity: O(1)
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see BinarySearch
* @see SearchAlgorithm
*/
public class LinearSearch implements SearchAlgorithm {
/**
- * Generic Linear search method
+ * Generic Linear search method that searches for a value
+ * in the given array by checking each element one by one.
*
- * @param array List to be searched
+ * @param array List to be searched (can be unsorted)
* @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/RotatedBinarySearch.java b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java
new file mode 100644
index 000000000000..86099b2fa2fa
--- /dev/null
+++ b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.searches;
+
+import com.thealgorithms.devutils.searches.SearchAlgorithm;
+
+/**
+ * Searches for a key in a sorted array that has been rotated at an unknown pivot.
+ *
+ *
+ * Example:
+ * {@code [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]}
+ *
+ *
+ * This is a modified binary search. When the array contains no duplicates, the
+ * time complexity is {@code O(log n)}. With duplicates, the algorithm still
+ * works but may degrade to {@code O(n)} in the worst case.
+ *
+ * @see Search in rotated sorted array
+ * @see SearchAlgorithm
+ */
+public final class RotatedBinarySearch implements SearchAlgorithm {
+
+ @Override
+ public > int find(T[] array, T key) {
+ int left = 0;
+ int right = array.length - 1;
+
+ while (left <= right) {
+ int middle = (left + right) >>> 1;
+ int cmp = key.compareTo(array[middle]);
+ if (cmp == 0) {
+ return middle;
+ }
+
+ // Handle duplicates: if we cannot determine which side is sorted.
+ if (array[left].compareTo(array[middle]) == 0 && array[middle].compareTo(array[right]) == 0) {
+ left++;
+ right--;
+ continue;
+ }
+
+ // Left half is sorted.
+ if (array[left].compareTo(array[middle]) <= 0) {
+ if (array[left].compareTo(key) <= 0 && key.compareTo(array[middle]) < 0) {
+ right = middle - 1;
+ } else {
+ left = middle + 1;
+ }
+ } else {
+ // Right half is sorted.
+ if (array[middle].compareTo(key) < 0 && key.compareTo(array[right]) <= 0) {
+ left = middle + 1;
+ } else {
+ right = middle - 1;
+ }
+ }
+ }
+
+ return -1;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
index 1a5903a5d134..473fc2c3f094 100644
--- a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
@@ -65,7 +65,8 @@ public > int find(T[] array, T key) {
int i = 0;
// Search without bound checking since sentinel guarantees we'll find the key
- while (array[i].compareTo(key) != 0) {
+ // Null check for array element to prevent NPE when array contains null elements
+ while (array[i] != null && array[i].compareTo(key) != 0) {
i++;
}
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/slidingwindow/CountDistinctElementsInWindow.java b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java
new file mode 100644
index 000000000000..19e573437f6d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.slidingwindow;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Counts the number of distinct elements in every window of size k.
+ *
+ * @see Reference
+ */
+public final class CountDistinctElementsInWindow {
+
+ private CountDistinctElementsInWindow() {
+ }
+
+ /**
+ * Returns an array where each element is the count of distinct
+ * elements in the corresponding window of size k.
+ *
+ * @param arr the input array
+ * @param k the window size
+ * @return array of distinct element counts per window
+ */
+ public static int[] countDistinct(int[] arr, int k) {
+ if (arr == null || arr.length == 0 || k <= 0 || k > arr.length) {
+ throw new IllegalArgumentException("Invalid input");
+ }
+
+ int n = arr.length;
+ int[] result = new int[n - k + 1];
+ Map freqMap = new HashMap<>();
+
+ for (int i = 0; i < k; i++) {
+ freqMap.merge(arr[i], 1, Integer::sum);
+ }
+ result[0] = freqMap.size();
+
+ for (int i = k; i < n; i++) {
+ freqMap.merge(arr[i], 1, Integer::sum);
+
+ int outgoing = arr[i - k];
+
+ Integer count = freqMap.get(outgoing);
+ if (count != null) {
+ if (count == 1) {
+ freqMap.remove(outgoing);
+ } else {
+ freqMap.put(outgoing, count - 1);
+ }
+ }
+
+ result[i - k + 1] = freqMap.size();
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java
new file mode 100644
index 000000000000..46f8deeb58dd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java
@@ -0,0 +1,99 @@
+package com.thealgorithms.slidingwindow;
+
+/**
+ * Counts the number of "nice subarrays".
+ * A nice subarray is a contiguous subarray that contains exactly k odd numbers.
+ *
+ * This implementation uses the sliding window technique.
+ *
+ * Reference:
+ * https://leetcode.com/problems/count-number-of-nice-subarrays/
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(n)
+ */
+public final class CountNiceSubarrays {
+
+ // Private constructor to prevent instantiation
+ private CountNiceSubarrays() {
+ }
+
+ /**
+ * Returns the count of subarrays containing exactly k odd numbers.
+ *
+ * @param nums input array of integers
+ * @param k number of odd elements required in the subarray
+ * @return number of nice subarrays
+ */
+ public static int countNiceSubarrays(int[] nums, int k) {
+
+ int n = nums.length;
+
+ // Left pointer of the sliding window
+ int left = 0;
+
+ // Tracks number of odd elements in the current window
+ int oddCount = 0;
+
+ // Final answer: total number of nice subarrays
+ int result = 0;
+
+ /*
+ * memo[i] stores how many valid starting positions exist
+ * when the left pointer is at index i.
+ *
+ * This avoids recomputing the same values again.
+ */
+ int[] memo = new int[n];
+
+ // Right pointer moves forward to expand the window
+ for (int right = 0; right < n; right++) {
+
+ // If current element is odd, increment odd count
+ if ((nums[right] & 1) == 1) {
+ oddCount++;
+ }
+
+ /*
+ * If oddCount exceeds k, shrink the window from the left
+ * until oddCount becomes valid again.
+ */
+ if (oddCount > k) {
+ left += memo[left];
+ oddCount--;
+ }
+
+ /*
+ * When the window contains exactly k odd numbers,
+ * count all possible valid subarrays starting at `left`.
+ */
+ if (oddCount == k) {
+
+ /*
+ * If this left index hasn't been processed before,
+ * count how many consecutive even numbers follow it.
+ */
+ if (memo[left] == 0) {
+ int count = 0;
+ int temp = left;
+
+ // Count consecutive even numbers
+ while ((nums[temp] & 1) == 0) {
+ count++;
+ temp++;
+ }
+
+ /*
+ * Number of valid subarrays starting at `left`
+ * is (count of even numbers + 1)
+ */
+ memo[left] = count + 1;
+ }
+
+ // Add number of valid subarrays for this left position
+ result += memo[left];
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/sorts/BitonicSort.java b/src/main/java/com/thealgorithms/sorts/BitonicSort.java
index 1c1a3ac45540..a714809ea5b2 100644
--- a/src/main/java/com/thealgorithms/sorts/BitonicSort.java
+++ b/src/main/java/com/thealgorithms/sorts/BitonicSort.java
@@ -21,6 +21,9 @@ private enum Direction {
*/
@Override
public > T[] sort(T[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("The input array cannot be null");
+ }
if (array.length == 0) {
return array;
}
diff --git a/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java
new file mode 100644
index 000000000000..062da01f380e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java
@@ -0,0 +1,145 @@
+package com.thealgorithms.sorts;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A concurrent implementation of the Merge Sort algorithm.
+ *
+ * This implementation utilizes a divide-and-conquer strategy, distributing
+ * the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}.
+ * To prevent the overhead of thread creation and context switching from outweighing
+ * the benefits of concurrency, it falls back to a standard sequential merge sort
+ * when the sub-array size drops below a predefined threshold, or when the maximum
+ * concurrency depth is reached (preventing thread starvation deadlocks).
+ *
+ * Complexity:
+ *
+ * - Time Complexity: $O(N \log N)$
+ * - Space Complexity: $O(N)$
+ *
+ */
+public final class ConcurrentMergeSort {
+
+ private ConcurrentMergeSort() {
+ }
+
+ /**
+ * Fallback threshold where the algorithm switches to standard sequential
+ * Merge Sort to prevent thread-creation overhead from ruining performance.
+ */
+ private static final int SEQUENTIAL_THRESHOLD = 8192;
+
+ /**
+ * Sorts the specified array of integers concurrently using Merge Sort.
+ *
+ * @param array the array to be sorted
+ */
+ public static void sort(int[] array) {
+ if (array == null || array.length <= 1) {
+ return;
+ }
+
+ int availableProcessors = Runtime.getRuntime().availableProcessors();
+
+ // Calculate a safe maximum depth to prevent creating more tasks than the pool can handle.
+ // This effectively prevents thread starvation deadlock in fixed-size thread pools,
+ // by forcing leaf tasks to run sequentially and eventually complete.
+ int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1;
+
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
+
+ try {
+ int[] tempArray = new int[array.length];
+ concurrentMergeSort(array, tempArray, 0, array.length - 1, executor, maxDepth);
+ } finally {
+ // Ensure the executor is gracefully shut down
+ executor.shutdown();
+ }
+ }
+
+ /**
+ * Recursively sorts the array utilizing the provided executor for concurrency.
+ *
+ * @param array the array to sort
+ * @param temp a temporary array for merging
+ * @param left the starting index of the sub-array
+ * @param right the ending index of the sub-array
+ * @param executor the {@link ThreadPoolExecutor} to handle concurrent tasks
+ * @param depth the remaining depth for allowing concurrent execution
+ */
+ private static void concurrentMergeSort(int[] array, int[] temp, int left, int right, ThreadPoolExecutor executor, int depth) {
+ int length = right - left + 1;
+
+ // Switch to sequential sort if the array is small or we have reached the maximum concurrent depth
+ if (length < SEQUENTIAL_THRESHOLD || depth <= 0) {
+ sequentialMergeSort(array, temp, left, right);
+ return;
+ }
+
+ int mid = left + (right - left) / 2;
+
+ // Submit the left half for concurrent execution
+ CompletableFuture leftTask = CompletableFuture.runAsync(() -> concurrentMergeSort(array, temp, left, mid, executor, depth - 1), executor);
+
+ // Process the right half in the current thread to optimize resource usage
+ concurrentMergeSort(array, temp, mid + 1, right, executor, depth - 1);
+
+ // Wait for the concurrently executed left half to complete
+ leftTask.join();
+
+ merge(array, temp, left, mid, right);
+ }
+
+ /**
+ * Sorts the specified sub-array sequentially using standard Merge Sort.
+ *
+ * @param array the array to sort
+ * @param temp a temporary array for merging
+ * @param left the starting index of the sub-array
+ * @param right the ending index of the sub-array
+ */
+ private static void sequentialMergeSort(int[] array, int[] temp, int left, int right) {
+ if (left >= right) {
+ return;
+ }
+
+ int mid = left + (right - left) / 2;
+ sequentialMergeSort(array, temp, left, mid);
+ sequentialMergeSort(array, temp, mid + 1, right);
+ merge(array, temp, left, mid, right);
+ }
+
+ /**
+ * Merges two sorted sub-arrays into a single sorted sub-array.
+ *
+ * @param array the original array containing the sub-arrays
+ * @param temp a temporary array used for merging
+ * @param left the starting index of the first sub-array
+ * @param mid the ending index of the first sub-array (and the partition point)
+ * @param right the ending index of the second sub-array
+ */
+ private static void merge(int[] array, int[] temp, int left, int mid, int right) {
+ System.arraycopy(array, left, temp, left, right - left + 1);
+
+ int i = left;
+ int j = mid + 1;
+ int k = left;
+
+ while (i <= mid && j <= right) {
+ if (temp[i] <= temp[j]) {
+ array[k++] = temp[i++];
+ } else {
+ array[k++] = temp[j++];
+ }
+ }
+
+ while (i <= mid) {
+ array[k++] = temp[i++];
+ }
+
+ // Remaining elements from the right half are already in their correct relative positions
+ }
+}
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/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java
new file mode 100644
index 000000000000..28f16e3016ff
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java
@@ -0,0 +1,207 @@
+package com.thealgorithms.sorts;
+
+import java.util.Arrays;
+
+/**
+ * Library Sort (also known as Gapped Insertion Sort) maintains a sparse
+ * working array with gaps distributed between elements, so that most
+ * insertions land directly in an empty gap without shifting anything.
+ * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...);
+ * after each round the array is rebalanced so gaps are spread out evenly
+ * again for the next round.
+ * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse
+ * Space Complexity: O(n)
+ *
+ * @see
+ * Wikipedia: Library Sort
+ * @author Vraj Prajapati (@Rosander0)
+ */
+public final class LibrarySort {
+
+ private static final int GAP_FACTOR = 2;
+
+ private LibrarySort() {
+ // Utility class
+ }
+
+ /**
+ * Sorts an array using the Library Sort algorithm.
+ *
+ * @param array the array to sort (must not be null)
+ * @return the sorted array
+ * @throws IllegalArgumentException if {@code array} is {@code null}
+ */
+ public static int[] sort(final int[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("Input array must not be null.");
+ }
+ if (array.length <= 1) {
+ return array;
+ }
+
+ final int n = array.length;
+ final int capacity = GAP_FACTOR * n;
+ final int[] data = new int[capacity];
+ final boolean[] occupied = new boolean[capacity];
+
+ final int mid = capacity / 2;
+ data[mid] = array[0];
+ occupied[mid] = true;
+
+ int filled = 1;
+ int nextToInsert = 1;
+ int round = 0;
+ while (nextToInsert < n) {
+ final int roundSize = Math.min(1 << round, n - nextToInsert);
+ for (int i = 0; i < roundSize; i++) {
+ insert(data, occupied, array[nextToInsert + i]);
+ filled++;
+ }
+ nextToInsert += roundSize;
+ round++;
+ if (nextToInsert < n) {
+ rebalance(data, occupied, filled);
+ }
+ }
+
+ int idx = 0;
+ for (int i = 0; i < capacity; i++) {
+ if (occupied[i]) {
+ array[idx++] = data[i];
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Inserts {@code value} into the gapped array, placing it directly in an
+ * empty gap when possible, otherwise shifting toward the nearest gap.
+ */
+ private static void insert(final int[] data, final boolean[] occupied, final int value) {
+ final int pos = findInsertionIndex(data, occupied, value);
+ if (pos >= data.length) {
+ insertAtEnd(data, occupied, value);
+ return;
+ }
+
+ if (!occupied[pos]) {
+ data[pos] = value;
+ occupied[pos] = true;
+ return;
+ }
+
+ int right = pos;
+ while (right < data.length && occupied[right]) {
+ right++;
+ }
+ int left = pos - 1;
+ while (left >= 0 && occupied[left]) {
+ left--;
+ }
+
+ final boolean canGoRight = right < data.length;
+ final boolean canGoLeft = left >= 0;
+
+ if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) {
+ // Shift data[pos, right) one slot to the right, opening a gap at pos.
+ // occupied[pos] is untouched by the copy and was already true.
+ System.arraycopy(data, pos, data, pos + 1, right - pos);
+ occupied[right] = true;
+ data[pos] = value;
+ } else if (canGoLeft) {
+ // Shift data[left + 1, pos) one slot to the left, opening a gap at pos - 1.
+ // occupied[pos - 1] is untouched by the copy and was already true.
+ System.arraycopy(data, left + 1, data, left, pos - 1 - left);
+ occupied[left] = true;
+ data[pos - 1] = value;
+ } else {
+ // Unreachable in practice: canGoRight and canGoLeft can only both be false if
+ // every slot in this capacity-2n array is occupied, but at most n elements are
+ // ever present at once. Kept as a defensive guard against that invariant breaking.
+ throw new IllegalStateException("No gap available for insertion; rebalance too infrequent.");
+ }
+ }
+
+ /**
+ * Handles insertion of a new global maximum, which must land after every
+ * currently occupied slot. Since there is no room to its right, this
+ * shifts occupied slots left into the nearest gap instead.
+ */
+ private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) {
+ final int last = data.length - 1;
+ // occupied[last] is unreachable as false here: insertAtEnd() is only called when
+ // findInsertionIndex() returns data.length, which requires data[last] to already be
+ // occupied. Kept as a defensive guard in case that invariant is ever broken.
+ if (!occupied[last]) {
+ data[last] = value;
+ occupied[last] = true;
+ return;
+ }
+ int left = last - 1;
+ while (left >= 0 && occupied[left]) {
+ left--;
+ }
+ // left < 0 is unreachable in practice: at most n elements ever occupy this
+ // capacity-2n array, so fewer than half the slots left of `last` can be filled,
+ // guaranteeing a gap exists before the scan reaches index -1.
+ if (left < 0) {
+ throw new IllegalStateException("No gap available for insertion; rebalance too infrequent.");
+ }
+ // Shift data[left + 1, last] one slot to the left, opening a gap at last.
+ // occupied[last] is untouched by the copy and was already true.
+ System.arraycopy(data, left + 1, data, left, last - left);
+ occupied[left] = true;
+ data[last] = value;
+ }
+
+ /**
+ * Finds the leftmost index at which {@code value} can be inserted so
+ * that occupied slots remain sorted. Empty slots are compared using the
+ * value of the nearest occupied slot at or after them, which is a
+ * monotonic function of index and therefore safe to binary search over.
+ */
+ private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) {
+ int lo = 0;
+ int hi = data.length;
+ while (lo < hi) {
+ final int mid = lo + (hi - lo) / 2;
+ final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid);
+ if (probe != Integer.MAX_VALUE && probe <= value) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ return lo;
+ }
+
+ private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) {
+ for (int i = index; i < data.length; i++) {
+ if (occupied[i]) {
+ return data[i];
+ }
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Redistributes the {@code filled} occupied elements evenly across the
+ * full capacity of {@code data}, restoring uniform gaps between them.
+ */
+ private static void rebalance(final int[] data, final boolean[] occupied, final int filled) {
+ final int capacity = data.length;
+ final int[] temp = new int[filled];
+ int idx = 0;
+ for (int i = 0; i < capacity; i++) {
+ if (occupied[i]) {
+ temp[idx++] = data[i];
+ }
+ }
+ Arrays.fill(occupied, false);
+ for (int k = 0; k < filled; k++) {
+ final int pos = (int) ((long) k * capacity / filled);
+ data[pos] = temp[k];
+ occupied[pos] = true;
+ }
+ }
+}
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/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java
index 2d1814441701..e6d1a16a7af7 100644
--- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java
+++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java
@@ -1,5 +1,10 @@
package com.thealgorithms.sorts;
+/**
+ * Implementation of the Selection Sort algorithm.
+ *
+ * @see SortAlgorithm
+ */
public class SelectionSort implements SortAlgorithm {
/**
* Generic Selection Sort algorithm.
@@ -11,11 +16,12 @@ public class SelectionSort implements SortAlgorithm {
*
* Space Complexity: O(1) β in-place sorting.
*
- * @see SortAlgorithm
+ * @param array the array to be sorted
+ * @param the type of elements in the array
+ * @return the sorted array
*/
@Override
public > T[] sort(T[] array) {
-
for (int i = 0; i < array.length - 1; i++) {
final int minIndex = findIndexOfMin(array, i);
SortUtils.swap(array, i, minIndex);
@@ -23,6 +29,14 @@ public > T[] sort(T[] array) {
return array;
}
+ /**
+ * Finds the index of the minimum element in the array starting from a given index.
+ *
+ * @param array the array to search
+ * @param startIndex the index to start searching from
+ * @param the type of elements in the array
+ * @return the index of the minimum element
+ */
private static > int findIndexOfMin(T[] array, final int startIndex) {
int minIndex = startIndex;
for (int i = startIndex + 1; i < array.length; i++) {
diff --git a/src/main/java/com/thealgorithms/sorts/SmoothSort.java b/src/main/java/com/thealgorithms/sorts/SmoothSort.java
new file mode 100644
index 000000000000..c45d6f1f02b2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/SmoothSort.java
@@ -0,0 +1,168 @@
+package com.thealgorithms.sorts;
+
+/**
+ * Smooth Sort is an in-place, comparison-based sorting algorithm proposed by Edsger W. Dijkstra (1981).
+ *
+ * It can be viewed as a variant of heapsort that maintains a forest of heap-ordered Leonardo trees
+ * (trees whose sizes are Leonardo numbers). The algorithm is adaptive: when the input is already
+ * sorted or nearly sorted, the heap invariants are often satisfied and the expensive rebalancing
+ * operations do little work, yielding near-linear behavior.
+ *
+ * Time Complexity:
+ *
+ * - Best case: O(n) for already sorted input
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ *
+ * Space Complexity: O(1) auxiliary space (in-place).
+ *
+ * @see Smoothsort
+ * @see Leonardo numbers
+ * @see SortAlgorithm
+ */
+public class SmoothSort implements SortAlgorithm {
+
+ /**
+ * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that
+ * fits into a signed 32-bit integer.
+ */
+ private static final int[] LEONARDO = {1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703, 48315633, 78176337,
+ 126491971, 204668309, 331160281, 535828591, 866988873, 1402817465};
+
+ /**
+ * Sorts the given array in ascending order using Smooth Sort.
+ *
+ * @param array the array to sort
+ * @param the element type
+ * @return the sorted array
+ */
+ @Override
+ public > T[] sort(final T[] array) {
+ if (array.length < 2) {
+ return array;
+ }
+
+ final int last = array.length - 1;
+
+ // The forest shape is encoded as (p, pshift): p is a bit-vector of present tree orders,
+ // shifted right by pshift. pshift is the order of the rightmost (current) Leonardo tree.
+ long p = 1L;
+ int pshift = 1;
+
+ int head = 0;
+ while (head < last) {
+ if ((p & 3L) == 3L) {
+ sift(array, pshift, head);
+ p >>>= 2;
+ pshift += 2;
+ } else {
+ // Add a new singleton tree; if it will not be merged anymore, we must fully trinkle.
+ if (LEONARDO[pshift - 1] >= last - head) {
+ trinkle(array, p, pshift, head, false);
+ } else {
+ // This tree will be merged later, so it is enough to restore its internal heap property.
+ sift(array, pshift, head);
+ }
+
+ if (pshift == 1) {
+ // If L(1) is used, the new singleton is L(0).
+ p <<= 1;
+ pshift = 0;
+ } else {
+ // Otherwise, shift to order 1 and append a singleton of order 1.
+ p <<= (pshift - 1);
+ pshift = 1;
+ }
+ }
+
+ p |= 1L;
+ head++;
+ }
+
+ trinkle(array, p, pshift, head, false);
+
+ // Repeatedly remove the maximum (always at head) by shrinking the heap region.
+ while (pshift != 1 || p != 1L) {
+ if (pshift <= 1) {
+ // Rightmost tree is a singleton (order 0 or 1). Move to the previous tree root.
+ final long mask = p & ~1L;
+ final int shift = Long.numberOfTrailingZeros(mask);
+ p >>>= shift;
+ pshift += shift;
+ } else {
+ // Split a tree of order (pshift) into two children trees of orders (pshift-1) and (pshift-2).
+ p <<= 2;
+ p ^= 7L;
+ pshift -= 2;
+
+ trinkle(array, p >>> 1, pshift + 1, head - LEONARDO[pshift] - 1, true);
+ trinkle(array, p, pshift, head - 1, true);
+ }
+
+ head--;
+ }
+
+ return array;
+ }
+
+ private static > void sift(final T[] array, int order, int root) {
+ final T value = array[root];
+
+ while (order > 1) {
+ final int right = root - 1;
+ final int left = root - 1 - LEONARDO[order - 2];
+
+ if (!SortUtils.less(value, array[left]) && !SortUtils.less(value, array[right])) {
+ break;
+ }
+
+ if (!SortUtils.less(array[left], array[right])) {
+ array[root] = array[left];
+ root = left;
+ order -= 1;
+ } else {
+ array[root] = array[right];
+ root = right;
+ order -= 2;
+ }
+ }
+
+ array[root] = value;
+ }
+
+ private static > void trinkle(final T[] array, long p, int order, int root, boolean trusty) {
+ final T value = array[root];
+
+ while (p != 1L) {
+ final int stepson = root - LEONARDO[order];
+
+ if (!SortUtils.less(value, array[stepson])) {
+ break;
+ }
+
+ if (!trusty && order > 1) {
+ final int right = root - 1;
+ final int left = root - 1 - LEONARDO[order - 2];
+
+ if (!SortUtils.less(array[right], array[stepson]) || !SortUtils.less(array[left], array[stepson])) {
+ break;
+ }
+ }
+
+ array[root] = array[stepson];
+ root = stepson;
+
+ final long mask = p & ~1L;
+ final int shift = Long.numberOfTrailingZeros(mask);
+ p >>>= shift;
+ order += shift;
+ trusty = false;
+ }
+
+ if (!trusty) {
+ array[root] = value;
+ sift(array, order, root);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/sorts/TournamentSort.java b/src/main/java/com/thealgorithms/sorts/TournamentSort.java
new file mode 100644
index 000000000000..ec51a1e2c0a9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/TournamentSort.java
@@ -0,0 +1,84 @@
+package com.thealgorithms.sorts;
+
+import java.util.Arrays;
+
+/**
+ * Tournament Sort algorithm implementation.
+ *
+ * Tournament sort builds a winner tree (a complete binary tree storing the index
+ * of the smallest element in each subtree). It then repeatedly extracts the
+ * winner (minimum) and updates the path from the removed leaf to the root.
+ *
+ * Time Complexity:
+ * - Best case: O(n log n)
+ * - Average case: O(n log n)
+ * - Worst case: O(n log n)
+ *
+ * Space Complexity: O(n) β additional winner-tree storage
+ *
+ * @see Tournament Sort Algorithm
+ * @see SortAlgorithm
+ */
+public class TournamentSort implements SortAlgorithm {
+
+ @Override
+ public > T[] sort(T[] array) {
+ if (array == null || array.length < 2) {
+ return array;
+ }
+
+ final int n = array.length;
+ final int leafCount = nextPowerOfTwo(n);
+
+ // Winner tree represented as an array:
+ // - Leaves live at [leafCount .. 2*leafCount)
+ // - Internal nodes live at [1 .. leafCount)
+ // Each node stores an index into the original array or -1 for "empty".
+ final int[] tree = new int[2 * leafCount];
+ Arrays.fill(tree, -1);
+
+ for (int i = 0; i < n; i++) {
+ tree[leafCount + i] = i;
+ }
+
+ for (int node = leafCount - 1; node >= 1; node--) {
+ tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]);
+ }
+
+ final T[] result = array.clone();
+ for (int out = 0; out < n; out++) {
+ final int winner = tree[1];
+ result[out] = array[winner];
+
+ int node = leafCount + winner;
+ tree[node] = -1;
+
+ for (node /= 2; node >= 1; node /= 2) {
+ tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]);
+ }
+ }
+
+ System.arraycopy(result, 0, array, 0, n);
+ return array;
+ }
+
+ private static int nextPowerOfTwo(int n) {
+ int power = 1;
+ while (power < n) {
+ power <<= 1;
+ }
+ return power;
+ }
+
+ private static > int winnerIndex(T[] array, int leftIndex, int rightIndex) {
+ if (leftIndex == -1) {
+ return rightIndex;
+ }
+ if (rightIndex == -1) {
+ return leftIndex;
+ }
+
+ // If equal, prefer the left element to keep ordering deterministic.
+ return SortUtils.less(array[rightIndex], array[leftIndex]) ? rightIndex : leftIndex;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/sorts/WiggleSort.java b/src/main/java/com/thealgorithms/sorts/WiggleSort.java
index c272b820d07a..0349971d1c95 100644
--- a/src/main/java/com/thealgorithms/sorts/WiggleSort.java
+++ b/src/main/java/com/thealgorithms/sorts/WiggleSort.java
@@ -11,9 +11,9 @@
* https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity
* Also have a look at:
* https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity?noredirect=1&lq=1
- * Not all arrays are wiggle-sortable. This algorithm will find some obviously not wiggle-sortable
- * arrays and throw an error, but there are some exceptions that won't be caught, for example [1, 2,
- * 2].
+ * Not all arrays are wiggle-sortable. This algorithm detects non-wiggle-sortable inputs β for
+ * example [1, 2, 2], or arrays where more than half the values are equal β and throws an
+ * IllegalArgumentException instead of returning a wrongly ordered result.
*/
public class WiggleSort implements SortAlgorithm {
diff --git a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java
index 98c439341a21..7afe2c99aae8 100644
--- a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java
+++ b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java
@@ -8,6 +8,13 @@
* which we will pop one-by-one to create the string in reverse.
*
* Reference: https://www.geeksforgeeks.org/check-whether-the-given-string-is-palindrome-using-stack/
+ *
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
*/
public class PalindromeWithStack {
private LinkedList stack;
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/AlternativeStringArrange.java b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
index cf736dbd8cab..016ee2821a17 100644
--- a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
+++ b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
@@ -21,12 +21,19 @@ private AlternativeStringArrange() {
/**
* Arranges two strings by alternating their characters.
+ * If one string is longer than the other, the remaining characters of the longer string
+ * are appended at the end of the result.
*
- * @param firstString the first input string
- * @param secondString the second input string
+ * @param firstString the first input string, must not be {@code null}
+ * @param secondString the second input string, must not be {@code null}
* @return a new string with characters from both strings arranged alternately
+ * @throws IllegalArgumentException if {@code firstString} or {@code secondString} is {@code null}
*/
public static String arrange(String firstString, String secondString) {
+ if (firstString == null || secondString == null) {
+ throw new IllegalArgumentException("Input strings must not be null");
+ }
+
StringBuilder result = new StringBuilder();
int length1 = firstString.length();
int length2 = secondString.length();
diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java
index 5b97af0758f2..7bd84d47508f 100644
--- a/src/main/java/com/thealgorithms/strings/Anagrams.java
+++ b/src/main/java/com/thealgorithms/strings/Anagrams.java
@@ -5,7 +5,7 @@
/**
* An anagram is a word or phrase formed by rearranging the letters of a different word or phrase,
- * typically using all the original letters exactly once.[1]
+ * typically using all the original letters exactly once.
* For example, the word anagram itself can be rearranged into nag a ram,
* also the word binary into brainy and the word adobe into abode.
* Reference from https://en.wikipedia.org/wiki/Anagram
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/LongestCommonSubstring.java b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java
new file mode 100644
index 000000000000..b2190316aff2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.strings;
+
+/**
+ * Longest Common Substring finds the longest string that is a
+ * contiguous substring of two input strings.
+ * Example: "abcdef" and "zcdemf" -> "cde"
+ *
+ * @see
+ * Wikipedia: Longest Common Substring
+ *
+ * author: Vraj Prajapati @Rosander0
+ */
+public final class LongestCommonSubstring {
+
+ private LongestCommonSubstring() {
+ // Utility class
+ }
+
+ /**
+ * Finds the longest common substring of two strings.
+ *
+ * @param a First input string
+ * @param b Second input string
+ * @return The longest common substring, or empty string if none exists.
+ * If multiple substrings share the maximum length, the first one found is returned.
+ */
+ public static String longestCommonSubstring(final String a, final String b) {
+ if (a == null || b == null || a.isEmpty() || b.isEmpty()) {
+ return "";
+ }
+
+ int[][] dp = new int[a.length() + 1][b.length() + 1];
+ int maxLength = 0;
+ int endIndex = 0;
+
+ for (int i = 1; i <= a.length(); i++) {
+ for (int j = 1; j <= b.length(); j++) {
+ if (a.charAt(i - 1) == b.charAt(j - 1)) {
+ dp[i][j] = dp[i - 1][j - 1] + 1;
+ if (dp[i][j] > maxLength) {
+ maxLength = dp[i][j];
+ endIndex = i;
+ }
+ } else {
+ dp[i][j] = 0;
+ }
+ }
+ }
+
+ if (maxLength == 0) {
+ return "";
+ }
+ return a.substring(endIndex - maxLength, endIndex);
+ }
+}
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/MyAtoi.java b/src/main/java/com/thealgorithms/strings/MyAtoi.java
index 5a7c2ce53b1c..92de4039a582 100644
--- a/src/main/java/com/thealgorithms/strings/MyAtoi.java
+++ b/src/main/java/com/thealgorithms/strings/MyAtoi.java
@@ -45,7 +45,9 @@ public static int myAtoi(String s) {
int number = 0;
while (index < length) {
char ch = s.charAt(index);
- if (!Character.isDigit(ch)) {
+
+ // Accept only ASCII digits
+ if (ch < '0' || ch > '9') {
break;
}
diff --git a/src/main/java/com/thealgorithms/strings/Palindrome.java b/src/main/java/com/thealgorithms/strings/Palindrome.java
index 3567a371d70e..64de657df359 100644
--- a/src/main/java/com/thealgorithms/strings/Palindrome.java
+++ b/src/main/java/com/thealgorithms/strings/Palindrome.java
@@ -2,6 +2,13 @@
/**
* Wikipedia: https://en.wikipedia.org/wiki/Palindrome
+ *
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
*/
final class Palindrome {
private Palindrome() {
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/ReverseString.java b/src/main/java/com/thealgorithms/strings/ReverseString.java
index 7b918ebe1a59..e373dd0b7174 100644
--- a/src/main/java/com/thealgorithms/strings/ReverseString.java
+++ b/src/main/java/com/thealgorithms/strings/ReverseString.java
@@ -62,7 +62,7 @@ public static String reverse3(String string) {
/**
* Reverses the given string using a stack.
* This method uses a stack to reverse the characters of the string.
- * * @param str The input string to be reversed.
+ * @param str The input string to be reversed.
* @return The reversed string.
*/
public static String reverseStringUsingStack(String str) {
diff --git a/src/main/java/com/thealgorithms/strings/TitleCase.java b/src/main/java/com/thealgorithms/strings/TitleCase.java
new file mode 100644
index 000000000000..eb6c73623681
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/TitleCase.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.strings;
+
+/**
+ * Title Case converts a string so that the first letter of each word
+ * is capitalized and the rest are lowercase.
+ * Example: "the quick brown fox" -> "The Quick Brown Fox"
+ *
+ * @see
+ * Wikipedia: Title Case
+ */
+public final class TitleCase {
+
+ private TitleCase() {
+ // Utility class
+ }
+
+ /**
+ * Converts a string to title case.
+ *
+ * @param input The string to convert
+ * @return The title-cased string, or empty string if input is null/empty.
+ * If input contains only whitespace, it is returned as is.
+ */
+ public static String toTitleCase(final String input) {
+ if (input == null || input.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder(input.length());
+ boolean capitalizeNext = true;
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (Character.isWhitespace(c)) {
+ capitalizeNext = true;
+ result.append(c);
+ continue;
+ }
+
+ if (capitalizeNext) {
+ if (Character.isLetter(c)) {
+ result.append(Character.toUpperCase(c));
+ capitalizeNext = false;
+ } else {
+ // Keep capitalizeNext=true so the first *letter* after
+ // punctuation/digits is capitalized.
+ result.append(c);
+ }
+ } else {
+ result.append(Character.toLowerCase(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/backtracking/CombinationTest.java b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java
index a9d1163f3ecd..5d2f99ccadf8 100644
--- a/src/test/java/com/thealgorithms/backtracking/CombinationTest.java
+++ b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java
@@ -28,16 +28,16 @@ void testNoElement() {
@Test
void testLengthOne() {
List> result = Combination.combination(new Integer[] {1, 2}, 1);
- assertTrue(result.get(0).iterator().next() == 1);
- assertTrue(result.get(1).iterator().next() == 2);
+ assertEquals(1, result.get(0).iterator().next());
+ assertEquals(2, result.get(1).iterator().next());
}
@Test
void testLengthTwo() {
List> result = Combination.combination(new Integer[] {1, 2}, 2);
Integer[] arr = result.get(0).toArray(new Integer[2]);
- assertTrue(arr[0] == 1);
- assertTrue(arr[1] == 2);
+ assertEquals(1, arr[0]);
+ assertEquals(2, arr[1]);
}
@Test
diff --git a/src/test/java/com/thealgorithms/backtracking/PermutationTest.java b/src/test/java/com/thealgorithms/backtracking/PermutationTest.java
index 76a714829109..54747e5e73a1 100644
--- a/src/test/java/com/thealgorithms/backtracking/PermutationTest.java
+++ b/src/test/java/com/thealgorithms/backtracking/PermutationTest.java
@@ -12,13 +12,13 @@ public class PermutationTest {
@Test
void testNoElement() {
List result = Permutation.permutation(new Integer[] {});
- assertEquals(result.get(0).length, 0);
+ assertEquals(0, result.get(0).length);
}
@Test
void testSingleElement() {
List result = Permutation.permutation(new Integer[] {1});
- assertEquals(result.get(0)[0], 1);
+ assertEquals(1, result.get(0)[0]);
}
@Test
diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java
new file mode 100644
index 000000000000..ecd1f3c4dfae
--- /dev/null
+++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java
@@ -0,0 +1,99 @@
+package com.thealgorithms.backtracking;
+
+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.List;
+import org.junit.jupiter.api.Test;
+
+class RatInAMazeTest {
+
+ @Test
+ void testMultiplePathsExist() {
+ int[][] maze = {{1, 0, 0, 0}, {1, 1, 0, 1}, {0, 1, 0, 0}, {0, 1, 1, 1}};
+
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.size() >= 1);
+ for (String path : paths) {
+ assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0));
+ }
+ }
+
+ @Test
+ void testSinglePath() {
+ int[][] maze = {{1, 0, 0}, {1, 1, 0}, {0, 1, 1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertEquals(1, paths.size());
+ assertEquals("DRDR", paths.get(0));
+ }
+
+ @Test
+ void testNoPathExists() {
+ int[][] maze = {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.isEmpty());
+ }
+
+ @Test
+ void testSourceBlocked() {
+ int[][] maze = {{0, 1}, {1, 1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.isEmpty());
+ }
+
+ @Test
+ void testDestinationBlocked() {
+ int[][] maze = {{1, 1}, {1, 0}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.isEmpty());
+ }
+
+ @Test
+ void testSingleCellMazeOpen() {
+ int[][] maze = {{1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertEquals(1, paths.size());
+ assertEquals("", paths.get(0));
+ }
+
+ @Test
+ void testSingleCellMazeBlocked() {
+ int[][] maze = {{0}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.isEmpty());
+ }
+
+ @Test
+ void testNullMazeThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(null));
+ }
+
+ @Test
+ void testEmptyMazeThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][] {}));
+ }
+
+ @Test
+ void testNonSquareMazeThrowsException() {
+ int[][] maze = {{1, 0, 1}, {1, 1, 1}};
+ assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze));
+ }
+
+ @Test
+ void testAllCellsOpen() {
+ int[][] maze = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.size() > 1);
+ }
+
+ @Test
+ void testLargerMazeWithPath() {
+ int[][] maze = {{1, 1, 1, 1}, {0, 1, 0, 1}, {0, 1, 0, 1}, {0, 1, 1, 1}};
+ List paths = RatInAMaze.findPaths(maze);
+ assertTrue(paths.size() >= 1);
+ for (String path : paths) {
+ assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path);
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java
index 7aa41c4cf423..c8b20ad8d8f9 100644
--- a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java
+++ b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java
@@ -32,6 +32,29 @@ void caesarDecryptTest() {
assertEquals("Encrypt this text", cipherText);
}
+ @Test
+ void caesarEncryptWithNegativeShiftTest() {
+ // a shift of -1 must wrap 'A' backwards to 'Z', like a shift of +25 would
+ assertEquals("Z", caesar.encode("A", -1));
+ assertEquals("z", caesar.encode("a", -1));
+ assertEquals("EBIIL", caesar.encode("HELLO", -3));
+ }
+
+ @Test
+ void caesarDecryptWithNegativeShiftTest() {
+ assertEquals("A", caesar.decode("Z", -1));
+ assertEquals("HELLO", caesar.decode("EBIIL", -3));
+ }
+
+ @Test
+ void caesarNegativeShiftRoundTripTest() {
+ // encode followed by decode with the same shift must return the original text
+ for (int shift : new int[] {-1, -5, -25, -26, -27, -52}) {
+ String message = "The quick brown Fox";
+ assertEquals(message, caesar.decode(caesar.encode(message, shift), shift));
+ }
+ }
+
@Test
void caesarBruteForce() {
// given
diff --git a/src/test/java/com/thealgorithms/ciphers/ECCTest.java b/src/test/java/com/thealgorithms/ciphers/ECCTest.java
index 701f801af1c8..b78ba51f7c3e 100644
--- a/src/test/java/com/thealgorithms/ciphers/ECCTest.java
+++ b/src/test/java/com/thealgorithms/ciphers/ECCTest.java
@@ -37,7 +37,7 @@ void testEncrypt() {
System.out.println("Base Point G: " + curve.getBasePoint());
// Verify that the ciphertext is not empty
- assertEquals(cipherText.length, 2); // Check if the ciphertext contains two points (R and S)
+ assertEquals(2, cipherText.length); // Check if the ciphertext contains two points (R and S)
// Output the encrypted coordinate points
System.out.println("Encrypted Points:");
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/ciphers/PermutationCipherTest.java b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
index 4ba6787cc97e..ecb7455c1ba2 100644
--- a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
+++ b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.ciphers;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
@@ -121,8 +122,8 @@ void testNullString() {
String decrypted = cipher.decrypt(encrypted, key);
// then
- assertEquals(null, encrypted);
- assertEquals(null, decrypted);
+ assertNull(encrypted);
+ assertNull(decrypted);
}
@Test
diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java
new file mode 100644
index 000000000000..041f8c0dd4c1
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java
@@ -0,0 +1,70 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+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.ValueSource;
+
+class RailFenceCipherTest {
+
+ private final RailFenceCipher railFenceCipher = new RailFenceCipher();
+
+ @Test
+ void testEncrypt() {
+ assertEquals("WECRLTEERDSOEEFEAOCAIVDEN", railFenceCipher.encrypt("WEAREDISCOVEREDFLEEATONCE", 3));
+ }
+
+ @Test
+ void testDecrypt() {
+ assertEquals("WEAREDISCOVEREDFLEEATONCE", railFenceCipher.decrypt("WECRLTEERDSOEEFEAOCAIVDEN", 3));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"HELLOWORLD, 2", "HELLOWORLD, 3", "HELLOWORLD, 4", "ATTACKATDAWN, 5", "abcdefghij, 6"})
+ void testRoundTrip(String message, int rails) {
+ assertEquals(message, railFenceCipher.decrypt(railFenceCipher.encrypt(message, rails), rails));
+ }
+
+ /**
+ * Every character of the input must survive encryption, including the ones that used to collide
+ * with the placeholder that marked unused cells of the rail matrix.
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {"ab\ncdef", "line1\nline2\nline3", "\n\n\n\n\n", "a\nb", "tabs\tand\nnewlines\r\n"})
+ void testControlCharactersArePreserved(String message) {
+ for (int rails = 2; rails <= 5; rails++) {
+ String encrypted = railFenceCipher.encrypt(message, rails);
+ assertEquals(message.length(), encrypted.length(), "characters were dropped with " + rails + " rails");
+ assertEquals(message, railFenceCipher.decrypt(encrypted, rails), "round trip failed with " + rails + " rails");
+ }
+ }
+
+ @Test
+ void testEncryptWithNewlineMatchesReferencePattern() {
+ // Rails of "ab\ncdef" with 3 rails: {a, d} / {b, c, e} / {\n, f}
+ assertEquals("adbce\nf", railFenceCipher.encrypt("ab\ncdef", 3));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"HELLO, 1", "HELLO, 5", "HELLO, 9", "'', 1", "'', 4"})
+ void testDegenerateRailCountsReturnInput(String message, int rails) {
+ assertEquals(message, railFenceCipher.encrypt(message, rails));
+ assertEquals(message, railFenceCipher.decrypt(message, rails));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, -1, -7})
+ void testNonPositiveRailCountThrows(int rails) {
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt("HELLO", rails));
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt("HELLO", rails));
+ }
+
+ @Test
+ void testNullInputThrows() {
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt(null, 3));
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt(null, 3));
+ }
+}
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/compression/LZ78Test.java b/src/test/java/com/thealgorithms/compression/LZ78Test.java
index 7889b50b76f3..da1fd8d23318 100644
--- a/src/test/java/com/thealgorithms/compression/LZ78Test.java
+++ b/src/test/java/com/thealgorithms/compression/LZ78Test.java
@@ -1,7 +1,6 @@
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.assertTrue;
import java.util.List;
@@ -286,7 +285,6 @@ void testTokenStructure() {
// All tokens should have valid indices (>= 0)
for (LZ78.Token token : compressed) {
assertTrue(token.index() >= 0);
- assertNotNull(token.nextChar());
}
String decompressed = LZ78.decompress(compressed);
diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java
index fbc220c0ca95..cd0d6c8b38a8 100644
--- a/src/test/java/com/thealgorithms/conversions/Base64Test.java
+++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java
@@ -127,6 +127,9 @@ void testInvalidPaddingPosition() {
assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ"));
assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q="));
assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ"));
+ assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ=Q"));
+ assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=C"));
+ assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=A"));
}
@Test
diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java
index 8212793dfb79..f85a4628a1ac 100644
--- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java
@@ -98,11 +98,9 @@ void testIterator() {
@Test
void testIteratorEmptyBag() {
Bag bag = new Bag<>();
- int count = 0;
- for (String ignored : bag) {
- org.junit.jupiter.api.Assertions.fail("Iterator should not return any items for an empty bag");
+ for (String item : bag) {
+ org.junit.jupiter.api.Assertions.fail("Iterator returned item for an empty bag:" + item);
}
- assertEquals(0, count, "Iterator should not traverse any items in an empty bag");
}
@Test
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/graphs/DijkstraOptimizedAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java
deleted file mode 100644
index bf4e2828e069..000000000000
--- a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.thealgorithms.datastructures.graphs;
-
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class DijkstraOptimizedAlgorithmTest {
-
- private DijkstraOptimizedAlgorithm dijkstraOptimizedAlgorithm;
- private int[][] graph;
-
- @BeforeEach
- void setUp() {
- graph = new int[][] {
- {0, 4, 0, 0, 0, 0, 0, 8, 0},
- {4, 0, 8, 0, 0, 0, 0, 11, 0},
- {0, 8, 0, 7, 0, 4, 0, 0, 2},
- {0, 0, 7, 0, 9, 14, 0, 0, 0},
- {0, 0, 0, 9, 0, 10, 0, 0, 0},
- {0, 0, 4, 14, 10, 0, 2, 0, 0},
- {0, 0, 0, 0, 0, 2, 0, 1, 6},
- {8, 11, 0, 0, 0, 0, 1, 0, 7},
- {0, 0, 2, 0, 0, 0, 6, 7, 0},
- };
-
- dijkstraOptimizedAlgorithm = new DijkstraOptimizedAlgorithm(graph.length);
- }
-
- @Test
- void testRunAlgorithm() {
- int[] expectedDistances = {0, 4, 12, 19, 21, 11, 9, 8, 14};
- assertArrayEquals(expectedDistances, dijkstraOptimizedAlgorithm.run(graph, 0));
- }
-
- @Test
- void testGraphWithDisconnectedNodes() {
- int[][] disconnectedGraph = {
- {0, 3, 0, 0}, {3, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0} // Node 3 is disconnected
- };
-
- DijkstraOptimizedAlgorithm dijkstraDisconnected = new DijkstraOptimizedAlgorithm(disconnectedGraph.length);
-
- // Testing from vertex 0
- int[] expectedDistances = {0, 3, 4, Integer.MAX_VALUE}; // Node 3 is unreachable
- assertArrayEquals(expectedDistances, dijkstraDisconnected.run(disconnectedGraph, 0));
- }
-
- @Test
- void testSingleVertexGraph() {
- int[][] singleVertexGraph = {{0}};
- DijkstraOptimizedAlgorithm dijkstraSingleVertex = new DijkstraOptimizedAlgorithm(1);
-
- int[] expectedDistances = {0}; // The only vertex's distance to itself is 0
- assertArrayEquals(expectedDistances, dijkstraSingleVertex.run(singleVertexGraph, 0));
- }
-
- @Test
- void testInvalidSourceVertex() {
- assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, -1));
- assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, graph.length));
- }
-}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java
new file mode 100644
index 000000000000..003f6fcee812
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java
@@ -0,0 +1,134 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class RottingOrangesTest {
+
+ @Test
+ void testAllOrangesRotInSingleMinute() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}};
+
+ assertEquals(4, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testImpossibleToRotAllOranges() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}};
+
+ assertEquals(-1, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testNoFreshOranges() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 2}, {2, 2}};
+
+ assertEquals(0, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testNoRottenOranges() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{1, 1}, {1, 1}};
+
+ assertEquals(-1, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testEmptyGrid() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {};
+
+ assertEquals(0, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testSingleRottenOrange() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2}};
+
+ assertEquals(0, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testSingleFreshOrange() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{1}};
+
+ assertEquals(-1, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testSingleFreshOrangeNextToRottenOrange() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1}};
+
+ assertEquals(1, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testMultipleRottenSources() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1, 0, 2}, {1, 1, 1, 1}, {0, 1, 1, 1}};
+
+ assertEquals(3, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testFreshOrangeBlockedByEmptyCells() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 0, 1}, {0, 0, 0}, {1, 0, 1}};
+
+ assertEquals(-1, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testLinearSpread() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1, 1, 1, 1}};
+
+ assertEquals(4, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testVerticalSpread() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2}, {1}, {1}, {1}};
+
+ assertEquals(3, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testGridWithOnlyEmptyCells() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{0, 0}, {0, 0}};
+
+ assertEquals(0, rottingOranges.run(grid));
+ }
+
+ @Test
+ void testComplexGrid() {
+ RottingOranges rottingOranges = new RottingOranges();
+
+ int[][] grid = {{2, 1, 1}, {1, 1, 1}, {1, 1, 1}};
+
+ assertEquals(4, rottingOranges.run(grid));
+ }
+}
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/hashmap/hashing/MapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java
index 44551a8adac6..ef7739a2e8a9 100644
--- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java
@@ -81,19 +81,19 @@ void containsTest() {
@Test
void sizeTest() {
Map map = getMap();
- assertEquals(map.size(), 0);
+ assertEquals(0, map.size());
for (int i = -100; i < 100; i++) {
map.put(i, String.valueOf(i));
}
- assertEquals(map.size(), 200);
+ assertEquals(200, map.size());
for (int i = -50; i < 50; i++) {
map.delete(i);
}
- assertEquals(map.size(), 100);
+ assertEquals(100, map.size());
}
@Test
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
index d04a9de8a94b..792969200c82 100644
--- a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java
@@ -2,6 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
@@ -39,7 +40,7 @@ void testEquals() {
assertEquals(element1, element2); // Same key and info
assertNotEquals(element1, element3); // Different key
- assertNotEquals(null, element1); // Check for null
+ assertNotNull(element1);
assertNotEquals("String", element1); // Check for different type
}
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java
index 5483bbcd0394..4390c0f5f2eb 100644
--- a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java
@@ -2,98 +2,51 @@
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.Arrays;
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;
class MergeSortedArrayListTest {
- @Test
- void testMergeTwoSortedLists() {
- List listA = Arrays.asList(1, 3, 5, 7, 9);
- List listB = Arrays.asList(2, 4, 6, 8, 10);
+ @ParameterizedTest(name = "{3}")
+ @MethodSource("provideMergeTestData")
+ void testMergeParameterizedScenarios(List listA, List listB, List expected, String scenarioName) {
List result = new ArrayList<>();
-
MergeSortedArrayList.merge(listA, listB, result);
-
- List expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
- assertEquals(expected, result, "Merged list should be sorted and contain all elements from both input lists.");
+ assertEquals(expected, result, () -> "Failed scenario: " + scenarioName);
}
- @Test
- void testMergeWithEmptyList() {
- List listA = Arrays.asList(1, 2, 3);
- List listB = new ArrayList<>(); // Empty list
- List result = new ArrayList<>();
-
- MergeSortedArrayList.merge(listA, listB, result);
-
- List expected = Arrays.asList(1, 2, 3);
- assertEquals(expected, result, "Merged list should match listA when listB is empty.");
- }
-
- @Test
- void testMergeWithBothEmptyLists() {
- List listA = new ArrayList<>(); // Empty list
- List listB = new ArrayList<>(); // Empty list
- List result = new ArrayList<>();
-
- MergeSortedArrayList.merge(listA, listB, result);
-
- assertTrue(result.isEmpty(), "Merged list should be empty when both input lists are empty.");
+ private static Stream provideMergeTestData() {
+ return Stream.of(Arguments.of(Arrays.asList(1, 3, 5, 7, 9), Arrays.asList(2, 4, 6, 8, 10), Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "Standard alternating sorted lists"), Arguments.of(Arrays.asList(1, 2, 3), new ArrayList<>(), Arrays.asList(1, 2, 3), "Merge with empty second list"),
+ Arguments.of(new ArrayList<>(), Arrays.asList(4, 5, 6), Arrays.asList(4, 5, 6), "Merge with empty first list"), Arguments.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), "Merge with both lists empty"),
+ Arguments.of(Arrays.asList(1, 2, 2, 3), Arrays.asList(2, 3, 4), Arrays.asList(1, 2, 2, 2, 3, 3, 4), "Handling duplicate elements gracefully"),
+ Arguments.of(Arrays.asList(-3, -1, 2), Arrays.asList(-2, 0, 3), Arrays.asList(-3, -2, -1, 0, 2, 3), "Handling negative numbers mixed with positive numbers"));
}
@Test
- void testMergeWithDuplicateElements() {
- List listA = Arrays.asList(1, 2, 2, 3);
- List listB = Arrays.asList(2, 3, 4);
+ void testMergeThrowsExceptionWhenListAIsNull() {
+ List listB = Arrays.asList(1, 2, 3);
List result = new ArrayList<>();
-
- MergeSortedArrayList.merge(listA, listB, result);
-
- List expected = Arrays.asList(1, 2, 2, 2, 3, 3, 4);
- assertEquals(expected, result, "Merged list should correctly handle and include duplicate elements.");
+ assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(null, listB, result));
}
@Test
- void testMergeWithNegativeAndPositiveNumbers() {
- List listA = Arrays.asList(-3, -1, 2);
- List listB = Arrays.asList(-2, 0, 3);
+ void testMergeThrowsExceptionWhenListBIsNull() {
+ List listA = Arrays.asList(1, 2, 3);
List result = new ArrayList<>();
-
- MergeSortedArrayList.merge(listA, listB, result);
-
- List expected = Arrays.asList(-3, -2, -1, 0, 2, 3);
- assertEquals(expected, result, "Merged list should correctly handle negative and positive numbers.");
+ assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, null, result));
}
@Test
- void testMergeThrowsExceptionOnNullInput() {
- List listA = null;
- List listB = Arrays.asList(1, 2, 3);
- List result = new ArrayList<>();
-
- List finalListB = listB;
- List finalListA = listA;
- List finalResult = result;
- assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA, finalListB, finalResult), "Should throw NullPointerException if any input list is null.");
-
- listA = Arrays.asList(1, 2, 3);
- listB = null;
- List finalListA1 = listA;
- List finalListB1 = listB;
- List finalResult1 = result;
- assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA1, finalListB1, finalResult1), "Should throw NullPointerException if any input list is null.");
-
- listA = Arrays.asList(1, 2, 3);
- listB = Arrays.asList(4, 5, 6);
- result = null;
- List finalListA2 = listA;
- List finalListB2 = listB;
- List finalResult2 = result;
- assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA2, finalListB2, finalResult2), "Should throw NullPointerException if the result collection is null.");
+ void testMergeThrowsExceptionWhenResultCollectionIsNull() {
+ List listA = Arrays.asList(1, 2, 3);
+ List listB = Arrays.asList(4, 5, 6);
+ assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, listB, null));
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java
new file mode 100644
index 000000000000..ba5614a07916
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java
@@ -0,0 +1,74 @@
+package com.thealgorithms.datastructures.lists;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Objects;
+import org.junit.jupiter.api.Test;
+
+public class MiddleOfLinkedListTest {
+
+ private static SinglyLinkedListNode listOf(int firstValue, int... remainingValues) {
+ SinglyLinkedListNode head = new SinglyLinkedListNode(firstValue);
+ SinglyLinkedListNode current = head;
+
+ for (int i = 0; i < remainingValues.length; i++) {
+ current.next = new SinglyLinkedListNode(remainingValues[i]);
+ current = current.next;
+ }
+ return head;
+ }
+
+ @Test
+ void middleNodeOddLength() {
+ SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(3, middle.value);
+ }
+
+ @Test
+ void middleNodeEvenLengthReturnsSecondMiddle() {
+ SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5, 6);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(4, middle.value);
+ }
+
+ @Test
+ void middleNodeSingleElement() {
+ SinglyLinkedListNode head = listOf(42);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(42, middle.value);
+ }
+
+ @Test
+ void middleNodeTwoElementsReturnsSecond() {
+ SinglyLinkedListNode head = listOf(10, 20);
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head));
+ assertEquals(20, middle.value);
+ }
+
+ @Test
+ void middleNodeNullHead() {
+ assertNull(MiddleOfLinkedList.middleNode(null));
+ }
+
+ @Test
+ void middleNodeDoesNotModifyListStructure() {
+ SinglyLinkedListNode first = new SinglyLinkedListNode(1);
+ SinglyLinkedListNode second = new SinglyLinkedListNode(2);
+ SinglyLinkedListNode third = new SinglyLinkedListNode(3);
+ SinglyLinkedListNode fourth = new SinglyLinkedListNode(4);
+
+ first.next = second;
+ second.next = third;
+ third.next = fourth;
+
+ SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(first));
+ assertEquals(3, middle.value);
+
+ assertEquals(second, first.next);
+ assertEquals(third, second.next);
+ assertEquals(fourth, third.next);
+ assertNull(fourth.next);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java
similarity index 98%
rename from src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java
rename to src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java
index 0f0577d39094..10f6b8536b19 100644
--- a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java
@@ -1,9 +1,8 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.datastructures.lists;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.thealgorithms.datastructures.lists.SinglyLinkedList;
import org.junit.jupiter.api.Test;
public class PalindromeSinglyLinkedListTest {
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java
new file mode 100644
index 000000000000..1d397cf09301
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java
@@ -0,0 +1,119 @@
+package com.thealgorithms.datastructures.lists;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class SelfOrganizingLinkedListTest {
+
+ private SelfOrganizingLinkedList list;
+
+ @BeforeEach
+ void setUp() {
+ list = new SelfOrganizingLinkedList<>();
+ }
+
+ @Test
+ void testEmptyListAndGetters() {
+ assertTrue(list.isEmpty());
+ assertEquals(0, list.getSize());
+ assertNull(list.getHeadValue());
+ assertFalse(list.search(10));
+ }
+
+ @Test
+ void testInsertAndSizeState() {
+ assertTrue(list.isEmpty());
+ list.insert(10);
+ assertFalse(list.isEmpty());
+ assertEquals(1, list.getSize());
+
+ list.insert(20);
+ assertEquals(2, list.getSize());
+ }
+
+ @Test
+ void testMoveMiddleElementToFrontPreservesFullListStructure() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+ list.insert(40);
+
+ // Initial order: [10, 20, 30, 40]
+ assertTrue(list.search(30));
+
+ // Expected order: [30, 10, 20, 40]
+ assertEquals(4, list.getSize());
+ assertEquals(30, list.getHeadValue());
+
+ // Sequential head tracking to verify middle and tail pointers didn't break
+ assertTrue(list.search(10)); // [10, 30, 20, 40]
+ assertEquals(10, list.getHeadValue());
+
+ assertTrue(list.search(20)); // [20, 10, 30, 40]
+ assertEquals(20, list.getHeadValue());
+
+ assertTrue(list.search(40)); // [40, 20, 10, 30]
+ assertEquals(40, list.getHeadValue());
+ assertEquals(4, list.getSize());
+ }
+
+ @Test
+ void testMoveLastElementToFrontPreservesFullListStructure() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+
+ // Search tail element '30'
+ assertTrue(list.search(30)); // Order becomes [30, 10, 20]
+
+ assertEquals(30, list.getHeadValue());
+ assertEquals(3, list.getSize());
+
+ // Verify remaining chain order [10, 20]
+ assertTrue(list.search(20)); // [20, 30, 10]
+ assertEquals(20, list.getHeadValue());
+
+ assertTrue(list.search(10)); // [10, 20, 30]
+ assertEquals(10, list.getHeadValue());
+ assertEquals(3, list.getSize());
+ }
+
+ @Test
+ void testSearchNonExistentElementPreservesStructureAndSize() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+
+ assertFalse(list.search(99));
+ assertEquals(3, list.getSize());
+ assertEquals(10, list.getHeadValue());
+ }
+
+ @Test
+ void testDuplicateValuesMovesFirstMatchedToFront() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(10); // Duplicate '10' at tail
+ list.insert(30);
+
+ // Initial list state: [10, 20, 10, 30]
+ // Searching '10' hits the head immediately -> no re-linking
+ assertTrue(list.search(10));
+ assertEquals(10, list.getHeadValue());
+ assertEquals(4, list.getSize());
+
+ // Searching '20' moves middle element to head: [20, 10, 10, 30]
+ assertTrue(list.search(20));
+ assertEquals(20, list.getHeadValue());
+
+ // Searching '10' moves the FIRST instance of '10' (index 1) to head: [10, 20, 10, 30]
+ assertTrue(list.search(10));
+ assertEquals(10, list.getHeadValue());
+ assertEquals(4, list.getSize());
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
index e97fe091c556..3bb8bbabb761 100644
--- a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java
@@ -9,14 +9,14 @@ class PriorityQueuesTest {
void testPQInsertion() {
PriorityQueue myQueue = new PriorityQueue(4);
myQueue.insert(2);
- Assertions.assertEquals(myQueue.peek(), 2);
+ Assertions.assertEquals(2, myQueue.peek());
myQueue.insert(5);
myQueue.insert(3);
- Assertions.assertEquals(myQueue.peek(), 5);
+ Assertions.assertEquals(5, myQueue.peek());
myQueue.insert(10);
- Assertions.assertEquals(myQueue.peek(), 10);
+ Assertions.assertEquals(10, myQueue.peek());
}
@Test
@@ -28,32 +28,32 @@ void testPQDeletion() {
myQueue.insert(10);
myQueue.remove();
- Assertions.assertEquals(myQueue.peek(), 5);
+ Assertions.assertEquals(5, myQueue.peek());
myQueue.remove();
myQueue.remove();
- Assertions.assertEquals(myQueue.peek(), 2);
+ Assertions.assertEquals(2, myQueue.peek());
}
@Test
void testPQExtra() {
PriorityQueue myQueue = new PriorityQueue(4);
- Assertions.assertEquals(myQueue.isEmpty(), true);
- Assertions.assertEquals(myQueue.isFull(), false);
+ Assertions.assertTrue(myQueue.isEmpty());
+ Assertions.assertFalse(myQueue.isFull());
myQueue.insert(2);
myQueue.insert(5);
- Assertions.assertEquals(myQueue.isFull(), false);
+ Assertions.assertFalse(myQueue.isFull());
myQueue.insert(3);
myQueue.insert(10);
- Assertions.assertEquals(myQueue.isEmpty(), false);
- Assertions.assertEquals(myQueue.isFull(), true);
+ Assertions.assertFalse(myQueue.isEmpty());
+ Assertions.assertTrue(myQueue.isFull());
myQueue.remove();
- Assertions.assertEquals(myQueue.getSize(), 3);
- Assertions.assertEquals(myQueue.peek(), 5);
+ Assertions.assertEquals(3, myQueue.getSize());
+ Assertions.assertEquals(5, myQueue.peek());
myQueue.remove();
myQueue.remove();
- Assertions.assertEquals(myQueue.peek(), 2);
- Assertions.assertEquals(myQueue.getSize(), 1);
+ Assertions.assertEquals(2, myQueue.peek());
+ Assertions.assertEquals(1, myQueue.getSize());
}
@Test
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/queues/ThreadSafeQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java
new file mode 100644
index 000000000000..4c038c05b167
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java
@@ -0,0 +1,295 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ThreadSafeQueueTest {
+
+ @Test
+ public void testEnqueueDequeue() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(5);
+ queue.enqueue(1);
+ queue.enqueue(2);
+ queue.enqueue(3);
+
+ Assertions.assertEquals(3, queue.size());
+ Assertions.assertEquals(1, queue.dequeue());
+ Assertions.assertEquals(2, queue.dequeue());
+ Assertions.assertEquals(3, queue.dequeue());
+ Assertions.assertTrue(queue.isEmpty());
+ }
+
+ @Test
+ public void testOfferPoll() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(2);
+ Assertions.assertTrue(queue.offer("a"));
+ Assertions.assertTrue(queue.offer("b"));
+ Assertions.assertFalse(queue.offer("c"));
+
+ Assertions.assertEquals("a", queue.poll());
+ Assertions.assertEquals("b", queue.poll());
+ Assertions.assertNull(queue.poll());
+ }
+
+ @Test
+ public void testOfferRejectsWhenFull() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(2);
+ Assertions.assertTrue(queue.offer(1));
+ Assertions.assertTrue(queue.offer(2));
+ Assertions.assertFalse(queue.offer(3));
+ Assertions.assertEquals(2, queue.size());
+ }
+
+ @Test
+ public void testPollReturnsNullWhenEmpty() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(5);
+ Assertions.assertNull(queue.poll());
+ }
+
+ @Test
+ public void testEnqueueNullThrows() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(5);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> queue.enqueue(null));
+ }
+
+ @Test
+ public void testOfferNullThrows() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(5);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> queue.offer(null));
+ }
+
+ @Test
+ public void testInvalidCapacityThrows() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(0));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(-1));
+ }
+
+ @Test
+ public void testIsEmptyAndIsFull() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(2);
+ Assertions.assertTrue(queue.isEmpty());
+ Assertions.assertFalse(queue.isFull());
+
+ queue.enqueue(1);
+ Assertions.assertFalse(queue.isEmpty());
+ Assertions.assertFalse(queue.isFull());
+
+ queue.enqueue(2);
+ Assertions.assertFalse(queue.isEmpty());
+ Assertions.assertTrue(queue.isFull());
+
+ queue.dequeue();
+ Assertions.assertFalse(queue.isEmpty());
+ Assertions.assertFalse(queue.isFull());
+
+ queue.dequeue();
+ Assertions.assertTrue(queue.isEmpty());
+ Assertions.assertFalse(queue.isFull());
+ }
+
+ @Test
+ public void testCapacity() {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(10);
+ Assertions.assertEquals(10, queue.capacity());
+ }
+
+ @Test
+ public void testCircularBufferWrapAround() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(3);
+ queue.enqueue(1);
+ queue.enqueue(2);
+ queue.enqueue(3);
+
+ Assertions.assertEquals(1, queue.dequeue());
+ Assertions.assertEquals(2, queue.dequeue());
+
+ queue.enqueue(4);
+ queue.enqueue(5);
+
+ Assertions.assertEquals(3, queue.dequeue());
+ Assertions.assertEquals(4, queue.dequeue());
+ Assertions.assertEquals(5, queue.dequeue());
+ }
+
+ @Test
+ public void testMultipleProducersSingleConsumer() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(100);
+ int numProducers = 4;
+ int itemsPerProducer = 250;
+ int totalItems = numProducers * itemsPerProducer;
+ CountDownLatch doneLatch = new CountDownLatch(numProducers);
+ List results = new ArrayList<>();
+
+ ExecutorService executor = Executors.newFixedThreadPool(numProducers + 1);
+
+ for (int p = 0; p < numProducers; p++) {
+ final int producerId = p;
+ executor.submit(() -> {
+ try {
+ for (int i = 0; i < itemsPerProducer; i++) {
+ queue.enqueue(producerId * itemsPerProducer + i);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } finally {
+ doneLatch.countDown();
+ }
+ });
+ }
+
+ Thread consumerThread = new Thread(() -> {
+ try {
+ while (results.size() < totalItems) {
+ Integer item = queue.poll();
+ if (item != null) {
+ synchronized (results) {
+ results.add(item);
+ }
+ }
+ }
+ } catch (Exception e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ consumerThread.start();
+
+ Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS));
+ consumerThread.join(5000);
+
+ Assertions.assertEquals(totalItems, results.size());
+ executor.shutdown();
+ Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testSingleProducerMultipleConsumers() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(50);
+ int numConsumers = 4;
+ int totalItems = 1000;
+ CountDownLatch doneLatch = new CountDownLatch(numConsumers);
+ AtomicInteger consumedCount = new AtomicInteger(0);
+
+ ExecutorService executor = Executors.newFixedThreadPool(numConsumers + 1);
+
+ executor.submit(() -> {
+ try {
+ for (int i = 0; i < totalItems; i++) {
+ queue.enqueue(i);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+
+ for (int c = 0; c < numConsumers; c++) {
+ executor.submit(() -> {
+ try {
+ while (consumedCount.get() < totalItems) {
+ Integer item = queue.poll();
+ if (item != null) {
+ consumedCount.incrementAndGet();
+ }
+ }
+ } finally {
+ doneLatch.countDown();
+ }
+ });
+ }
+
+ Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS));
+ Assertions.assertEquals(totalItems, consumedCount.get());
+ executor.shutdown();
+ Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testBlockingEnqueueWhenFull() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(1);
+ queue.enqueue(1);
+
+ AtomicInteger blockedCount = new AtomicInteger(0);
+ Thread producer = new Thread(() -> {
+ try {
+ queue.enqueue(2);
+ blockedCount.incrementAndGet();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ producer.start();
+
+ Thread.sleep(100);
+ Assertions.assertEquals(1, queue.dequeue());
+
+ producer.join(2000);
+ Assertions.assertEquals(1, blockedCount.get());
+ Assertions.assertEquals(2, queue.dequeue());
+ }
+
+ @Test
+ public void testBlockingDequeueWhenEmpty() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(5);
+
+ AtomicInteger result = new AtomicInteger(-1);
+ Thread consumer = new Thread(() -> {
+ try {
+ result.set(queue.dequeue());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ consumer.start();
+
+ Thread.sleep(100);
+ queue.enqueue(42);
+
+ consumer.join(2000);
+ Assertions.assertEquals(42, result.get());
+ }
+
+ @Test
+ public void testStressConcurrentAccess() throws InterruptedException {
+ ThreadSafeQueue queue = new ThreadSafeQueue<>(10);
+ int numThreads = 8;
+ int opsPerThread = 500;
+ CountDownLatch latch = new CountDownLatch(numThreads);
+ AtomicInteger enqueueCount = new AtomicInteger(0);
+ AtomicInteger dequeueCount = new AtomicInteger(0);
+
+ ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+
+ for (int t = 0; t < numThreads; t++) {
+ final boolean isProducer = t % 2 == 0;
+ executor.submit(() -> {
+ try {
+ for (int i = 0; i < opsPerThread; i++) {
+ if (isProducer) {
+ if (queue.offer(i)) {
+ enqueueCount.incrementAndGet();
+ }
+ } else {
+ if (queue.poll() != null) {
+ dequeueCount.incrementAndGet();
+ }
+ }
+ }
+ } finally {
+ latch.countDown();
+ }
+ });
+ }
+
+ Assertions.assertTrue(latch.await(10, TimeUnit.SECONDS));
+ Assertions.assertTrue(enqueueCount.get() >= dequeueCount.get());
+ Assertions.assertEquals(enqueueCount.get() - dequeueCount.get(), queue.size());
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java
similarity index 97%
rename from src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java
rename to src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java
index 29189290e1d4..f0cb1724f67c 100644
--- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.tree;
+package com.thealgorithms.datastructures.trees;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
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/datastructures/trees/SegmentTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java
new file mode 100644
index 000000000000..196c32575709
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java
@@ -0,0 +1,96 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class SegmentTreeTest {
+
+ private static SegmentTree treeOf(int... values) {
+ return new SegmentTree(values.length, values);
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0, 4, 15", "0, 0, 1", "4, 4, 5", "1, 3, 9", "2, 4, 12"})
+ void testRangeSums(int start, int end, int expected) {
+ assertEquals(expected, treeOf(1, 2, 3, 4, 5).getSum(start, end));
+ }
+
+ @Test
+ void testSingleElementTree() {
+ SegmentTree tree = treeOf(42);
+ assertEquals(42, tree.getSum(0, 0));
+ tree.update(0, 7);
+ assertEquals(7, tree.getSum(0, 0));
+ }
+
+ @Test
+ void testUpdateIsReflectedInSubsequentQueries() {
+ SegmentTree tree = treeOf(1, 2, 3, 4, 5);
+ tree.update(2, 10);
+ assertEquals(22, tree.getSum(0, 4));
+ assertEquals(16, tree.getSum(1, 3));
+ tree.update(0, -1);
+ assertEquals(20, tree.getSum(0, 4));
+ }
+
+ @Test
+ void testNegativeValues() {
+ SegmentTree tree = treeOf(-5, 3, -2, 8);
+ assertEquals(4, tree.getSum(0, 3));
+ assertEquals(-4, tree.getSum(0, 2));
+ }
+
+ /**
+ * index == n is past the last element, so it must be rejected by the guard instead of reaching
+ * the backing array and throwing {@link ArrayIndexOutOfBoundsException}.
+ */
+ @ParameterizedTest
+ @ValueSource(ints = {5, 6, 100, -1})
+ void testUpdateOutOfRangeIndexIsIgnored(int index) {
+ SegmentTree tree = treeOf(1, 2, 3, 4, 5);
+ assertDoesNotThrow(() -> tree.update(index, 99));
+ assertEquals(15, tree.getSum(0, 4), "out of range update must not modify the tree");
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0, 5", "0, 6", "3, 2", "-1, 3", "5, 5"})
+ void testOutOfRangeQueriesReturnZero(int start, int end) {
+ assertEquals(0, treeOf(1, 2, 3, 4, 5).getSum(start, end));
+ }
+
+ @Test
+ void testConstructorRejectsInvalidSize() {
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(0, new int[] {1, 2, 3}));
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(-1, new int[] {1, 2, 3}));
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(4, new int[] {1, 2, 3}));
+ }
+
+ @Test
+ void testConstructorRejectsNullArray() {
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(3, null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17})
+ void testMatchesBruteForceForVariousSizes(int size) {
+ int[] values = new int[size];
+ for (int i = 0; i < size; i++) {
+ values[i] = i * 3 - 4;
+ }
+ SegmentTree tree = new SegmentTree(size, values.clone());
+
+ for (int start = 0; start < size; start++) {
+ int expected = 0;
+ for (int end = start; end < size; end++) {
+ expected += values[end];
+ assertEquals(expected, tree.getSum(start, end), "sum of [" + start + ", " + end + "] with size " + size);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
index 09ada594faca..52b74a7a1faf 100644
--- a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
@@ -2,6 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
@@ -30,7 +31,7 @@ public void searchAndNotFound() {
treap.insert(3);
treap.insert(8);
treap.insert(1);
- assertEquals(null, treap.search(4));
+ assertNull(treap.search(4));
}
@Test
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java
new file mode 100644
index 000000000000..592170673a3a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java
@@ -0,0 +1,117 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class WaveletTreeTest {
+
+ @Test
+ public void testRank() {
+ int[] arr = {5, 1, 2, 5, 1};
+ WaveletTree wt = new WaveletTree(arr);
+
+ // x = 1
+ assertEquals(1, wt.rank(1, 1)); // In [5, 1], '1' appears 1 time
+ assertEquals(2, wt.rank(1, 4)); // In [5, 1, 2, 5, 1], '1' appears 2 times
+ assertEquals(0, wt.rank(1, 0)); // In [5], '1' appears 0 times
+
+ // x = 5
+ assertEquals(1, wt.rank(5, 0)); // In [5], '5' appears 1 time
+ assertEquals(1, wt.rank(5, 2)); // In [5, 1, 2], '5' appears 1 time
+ assertEquals(2, wt.rank(5, 4)); // In [5, 1, 2, 5, 1], '5' appears 2 times
+
+ // Out of bounds / invalid value
+ assertEquals(0, wt.rank(10, 4)); // '10' is not in the array
+ assertEquals(0, wt.rank(5, -1)); // Invalid end index
+ }
+
+ @Test
+ public void testSelect() {
+ int[] arr = {5, 1, 2, 5, 1};
+ WaveletTree wt = new WaveletTree(arr);
+
+ assertEquals(1, wt.select(1, 1)); // 1st '1' is at index 1
+ assertEquals(4, wt.select(1, 2)); // 2nd '1' is at index 4
+
+ assertEquals(0, wt.select(5, 1)); // 1st '5' is at index 0
+ assertEquals(3, wt.select(5, 2)); // 2nd '5' is at index 3
+
+ assertEquals(2, wt.select(2, 1)); // 1st '2' is at index 2
+
+ assertEquals(-1, wt.select(5, 3)); // 3rd '5' doesn't exist
+ assertEquals(-1, wt.select(10, 1)); // '10' doesn't exist
+ assertEquals(-1, wt.select(5, 0)); // invalid k
+ }
+
+ @Test
+ public void testKthSmallest() {
+ int[] arr = {5, 1, 2, 5, 1};
+ WaveletTree wt = new WaveletTree(arr);
+
+ // Array: [5, 1, 2, 5, 1] -> Sorted: [1, 1, 2, 5, 5]
+ assertEquals(1, wt.kthSmallest(0, 4, 1)); // 1st smallest in [5, 1, 2, 5, 1] is 1
+ assertEquals(1, wt.kthSmallest(0, 4, 2)); // 2nd smallest in [5, 1, 2, 5, 1] is 1
+ assertEquals(2, wt.kthSmallest(0, 4, 3)); // 3rd smallest in [5, 1, 2, 5, 1] is 2
+ assertEquals(5, wt.kthSmallest(0, 4, 4)); // 4th smallest in [5, 1, 2, 5, 1] is 5
+ assertEquals(5, wt.kthSmallest(0, 4, 5)); // 5th smallest in [5, 1, 2, 5, 1] is 5
+
+ // Subarray: arr[1..3] = [1, 2, 5] -> Sorted: [1, 2, 5]
+ assertEquals(1, wt.kthSmallest(1, 3, 1)); // 1st smallest in [1, 2, 5] is 1
+ assertEquals(2, wt.kthSmallest(1, 3, 2)); // 2nd smallest in [1, 2, 5] is 2
+ assertEquals(5, wt.kthSmallest(1, 3, 3)); // 3rd smallest in [1, 2, 5] is 5
+
+ // Invalid ranges / arguments
+ assertEquals(-1, wt.kthSmallest(4, 2, 1)); // Invalid range (left > right)
+ assertEquals(-1, wt.kthSmallest(0, 4, 10)); // k > range length
+ assertEquals(-1, wt.kthSmallest(0, 4, 0)); // k < 1
+ }
+
+ @Test
+ public void testEmptyAndSingleElementArray() {
+ WaveletTree wtEmpty = new WaveletTree(new int[] {});
+ assertEquals(0, wtEmpty.rank(1, 0));
+ assertEquals(-1, wtEmpty.select(1, 1));
+ assertEquals(-1, wtEmpty.kthSmallest(0, 0, 1));
+
+ WaveletTree wtSingle = new WaveletTree(new int[] {42});
+ assertEquals(1, wtSingle.rank(42, 0));
+ assertEquals(0, wtSingle.rank(42, -1));
+ assertEquals(0, wtSingle.select(42, 1));
+ assertEquals(-1, wtSingle.select(42, 2));
+ assertEquals(42, wtSingle.kthSmallest(0, 0, 1));
+ }
+
+ @Test
+ public void testNullArrayAndCustomBounds() {
+ WaveletTree wtNull = new WaveletTree(null);
+ assertEquals(0, wtNull.rank(1, 0));
+
+ WaveletTree wtNullCustom = new WaveletTree(null, 1, 5);
+ assertEquals(-1, wtNullCustom.select(1, 1));
+
+ int[] arr = {5, 1, 2, 5, 1};
+ WaveletTree wtCustom = new WaveletTree(arr, 1, 10);
+ assertEquals(2, wtCustom.rank(5, 4));
+ assertEquals(0, wtCustom.rank(4, 4)); // Query an element inside bounds but not in array
+ assertEquals(0, wtCustom.rank(10, 4)); // Query upper bound
+ }
+
+ @Test
+ public void testNegativeValues() {
+ int[] arr = {-5, 10, -2, 0, -5};
+ WaveletTree wt = new WaveletTree(arr);
+
+ assertEquals(2, wt.rank(-5, 4));
+ assertEquals(1, wt.rank(0, 3));
+
+ assertEquals(0, wt.select(-5, 1));
+ assertEquals(4, wt.select(-5, 2));
+ assertEquals(3, wt.select(0, 1));
+
+ // Sorted: [-5, -5, -2, 0, 10]
+ assertEquals(-5, wt.kthSmallest(0, 4, 1));
+ assertEquals(-2, wt.kthSmallest(0, 4, 3));
+ assertEquals(10, wt.kthSmallest(0, 4, 5));
+ }
+}
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/DigitDPTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java
new file mode 100644
index 000000000000..762fe86d4d65
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java
@@ -0,0 +1,70 @@
+package com.thealgorithms.dynamicprogramming;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the generalized DigitDP implementation.
+ */
+public class DigitDPTest {
+
+ @Test
+ public void testDigitDPBasicRange() {
+ // Numbers between 1 and 20 with a digit sum of 5: 5, 14
+ long result = DigitDP.countRangeWithDigitSum(1, 20, 5);
+ assertEquals(2, result);
+ }
+
+ @Test
+ public void testDigitDPZeroBound() {
+ // Number 0 has a digit sum of 0
+ long result = DigitDP.countRangeWithDigitSum(0, 0, 0);
+ assertEquals(1, result);
+ }
+
+ @Test
+ public void testDigitDPLargeRange() {
+ // Count numbers between 1 and 100 with a digit sum of 9
+ // 9, 18, 27, 36, 45, 54, 63, 72, 81, 90 (10 numbers)
+ long result = DigitDP.countRangeWithDigitSum(1, 100, 9);
+ assertEquals(10, result);
+ }
+
+ @Test
+ public void testDigitDPNoMatches() {
+ // No numbers between 10 and 15 can have a digit sum of 20
+ long result = DigitDP.countRangeWithDigitSum(10, 15, 20);
+ assertEquals(0, result);
+ }
+
+ @Test
+ public void testDigitDPExceedsMaxSum() {
+ // Sum condition that exceeds max possible physical sum array constraints
+ // gracefully returns 0
+ long result = DigitDP.countRangeWithDigitSum(1, 100, 200);
+ assertEquals(0, result);
+ }
+
+ @Test
+ public void testDigitDPInvalidRange() {
+ // Lower bound greater than upper bound should evaluate gracefully to 0
+ long result = DigitDP.countRangeWithDigitSum(50, 20, 5);
+ assertEquals(0, result);
+ }
+
+ @Test
+ public void testDigitDPExceedsMaxSumEdgeCase() {
+ // Yeh test case target > MAX_DIGIT_SUM wali condition ko hit karega
+ long result = DigitDP.countRangeWithDigitSum(1, 100, 180);
+ assertEquals(0, result);
+ }
+
+ @Test
+ public void testDigitDPMemoizationHit() {
+ // Badi range dene se overlapping subproblems bante hain,
+ // jisse memoization hit hogi aur coverage 100% ho jayegi.
+ long result1 = DigitDP.countRangeWithDigitSum(1, 100000, 15);
+ long result2 = DigitDP.countRangeWithDigitSum(1, 100000, 15);
+ assertEquals(result1, result2);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
index 40bbdff15ca6..91169c4cc9d8 100644
--- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
@@ -1,6 +1,7 @@
package com.thealgorithms.dynamicprogramming;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
@@ -55,27 +56,24 @@ public void testLCSWithBothEmptyStrings() {
public void testLCSWithNullFirstString() {
String str1 = null;
String str2 = "XYZ";
- String expected = null; // Should return null if first string is null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
public void testLCSWithNullSecondString() {
String str1 = "ABC";
String str2 = null;
- String expected = null; // Should return null if second string is null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
public void testLCSWithNullBothStrings() {
String str1 = null;
String str2 = null;
- String expected = null; // Should return null if both strings are null
String result = LongestCommonSubsequence.getLCS(str1, str2);
- assertEquals(expected, result);
+ assertNull(result);
}
@Test
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java
new file mode 100644
index 000000000000..a1ee624e94d2
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.dynamicprogramming;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class LongestPalindromicSubsequenceTest {
+
+ @ParameterizedTest
+ @CsvSource({"BBABCBCAB, BACBCAB", "BABCBAB, BABCBAB", "A, A", "AA, AA", "AB, B"})
+ void testLpsKnownCases(String input, String expectedLps) {
+ assertEquals(expectedLps, LongestPalindromicSubsequence.lps(input));
+ }
+
+ @Test
+ void testLpsEmptyString() {
+ assertEquals("", LongestPalindromicSubsequence.lps(""));
+ }
+
+ @Test
+ void testLpsSingleCharacter() {
+ assertEquals("Z", LongestPalindromicSubsequence.lps("Z"));
+ }
+
+ @Test
+ void testLpsAllSameCharacters() {
+ assertEquals("AAAA", LongestPalindromicSubsequence.lps("AAAA"));
+ }
+
+ @Test
+ void testLpsAlreadyPalindrome() {
+ assertEquals("RACECAR", LongestPalindromicSubsequence.lps("RACECAR"));
+ }
+
+ @Test
+ void testLpsNoRepeatingCharacters() {
+ assertEquals(1, LongestPalindromicSubsequence.lps("ABCDE").length());
+ }
+
+ @Test
+ void testLpsNullThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> { LongestPalindromicSubsequence.lps(null); });
+ }
+
+ @Test
+ void testLpsResultIsActuallyPalindrome() {
+ String result = LongestPalindromicSubsequence.lps("BBABCBCAB");
+ String reversed = new StringBuilder(result).reverse().toString();
+ assertEquals(result, reversed);
+ }
+}
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 |