This filter applies an exponential moving average to a sequence of audio
+ *
+ * This filter applies an exponential moving average to a sequence of audio
* signal values, making it useful for smoothing out rapid fluctuations.
* The smoothing factor (alpha) controls the degree of smoothing.
*
- *
Based on the definition from
+ *
+ * Based on the definition from
* Wikipedia link.
*/
public class EMAFilter {
private final double alpha;
private double emaValue;
+
/**
* Constructs an EMA filter with a given smoothing factor.
*
@@ -26,14 +29,17 @@ public EMAFilter(double alpha) {
this.alpha = alpha;
this.emaValue = 0.0;
}
+
/**
* Applies the EMA filter to an audio signal array.
+ * EMA formula:
+ * EMA = alpha * currentSample + (1 - alpha) * previousEMA
*
* @param audioSignal Array of audio samples to process
* @return Array of processed (smoothed) samples
*/
public double[] apply(double[] audioSignal) {
- if (audioSignal.length == 0) {
+ if (audioSignal == null || audioSignal.length == 0) {
return new double[0];
}
double[] emaSignal = new double[audioSignal.length];
diff --git a/src/main/java/com/thealgorithms/backtracking/NQueens.java b/src/main/java/com/thealgorithms/backtracking/NQueens.java
index 1a8e453e34cb..404f677738a0 100644
--- a/src/main/java/com/thealgorithms/backtracking/NQueens.java
+++ b/src/main/java/com/thealgorithms/backtracking/NQueens.java
@@ -1,7 +1,9 @@
package com.thealgorithms.backtracking;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Problem statement: Given a N x N chess board. Return all arrangements in
@@ -32,7 +34,22 @@
* queen is not placed safely. If there is no such way then return an empty list
* as solution
*/
+
+/*
+ * Time Complexity: O(N!)
+ * space Complexity: O(N)
+ */
public final class NQueens {
+
+ // Store occupied rows for constant time safety check
+ private static final Set OCROWS = new HashSet<>();
+
+ // Store occupied main diagonals (row - column)
+ private static final Set OCDIAG = new HashSet<>();
+
+ // Store occupied anti-diagonals (row + columns)
+ private static final Set OCANTIDIAG = new HashSet<>();
+
private NQueens() {
}
@@ -43,10 +60,10 @@ public static List> getNQueensArrangements(int queens) {
}
public static void placeQueens(final int queens) {
- List> arrangements = new ArrayList>();
+ List> arrangements = new ArrayList<>();
getSolution(queens, arrangements, new int[queens], 0);
if (arrangements.isEmpty()) {
- System.out.println("There is no way to place " + queens + " queens on board of size " + queens + "x" + queens);
+ System.out.println(" no way to place " + queens + " queens on board of size " + queens + "x" + queens);
} else {
System.out.println("Arrangement for placing " + queens + " queens");
}
@@ -59,15 +76,15 @@ public static void placeQueens(final int queens) {
/**
* This is backtracking function which tries to place queen recursively
*
- * @param boardSize: size of chess board
- * @param solutions: this holds all possible arrangements
- * @param columns: columns[i] = rowId where queen is placed in ith column.
+ * @param boardSize: size of chess board
+ * @param solutions: this holds all possible arrangements
+ * @param columns: columns[i] = rowId where queen is placed in ith column.
* @param columnIndex: This is the column in which queen is being placed
*/
private static void getSolution(int boardSize, List> solutions, int[] columns, int columnIndex) {
if (columnIndex == boardSize) {
// this means that all queens have been placed
- List sol = new ArrayList();
+ List sol = new ArrayList<>();
for (int i = 0; i < boardSize; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < boardSize; j++) {
@@ -82,30 +99,29 @@ private static void getSolution(int boardSize, List> solutions, int
// This loop tries to place queen in a row one by one
for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
columns[columnIndex] = rowIndex;
- if (isPlacedCorrectly(columns, rowIndex, columnIndex)) {
- // If queen is placed successfully at rowIndex in column=columnIndex then try
- // placing queen in next column
- getSolution(boardSize, solutions, columns, columnIndex + 1);
- }
- }
- }
- /**
- * This function checks if queen can be placed at row = rowIndex in column =
- * columnIndex safely
- *
- * @param columns: columns[i] = rowId where queen is placed in ith column.
- * @param rowIndex: row in which queen has to be placed
- * @param columnIndex: column in which queen is being placed
- * @return true: if queen can be placed safely false: otherwise
- */
- private static boolean isPlacedCorrectly(int[] columns, int rowIndex, int columnIndex) {
- for (int i = 0; i < columnIndex; i++) {
- int diff = Math.abs(columns[i] - rowIndex);
- if (diff == 0 || columnIndex - i == diff) {
- return false;
+ // Skip current position if row or diagonal is already occupied
+ boolean isROp = OCROWS.contains(rowIndex);
+
+ boolean isDOp = OCDIAG.contains(rowIndex - columnIndex) || OCANTIDIAG.contains(rowIndex + columnIndex);
+
+ if (isROp || isDOp) {
+ continue;
}
+
+ // Mark current row and diagonal as occupied
+ OCROWS.add(rowIndex);
+ OCDIAG.add(rowIndex - columnIndex);
+ OCANTIDIAG.add(rowIndex + columnIndex);
+
+ // Move to the next column after placing current queen
+ getSolution(boardSize, solutions, columns, columnIndex + 1);
+
+ // Backtrack by removing current queen
+
+ OCROWS.remove(rowIndex);
+ OCDIAG.remove(rowIndex - columnIndex);
+ OCANTIDIAG.remove(rowIndex + columnIndex);
}
- return true;
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java
new file mode 100644
index 000000000000..183b4bbd97f8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java
@@ -0,0 +1,119 @@
+package com.thealgorithms.backtracking;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rat in a Maze Problem using Backtracking.
+ *
+ *
Given an {@code n x n} binary maze where {@code 1} represents an open cell
+ * and {@code 0} represents a blocked cell, find all paths for a rat starting at
+ * the top-left cell {@code (0, 0)} to reach the bottom-right cell {@code (n-1, n-1)}.
+ *
+ *
The rat can move in four directions: Up (U), Down (D), Left (L), Right (R).
+ * Each cell may be visited at most once per path.
+ *
+ *
Time Complexity: O(4^(n²)) in the worst case (four choices per cell).
+ * Space Complexity: O(n²) for the visited matrix and recursion stack.
+ *
+ *
+ *
+ * @see Maze solving algorithm
+ * @author the-Sunny-Sharma (GitHub)
+ */
+public final class RatInAMaze {
+
+ private RatInAMaze() {
+ }
+
+ /**
+ * Finds all paths from the top-left to the bottom-right of the given maze.
+ *
+ * @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked
+ * @return a sorted list of all valid path strings using directions D, L, R, U;
+ * an empty list if no path exists
+ * @throws IllegalArgumentException if the maze is null, empty, or not square
+ */
+ public static List findPaths(final int[][] maze) {
+ if (maze == null || maze.length == 0) {
+ throw new IllegalArgumentException("Maze must not be null or empty.");
+ }
+ int n = maze.length;
+ for (int[] row : maze) {
+ if (row.length != n) {
+ throw new IllegalArgumentException("Maze must be a square (n x n) matrix.");
+ }
+ }
+ List results = new ArrayList<>();
+ if (maze[0][0] == 0 || maze[n - 1][n - 1] == 0) {
+ return results;
+ }
+ boolean[][] visited = new boolean[n][n];
+ solve(maze, 0, 0, n, "", visited, results);
+ return results;
+ }
+
+ /**
+ * Recursive backtracking helper that explores all four directions.
+ *
+ * @param maze the binary maze
+ * @param row current row position
+ * @param col current column position
+ * @param n maze dimension
+ * @param path path string built so far
+ * @param visited tracks visited cells for the current path
+ * @param results accumulates complete paths
+ */
+ private static void solve(final int[][] maze, final int row, final int col, final int n, final String path, final boolean[][] visited, final List results) {
+ // Base case: reached destination
+ if (row == n - 1 && col == n - 1) {
+ results.add(path);
+ return;
+ }
+
+ // Mark current cell as visited
+ visited[row][col] = true;
+
+ // Explore in alphabetical order: Down, Left, Right, Up
+ // Down
+ if (isSafe(maze, row + 1, col, n, visited)) {
+ solve(maze, row + 1, col, n, path + 'D', visited, results);
+ }
+ // Left
+ if (isSafe(maze, row, col - 1, n, visited)) {
+ solve(maze, row, col - 1, n, path + 'L', visited, results);
+ }
+ // Right
+ if (isSafe(maze, row, col + 1, n, visited)) {
+ solve(maze, row, col + 1, n, path + 'R', visited, results);
+ }
+ // Up
+ if (isSafe(maze, row - 1, col, n, visited)) {
+ solve(maze, row - 1, col, n, path + 'U', visited, results);
+ }
+
+ // Backtrack: unmark current cell
+ visited[row][col] = false;
+ }
+
+ /**
+ * Checks whether moving to {@code (row, col)} is valid.
+ *
+ * @param maze the binary maze
+ * @param row target row
+ * @param col target column
+ * @param n maze dimension
+ * @param visited tracks visited cells for the current path
+ * @return {@code true} if the cell is within bounds, open, and not yet visited
+ */
+ private static boolean isSafe(final int[][] maze, final int row, final int col, final int n, final boolean[][] visited) {
+ return row >= 0 && row < n && col >= 0 && col < n && maze[row][col] == 1 && !visited[row][col];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
index 0d6fd140c720..5038d44079ec 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
@@ -9,6 +9,12 @@
*
*
* @author Hardvan
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
*/
public final class BinaryPalindromeCheck {
private BinaryPalindromeCheck() {
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
index 3d31cb3e7f6c..314e7fba38a3 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
@@ -1,10 +1,4 @@
-/**
- * [Brief description of what the algorithm does]
- *
- * Time Complexity: O(n) [or appropriate complexity]
- * Space Complexity: O(n)
- * @author Reshma Kakkirala
- */
+
package com.thealgorithms.conversions;
import java.util.Arrays;
diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java
index 5219c4ba7f4e..fb4411b399a3 100644
--- a/src/main/java/com/thealgorithms/conversions/Base64.java
+++ b/src/main/java/com/thealgorithms/conversions/Base64.java
@@ -119,8 +119,15 @@ public static byte[] decode(String input) {
// Validate padding: '=' can only appear at the end (last 1 or 2 chars)
int firstPadding = input.indexOf('=');
- if (firstPadding != -1 && firstPadding < input.length() - 2) {
- throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)");
+ if (firstPadding != -1) {
+ if (firstPadding < input.length() - 2) {
+ throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)");
+ }
+ for (int i = firstPadding; i < input.length(); i++) {
+ if (input.charAt(i) != '=') {
+ throw new IllegalArgumentException("A padding '=' must not be followed by a non-padding character");
+ }
+ }
}
List result = new ArrayList<>();
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java
index 70699a9461f7..1b5f765843be 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java
@@ -1,6 +1,7 @@
package com.thealgorithms.datastructures.graphs;
import java.util.Arrays;
+import java.util.PriorityQueue;
/**
* Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph.
@@ -18,6 +19,21 @@ public DijkstraAlgorithm(int vertexCount) {
this.vertexCount = vertexCount;
}
+ private static class Node implements Comparable {
+ int id;
+ int distance;
+
+ Node(int id, int distance) {
+ this.id = id;
+ this.distance = distance;
+ }
+
+ @Override
+ public int compareTo(Node other) {
+ return Integer.compare(this.distance, other.distance);
+ }
+ }
+
/**
* Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices.
*
@@ -36,18 +52,25 @@ public int[] run(int[][] graph, int source) {
int[] distances = new int[vertexCount];
boolean[] processed = new boolean[vertexCount];
+ PriorityQueue unprocessed = new PriorityQueue<>();
Arrays.fill(distances, Integer.MAX_VALUE);
- Arrays.fill(processed, false);
distances[source] = 0;
+ unprocessed.add(new Node(source, 0));
+
+ while (!unprocessed.isEmpty()) {
+ Node current = unprocessed.poll();
+ int u = current.id;
- for (int count = 0; count < vertexCount - 1; count++) {
- int u = getMinDistanceVertex(distances, processed);
+ if (processed[u]) {
+ continue;
+ }
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]) {
distances[v] = distances[u] + graph[u][v];
+ unprocessed.add(new Node(v, distances[v]));
}
}
}
@@ -56,27 +79,6 @@ public int[] run(int[][] graph, int source) {
return distances;
}
- /**
- * Finds the vertex with the minimum distance value from the set of vertices that have not yet been processed.
- *
- * @param distances The array of current shortest distances from the source vertex.
- * @param processed The array indicating whether each vertex has been processed.
- * @return The index of the vertex with the minimum distance value.
- */
- private int getMinDistanceVertex(int[] distances, boolean[] processed) {
- int min = Integer.MAX_VALUE;
- int minIndex = -1;
-
- for (int v = 0; v < vertexCount; v++) {
- if (!processed[v] && distances[v] <= min) {
- min = distances[v];
- minIndex = v;
- }
- }
-
- return minIndex;
- }
-
/**
* Prints the shortest distances from the source vertex to all other vertices.
*
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
deleted file mode 100644
index a686b808a970..000000000000
--- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.thealgorithms.datastructures.graphs;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.TreeSet;
-import org.apache.commons.lang3.tuple.Pair;
-
-/**
- * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph.
- */
-public class DijkstraOptimizedAlgorithm {
-
- private final int vertexCount;
-
- /**
- * Constructs a Dijkstra object with the given number of vertices.
- *
- * @param vertexCount The number of vertices in the graph.
- */
- public DijkstraOptimizedAlgorithm(int vertexCount) {
- this.vertexCount = vertexCount;
- }
-
- /**
- * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices.
- *
- * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i}
- * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices.
- *
- * @param graph The graph represented as an adjacency matrix.
- * @param source The source vertex.
- * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}.
- * @throws IllegalArgumentException if the source vertex is out of range.
- */
- public int[] run(int[][] graph, int source) {
- if (source < 0 || source >= vertexCount) {
- throw new IllegalArgumentException("Incorrect source");
- }
-
- int[] distances = new int[vertexCount];
- boolean[] processed = new boolean[vertexCount];
- Set> unprocessed = new TreeSet<>();
-
- Arrays.fill(distances, Integer.MAX_VALUE);
- Arrays.fill(processed, false);
- distances[source] = 0;
- unprocessed.add(Pair.of(0, source));
-
- while (!unprocessed.isEmpty()) {
- Pair 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/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/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/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/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/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/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/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/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/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/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/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/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
index d24cc1c774bc..52dbc0b7e2c5 100644
--- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
+++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
@@ -1,14 +1,4 @@
-/**
- * Interpolation Search estimates the position of the target value
- * based on the distribution of values.
- *
- * Example:
- * Input: [10, 20, 30, 40], target = 30
- * Output: Index = 2
- *
- * Time Complexity: O(log log n) (average case)
- * Space Complexity: O(1)
- */
+
package com.thealgorithms.searches;
/**
@@ -46,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/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java
index 3f273e167f0a..c5f6e6ba9776 100644
--- a/src/main/java/com/thealgorithms/searches/LinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java
@@ -1,16 +1,4 @@
-/**
- * Performs Linear Search on an array.
- *
- * Linear search checks each element one by one until the target is found
- * or the array ends.
- *
- * Example:
- * Input: [2, 4, 6, 8], target = 6
- * Output: Index = 2
- *
- * Time Complexity: O(n)
- * Space Complexity: O(1)
- */
+
package com.thealgorithms.searches;
import com.thealgorithms.devutils.searches.SearchAlgorithm;
@@ -22,10 +10,34 @@
*
* 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
+ *
+ * If target = 7:
+ * Output: -1 (not found)
+ *
* Time Complexity:
- * - Best case: O(1)
- * - Average case: O(n)
- * - Worst case: O(n)
+ * - 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
*
* Space Complexity: O(1)
*
@@ -37,9 +49,10 @@
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, -1 if array is null or empty, or key not found
*/
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/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/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/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/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/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/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/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/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/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/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/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/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/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/maths/FriendlyNumberTest.java b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java
new file mode 100644
index 000000000000..be5ddd7ee79e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class FriendlyNumberTest {
+
+ @Test
+ public void testFriendlyNumbers() {
+ // 6 and 28 are friendly (abundancy index = 2)
+ assertTrue(FriendlyNumber.areFriendly(6, 28));
+ // Every number is friendly with itself
+ assertTrue(FriendlyNumber.areFriendly(6, 6));
+ assertTrue(FriendlyNumber.areFriendly(1, 1));
+ }
+
+ @Test
+ public void testNonFriendlyNumbers() {
+ assertFalse(FriendlyNumber.areFriendly(6, 10));
+ assertFalse(FriendlyNumber.areFriendly(10, 15));
+ assertFalse(FriendlyNumber.areFriendly(4, 9));
+ }
+
+ @Test
+ public void testInvalidInputs() {
+ assertFalse(FriendlyNumber.areFriendly(0, 6));
+ assertFalse(FriendlyNumber.areFriendly(-1, 6));
+ assertFalse(FriendlyNumber.areFriendly(6, -1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java
new file mode 100644
index 000000000000..19558510f916
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class JacobsthalNumberTest {
+
+ @Test
+ public void testBaseCases() {
+ assertEquals(0, JacobsthalNumber.jacobsthal(0));
+ assertEquals(1, JacobsthalNumber.jacobsthal(1));
+ }
+
+ @Test
+ public void testKnownValues() {
+ assertEquals(1, JacobsthalNumber.jacobsthal(2));
+ assertEquals(3, JacobsthalNumber.jacobsthal(3));
+ assertEquals(5, JacobsthalNumber.jacobsthal(4));
+ assertEquals(11, JacobsthalNumber.jacobsthal(5));
+ assertEquals(21, JacobsthalNumber.jacobsthal(6));
+ assertEquals(43, JacobsthalNumber.jacobsthal(7));
+ assertEquals(85, JacobsthalNumber.jacobsthal(8));
+ assertEquals(171, JacobsthalNumber.jacobsthal(9));
+ assertEquals(341, JacobsthalNumber.jacobsthal(10));
+ }
+
+ @Test
+ public void testInvalidInput() {
+ assertThrows(IllegalArgumentException.class, () -> JacobsthalNumber.jacobsthal(-1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java
similarity index 99%
rename from src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java
rename to src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java
index 7c3ce6635aa0..5a3d1c64b379 100644
--- a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java
+++ b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.maths;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java
new file mode 100644
index 000000000000..b9d7f04b4d0b
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.maths;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Vraj Prajapati (@Rosander0)
+ */
+public class PadovanSequenceTest {
+
+ @Test
+ public void testBaseCase() {
+ assertEquals(1, PadovanSequence.padovan(0));
+ assertEquals(1, PadovanSequence.padovan(1));
+ assertEquals(1, PadovanSequence.padovan(2));
+ }
+
+ @Test
+ public void testKnownValues() {
+ assertEquals(2, PadovanSequence.padovan(3));
+ assertEquals(2, PadovanSequence.padovan(4));
+ assertEquals(3, PadovanSequence.padovan(5));
+ assertEquals(4, PadovanSequence.padovan(6));
+ assertEquals(5, PadovanSequence.padovan(7));
+ assertEquals(7, PadovanSequence.padovan(8));
+ assertEquals(9, PadovanSequence.padovan(9));
+ assertEquals(12, PadovanSequence.padovan(10));
+ }
+
+ @Test
+ public void testInvalidInput() {
+ assertThrows(IllegalArgumentException.class, () -> PadovanSequence.padovan(-1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java
similarity index 98%
rename from src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java
rename to src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java
index 130cd19b47b1..2405da558700 100644
--- a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java
+++ b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.maths;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
diff --git a/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java
new file mode 100644
index 000000000000..0ec476dc0641
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class PerrinNumberTest {
+
+ @Test
+ public void testBaseCases() {
+ assertEquals(3, PerrinNumber.perrin(0));
+ assertEquals(0, PerrinNumber.perrin(1));
+ assertEquals(2, PerrinNumber.perrin(2));
+ }
+
+ @Test
+ public void testKnownValues() {
+ assertEquals(3, PerrinNumber.perrin(3));
+ assertEquals(2, PerrinNumber.perrin(4));
+ assertEquals(5, PerrinNumber.perrin(5));
+ assertEquals(5, PerrinNumber.perrin(6));
+ assertEquals(7, PerrinNumber.perrin(7));
+ assertEquals(10, PerrinNumber.perrin(8));
+ assertEquals(12, PerrinNumber.perrin(9));
+ assertEquals(17, PerrinNumber.perrin(10));
+ }
+
+ @Test
+ public void testInvalidInput() {
+ assertThrows(IllegalArgumentException.class, () -> PerrinNumber.perrin(-1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/maths/SociableNumberTest.java b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java
new file mode 100644
index 000000000000..b4877cd761a7
--- /dev/null
+++ b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java
@@ -0,0 +1,56 @@
+package com.thealgorithms.maths;
+
+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 org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link SociableNumber}.
+ *
+ * @author Vraj Prajapati (@Rosander0)
+ */
+public class SociableNumberTest {
+
+ @Test
+ public void testSumOfProperDivisorsEdgeCases() {
+ assertEquals(0, SociableNumber.sumOfProperDivisors(0));
+ assertEquals(0, SociableNumber.sumOfProperDivisors(-5));
+ assertEquals(0, SociableNumber.sumOfProperDivisors(1));
+ assertEquals(1, SociableNumber.sumOfProperDivisors(2));
+ }
+
+ @Test
+ public void testSociableCycleOfLengthFive() {
+ assertTrue(SociableNumber.isSociable(12496, 5));
+ }
+
+ @Test
+ public void testAmicableNumbersAreSociableOfLengthTwo() {
+ assertTrue(SociableNumber.isSociable(220, 2));
+ assertTrue(SociableNumber.isSociable(284, 2));
+ }
+
+ @Test
+ public void testNonSociableNumbers() {
+ assertFalse(SociableNumber.isSociable(12, 5));
+ assertFalse(SociableNumber.isSociable(10, 3));
+ }
+
+ @Test
+ public void testEarlyCycleReturn() {
+ // 220 has cycle length 2; requesting a different length should return
+ // false because it returns to the start too early.
+ assertFalse(SociableNumber.isSociable(220, 3));
+ assertFalse(SociableNumber.isSociable(284, 4));
+ assertFalse(SociableNumber.isSociable(12496, 3));
+ }
+
+ @Test
+ public void testInvalidInputs() {
+ assertFalse(SociableNumber.isSociable(0, 5));
+ assertFalse(SociableNumber.isSociable(-1, 5));
+ assertFalse(SociableNumber.isSociable(220, 1));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java
new file mode 100644
index 000000000000..adc8fb717e57
--- /dev/null
+++ b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java
@@ -0,0 +1,115 @@
+package com.thealgorithms.matrix;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class QRDecompositionTest {
+
+ private static final double DELTA = 1e-9;
+
+ @Test
+ public void testQRDecomposition2x2() {
+ double[][] matrix = {{12, -51}, {6, 167}};
+ QRDecomposition.QR qr = QRDecomposition.decompose(matrix);
+ double[][] q = qr.getQ();
+ double[][] r = qr.getR();
+
+ double[][] reconstructed = multiplyMatrices(q, r);
+ for (int i = 0; i < matrix.length; i++) {
+ assertArrayEquals(matrix[i], reconstructed[i], DELTA);
+ }
+ }
+
+ @Test
+ public void testQRDecomposition3x3() {
+ double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}};
+ QRDecomposition.QR qr = QRDecomposition.decompose(matrix);
+ double[][] q = qr.getQ();
+ double[][] r = qr.getR();
+
+ double[][] reconstructed = multiplyMatrices(q, r);
+ for (int i = 0; i < matrix.length; i++) {
+ assertArrayEquals(matrix[i], reconstructed[i], DELTA);
+ }
+ }
+
+ @Test
+ public void testQROrthogonalColumns() {
+ double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}};
+ QRDecomposition.QR qr = QRDecomposition.decompose(matrix);
+ double[][] q = qr.getQ();
+
+ for (int i = 0; i < q[0].length; i++) {
+ for (int j = i; j < q[0].length; j++) {
+ double dot = 0;
+ for (int k = 0; k < q.length; k++) {
+ dot += q[k][i] * q[k][j];
+ }
+ if (i == j) {
+ assertArrayEquals(new double[] {1.0}, new double[] {dot}, DELTA);
+ } else {
+ assertArrayEquals(new double[] {0.0}, new double[] {dot}, DELTA);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRIsUpperTriangular() {
+ double[][] matrix = {{12, -51}, {6, 167}};
+ QRDecomposition.QR qr = QRDecomposition.decompose(matrix);
+ double[][] r = qr.getR();
+
+ for (int i = 1; i < r.length; i++) {
+ for (int j = 0; j < i; j++) {
+ assertArrayEquals(new double[] {0.0}, new double[] {r[i][j]}, DELTA);
+ }
+ }
+ }
+
+ @Test
+ public void testQRDecompositionIdentityMatrix() {
+ double[][] matrix = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
+ QRDecomposition.QR qr = QRDecomposition.decompose(matrix);
+ double[][] q = qr.getQ();
+ double[][] r = qr.getR();
+
+ for (int i = 0; i < matrix.length; i++) {
+ assertArrayEquals(matrix[i], q[i], DELTA);
+ assertArrayEquals(matrix[i], r[i], DELTA);
+ }
+ }
+
+ @Test
+ public void testQRDecompositionRankDeficientThrows() {
+ double[][] matrix = {{1, 2}, {2, 4}};
+ assertThrows(ArithmeticException.class, () -> QRDecomposition.decompose(matrix));
+ }
+
+ @Test
+ public void testQRDecompositionNullMatrixThrows() {
+ assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(null));
+ }
+
+ @Test
+ public void testQRDecompositionEmptyMatrixThrows() {
+ assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(new double[0][0]));
+ }
+
+ private static double[][] multiplyMatrices(double[][] a, double[][] b) {
+ int m = a.length;
+ int n = b[0].length;
+ int k = a[0].length;
+ double[][] result = new double[m][n];
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ for (int p = 0; p < k; p++) {
+ result[i][j] += a[i][p] * b[p][j];
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java
index b3b7e7ef129c..b7ef64125da8 100644
--- a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java
@@ -87,4 +87,15 @@ void testInterpolationSearchLargeNonUniformArray() {
int key = 21; // Present in the array
assertEquals(6, interpolationSearch.find(array, key), "The index of the found element should be 6.");
}
+
+ /**
+ * Test for interpolation search with specific sorted arrays that previously caused division by zero.
+ */
+ @Test
+ void testInterpolationSearchDivisionByZeroEdgeCases() {
+ InterpolationSearch interpolationSearch = new InterpolationSearch();
+ assertEquals(3, interpolationSearch.find(new int[] {0, 0, 0, 2}, 2));
+ assertEquals(0, interpolationSearch.find(new int[] {2, 2, 2, 2}, 2));
+ assertEquals(3, interpolationSearch.find(new int[] {0, 1, 2, 4}, 4));
+ }
}
diff --git a/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java
new file mode 100644
index 000000000000..a6931bca99d2
--- /dev/null
+++ b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.slidingwindow;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class CountDistinctElementsInWindowTest {
+
+ @Test
+ public void testBasicCase() {
+ assertArrayEquals(new int[] {3, 2, 2}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 2, 3}, 3));
+ }
+
+ @Test
+ public void testAllSame() {
+ assertArrayEquals(new int[] {1, 1, 1}, CountDistinctElementsInWindow.countDistinct(new int[] {2, 2, 2, 2}, 2));
+ }
+
+ @Test
+ public void testAllDistinct() {
+ assertArrayEquals(new int[] {3, 3}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 3));
+ }
+
+ @Test
+ public void testWindowSizeEqualsArray() {
+ assertArrayEquals(new int[] {4}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 4));
+ }
+
+ @Test
+ public void testInvalidInput() {
+ assertThrows(IllegalArgumentException.class, () -> CountDistinctElementsInWindow.countDistinct(new int[] {}, 2));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java
index 9e8ae9e9f153..4cd55a4d7410 100644
--- a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java
+++ b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java
@@ -1,9 +1,11 @@
package com.thealgorithms.strings;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class AlternativeStringArrangeTest {
@@ -20,4 +22,15 @@ private static Stream