From 54341c104e6ba307e61f538a9cf04ad3992cb656 Mon Sep 17 00:00:00 2001 From: Bhanu <144544908+Bhanubasyan@users.noreply.github.com> Date: Sat, 9 May 2026 02:22:16 +0530 Subject: [PATCH 01/63] Optimized NQueens implementation using hashing (#7416) * Optimized NQueens implementation using hashing * Fixed checkstyle naming issues * Fixed formatting issues * Fixed operator wrap formatting * Fixed formatting issues * Fixed operator wrapping style * Fixed print formatting * Fixed print formatting * Fixed operator formatting * Removed extra brace --- .../thealgorithms/backtracking/NQueens.java | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) 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; } } From e814d97309e8fd5486671896d62ad149beef0f70 Mon Sep 17 00:00:00 2001 From: Shalini H R Date: Sat, 9 May 2026 20:53:49 +0530 Subject: [PATCH 02/63] Added null check to EMAFilter (#7417) --- .../com/thealgorithms/audiofilters/EMAFilter.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java index 0dd23e937953..4a9e954bd202 100644 --- a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java +++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java @@ -3,16 +3,19 @@ /** * Exponential Moving Average (EMA) Filter for smoothing audio signals. * - *

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]; From e7f8979192ee84006e3eead98d6f891111664c9a Mon Sep 17 00:00:00 2001 From: Sunny Sharma <119731813+the-Sunny-Sharma@users.noreply.github.com> Date: Thu, 14 May 2026 02:17:00 +0530 Subject: [PATCH 03/63] feat: add Rat in a Maze backtracking algorithm (#7418) * feat: add Rat in a Maze backtracking algorithm with 10 unit tests * test: add coverage for all-open maze and larger maze path * style: apply clang-format fixes and add newline at end of files * style: apply clang-format and checkstyle fixes --- .../backtracking/RatInAMaze.java | 119 ++++++++++++++++++ .../backtracking/RatInAMazeTest.java | 99 +++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 src/main/java/com/thealgorithms/backtracking/RatInAMaze.java create mode 100644 src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java 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. + * + *

Example: + *

+ *   maze = { {1, 0, 0, 0},
+ *            {1, 1, 0, 1},
+ *            {0, 1, 0, 0},
+ *            {0, 1, 1, 1} }
+ *   Output: ["DDRDRR", "DRDDRR"]  (two valid paths)
+ * 
+ * + * @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/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); + } + } +} From 0811cd05e174dec23e05429d280d127f77d92dd0 Mon Sep 17 00:00:00 2001 From: Antariksh Mankar Date: Fri, 15 May 2026 14:30:41 +0530 Subject: [PATCH 04/63] [ENHANCEMENT] Add Wavelet Tree Data Structure (#7414) * Implement Wavelet Tree with rank and kthSmallest methods * Implement Wavelet Tree with rank and kthSmallest methods * Fix checkstyle multiple variable declarations violation --------- Co-authored-by: Deniz Altunkapan --- .../datastructures/trees/WaveletTree.java | 235 ++++++++++++++++++ .../datastructures/trees/WaveletTreeTest.java | 117 +++++++++ 2 files changed, 352 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java 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/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)); + } +} From 783c96f949095e2a9723724e9e301ac983ddab5d Mon Sep 17 00:00:00 2001 From: Utsav Tripathi Date: Sun, 17 May 2026 02:30:17 +0530 Subject: [PATCH 05/63] Fix: remove floating Javadoc comments causing compilation error (#7423) --- .../conversions/AnyBaseToAnyBase.java | 8 +------- .../searches/InterpolationSearch.java | 12 +----------- .../com/thealgorithms/searches/LinearSearch.java | 14 +------------- 3 files changed, 3 insertions(+), 31 deletions(-) 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/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index d24cc1c774bc..272627fc48b4 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; /** diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 3f273e167f0a..bd14fe21ea03 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; From 8848ed1eab41bf5d272e7fc7e9d90b5350157d7e Mon Sep 17 00:00:00 2001 From: Utsav Tripathi Date: Sun, 17 May 2026 16:35:57 +0530 Subject: [PATCH 06/63] Docs: add Javadoc to CoinChange class and method (#7424) * Fix: remove floating Javadoc comments causing compilation error * Docs: add Javadoc to CoinChange class and method * Style: apply clang-format to CoinChange.java --- .../greedyalgorithms/CoinChange.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) 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}; From 4b8099c27b4f7fe7dc465d80ed0a5d9e78bd4153 Mon Sep 17 00:00:00 2001 From: Shubham Bhati <112773220+Shubh2-0@users.noreply.github.com> Date: Mon, 18 May 2026 13:07:32 +0530 Subject: [PATCH 07/63] fix: add null input validation to AlternativeStringArrange.arrange() (#7425) * fix: add null input validation to AlternativeStringArrange.arrange() The arrange() method previously threw a NullPointerException when either input string was null. This change explicitly validates the inputs and throws IllegalArgumentException with a clear message, matching the fail-fast pattern used by other utility classes in this package (e.g. HammingDistance). - Add null guard at the start of arrange() - Update Javadoc with @throws and contract notes - Add parameterized test covering all three null-input combinations * fix: remove unused JUnit @Test import (Checkstyle violation) --- .../strings/AlternativeStringArrange.java | 11 +++++++++-- .../strings/AlternativeStringArrangeTest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) 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/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 provideTestData() { void arrangeTest(String input1, String input2, String expected) { assertEquals(expected, AlternativeStringArrange.arrange(input1, input2)); } + + @ParameterizedTest(name = "null input ({0}, {1}) should throw IllegalArgumentException") + @MethodSource("provideNullInputs") + void arrangeThrowsOnNullInput(String input1, String input2) { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> AlternativeStringArrange.arrange(input1, input2)); + assertEquals("Input strings must not be null", ex.getMessage()); + } + + private static Stream provideNullInputs() { + return Stream.of(Arguments.of(null, "abc"), Arguments.of("abc", null), Arguments.of(null, null)); + } } From 3ee310ec80fe0bfbc27ef97841471e5085326b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 21:09:26 +0200 Subject: [PATCH 08/63] chore(deps): bump org.junit:junit-bom from 6.0.3 to 6.1.0 (#7431) * chore(deps): bump org.junit:junit-bom from 6.0.3 to 6.1.0 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.0.3 to 6.1.0. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.3...r6.1.0) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * style: resolve `DLS_DEAD_LOCAL_STORE` in `testIteratorEmptyBag` --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: vil02 <65706193+vil02@users.noreply.github.com> --- pom.xml | 2 +- .../java/com/thealgorithms/datastructures/bag/BagTest.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index e0a3486b23bb..3fb0887973e0 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 6.0.3 + 6.1.0 pom import 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 From 0a62b113d64f6641ae25896c60da142fbc5c8cf8 Mon Sep 17 00:00:00 2001 From: premm Date: Fri, 22 May 2026 03:14:21 +0530 Subject: [PATCH 09/63] feat: add optimized Digit DP template and unit tests (#7430) * feat: add optimized Digit DP template and unit tests * test: add test cases for max target sum and memoization hit to achieve 100% coverage * style: fix indentation and code formatting for clang linter compliance * fix: remove checkstyle inner assignment in solve method * fix: remove checkstyle inner assignment in solve method -newline at end --- .../dynamicprogramming/DigitDP.java | 111 ++++++++++++++++++ .../dynamicprogramming/DigitDPTest.java | 70 +++++++++++ 2 files changed, 181 insertions(+) create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java 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/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); + } +} From e49cd55255711fa2ce3f4d99faae026318813484 Mon Sep 17 00:00:00 2001 From: Md Mushfiqur Rahim <20mahin2020@gmail.com> Date: Tue, 26 May 2026 01:49:36 +0600 Subject: [PATCH 10/63] feat(datastructures): add thread-safe bounded queue implementation (#7428) * feat(datastructures): add thread-safe bounded queue implementation Implements a thread-safe blocking queue using ReentrantLock and Condition variables for producer-consumer synchronization. ### What This Adds **ThreadSafeQueue.java** - Thread-safe bounded queue: - `enqueue()` - Blocking add to tail, waits when queue is full - `dequeue()` - Blocking remove from head, waits when queue is empty - `offer()` - Non-blocking add, returns false when full - `poll()` - Non-blocking remove, returns null when empty - `size()`, `isEmpty()`, `isFull()`, `capacity()` - State queries - Uses circular buffer for O(1) enqueue/dequeue operations - Supports multiple concurrent producers and consumers **ThreadSafeQueueTest.java** - Comprehensive test suite: - Basic enqueue/dequeue operations - Offer/poll non-blocking behavior - Null rejection validation - Invalid capacity rejection - Circular buffer wrap-around - Multiple producers single consumer concurrency - Single producer multiple consumers concurrency - Blocking behavior verification - Stress test with 8 concurrent threads ### Algorithm Uses a circular buffer with ReentrantLock and two Condition variables: - `notFull` - signaled when space becomes available - `notEmpty` - signaled when items are added - Producers await notFull when buffer is full - Consumers await notEmpty when buffer is empty - Signal opposite condition after each operation Time: O(1) enqueue/dequeue | Space: O(n) bounded buffer ### Reference https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem * fix(datastructures): correct test capacity and simplify concurrent test - testOfferPoll: Changed capacity from 3 to 2 so third offer correctly fails - testMultipleProducersSingleConsumer: Removed startLatch, use dedicated consumer thread with synchronized results list for thread safety * fix(datastructures): remove unused assertArrayEquals import Checkstyle flagged UnusedImports violation for org.junit.jupiter.api.Assertions.assertArrayEquals which was not used in any test method. * fix: replace signal() with signalAll() to satisfy SpotBugs MDM_SIGNAL_NOT_SIGNALALL SpotBugs flags all four Condition.signal() calls in ThreadSafeQueue as Medium severity bugs (MDM_SIGNAL_NOT_SIGNALALL). In a multi-producer/multi-consumer scenario, signal() wakes only one waiting thread, which can cause deadlock when multiple producers or consumers are blocked on the same condition variable. Using signalAll() ensures all waiting threads are notified and can re-check their loop condition, preventing the lost-wakeup problem that occurs when a single signal wakes a thread that cannot make progress. This change affects enqueue(), dequeue(), offer(), and poll() methods where notEmpty.signal() and notFull.signal() are replaced with notEmpty.signalAll() and notFull.signalAll() respectively. * test: replace static imports with Assertions prefix to satisfy PMD TooManyStaticImports PMD flags TooManyStaticImports when more than 4 static imports are present. The test file had 5 static imports from org.junit.jupiter.api.Assertions (equals, assertFalse, assertNull, assertThrows, assertTrue) which exceeded the default threshold. Replaced with regular import and Assertions. prefix to eliminate the PMD violation while maintaining readability. --- .../queues/ThreadSafeQueue.java | 186 +++++++++++ .../queues/ThreadSafeQueueTest.java | 295 ++++++++++++++++++ 2 files changed, 481 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java create mode 100644 src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java 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/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); + } +} From 42007c8b495136437c7aa39ecc218e2835853030 Mon Sep 17 00:00:00 2001 From: 07Vineet07 <164660112+07Vineet07@users.noreply.github.com> Date: Tue, 26 May 2026 14:48:41 +0530 Subject: [PATCH 11/63] docs: improve LinearSearch documentation with examples and step-by-step explanation (#7435) --- .../thealgorithms/searches/LinearSearch.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index bd14fe21ea03..c5f6e6ba9776 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -10,10 +10,34 @@ * * It works for both sorted and unsorted arrays. * + *

How it works step-by-step: + *

    + *
  1. Start from the first element of the array.
  2. + *
  3. Compare the current element with the target value.
  4. + *
  5. If they match, return the current index.
  6. + *
  7. If they don't match, move to the next element.
  8. + *
  9. Repeat until the element is found or the array ends.
  10. + *
  11. If not found, return -1.
  12. + *
+ * + *

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) * @@ -25,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 */ From 0905cbe1659c1b47cdba6de1ca89d00d26cdb9ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 23:16:07 +0200 Subject: [PATCH 12/63] chore(deps-dev): bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.5 to 3.5.6 (#7436) chore(deps-dev): bump org.apache.maven.plugins:maven-surefire-plugin Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.5 to 3.5.6. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.5...surefire-3.5.6) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-version: 3.5.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3fb0887973e0..b2838cbfb9ec 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ maven-surefire-plugin - 3.5.5 + 3.5.6 From 7b1995f872cd86b0de4c20f61ab9ff8b8f642774 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Thu, 28 May 2026 20:28:26 +0200 Subject: [PATCH 13/63] chore: use `srz-zumix/setup-infer` (#7437) --- .github/workflows/infer.yml | 32 +++----------------------------- .inferconfig | 1 + 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 9d4dcf63000b..fbc0f1f1bc7f 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -23,34 +23,8 @@ jobs: java-version: 21 distribution: 'temurin' - - name: Set up OCaml - uses: ocaml/setup-ocaml@v3 - with: - ocaml-compiler: 5 - - - name: Get current year/weak - run: echo "year_week=$(date +'%Y_%U')" >> $GITHUB_ENV - - - name: Cache infer build - id: cache-infer - uses: actions/cache@v5 - with: - path: infer - key: ${{ runner.os }}-infer-${{ env.year_week }} - - - name: Build infer - if: steps.cache-infer.outputs.cache-hit != 'true' - run: | - cd .. - git clone https://github.com/facebook/infer.git - cd infer - git checkout 02c2c43b71e4c5110c0be841e66153942fda06c9 - ./build-infer.sh java - cp -r infer ../Java - - - name: Add infer to PATH - run: | - echo "infer/bin" >> $GITHUB_PATH + - name: Set up inferAdd commentMore actions + uses: srz-zumix/setup-infer@v1 - name: Display infer version run: | @@ -60,5 +34,5 @@ jobs: - name: Run infer run: | mvn clean - infer --fail-on-issue --print-logs --no-progress-bar -- mvn test + infer --java-version 21 --fail-on-issue --print-logs --no-progress-bar -- mvn test ... diff --git a/.inferconfig b/.inferconfig index 239172177b38..cf26212feac5 100644 --- a/.inferconfig +++ b/.inferconfig @@ -21,6 +21,7 @@ "src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java", "src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java", "src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java", + "src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java", "src/test/java/com/thealgorithms/others/HuffmanTest.java", "src/test/java/com/thealgorithms/searches/QuickSelectTest.java", "src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java", From 978a3063ea44002e8f820e8553b6659a350e47ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 22:21:18 +0200 Subject: [PATCH 14/63] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.4.2 to 13.5.0 (#7441) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.4.2 to 13.5.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.4.2...checkstyle-13.5.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b2838cbfb9ec..1cd864ff23f2 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.4.2 + 13.5.0 From 5feaba6698250e674f1164647b966cc2d254beee Mon Sep 17 00:00:00 2001 From: sawaleparas Date: Thu, 4 Jun 2026 03:13:55 +0530 Subject: [PATCH 15/63] test: refactor MergeSortedArrayListTest using JUnit 5 parameterized tests (#7445) * test: upgrade MergeSortedArrayListTest to use JUnit 5 parameterized tests * style: strip trailing whitespace and add trailing newline via IDE * style: align layout with clang-format rules * style: collapse stream arguments to single lines for clang * style: fix line wrapping length for clang linter --------- Co-authored-by: psawale --- .../lists/MergeSortedArrayListTest.java | 93 +++++-------------- 1 file changed, 23 insertions(+), 70 deletions(-) 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)); } } From cecfad934137d44e0a915052c4b09f5451bbb96c Mon Sep 17 00:00:00 2001 From: Md Mushfiqur Rahim <20mahin2020@gmail.com> Date: Wed, 3 Jun 2026 14:47:15 -0700 Subject: [PATCH 16/63] feat(matrix): add QR decomposition algorithm using Gram-Schmidt process (#7427) * feat(matrix): add QR decomposition algorithm using Gram-Schmidt process Decomposes a matrix A into an orthogonal matrix Q and an upper triangular matrix R such that A = Q * R. ### What This Adds **QRDecomposition.java** - Main algorithm implementation: - `decompose()` - Performs QR factorization using the Gram-Schmidt process - Returns a QR object containing both Q (orthogonal) and R (upper triangular) matrices - Validates input matrix using MatrixUtil.validateInputMatrix() - Throws ArithmeticException for rank-deficient matrices **QRDecompositionTest.java** - Unit tests: - Tests for 2x2 and 3x3 matrix decomposition - Verifies Q * R reconstruction equals original matrix - Validates Q columns are orthonormal - Confirms R is upper triangular - Tests identity matrix decomposition - Tests rank-deficient matrix rejection - Tests null and empty matrix rejection ### Algorithm The Gram-Schmidt process orthogonalizes columns iteratively: - For each column j, subtract projections onto previous orthogonal vectors - Normalize to get j-th column of Q - Store coefficients in R[i][j] Time: O(m*n^2) | Space: O(m*n + n^2) ### Reference https://en.wikipedia.org/wiki/QR_decomposition * fix(matrix): inline matrix validation to avoid cross-package dependency MatrixUtil is in com.thealgorithms.matrix.utils package which causes compilation errors when referenced from com.thealgorithms.matrix. Inlined the validation logic (validateInputMatrix, hasValidRows, isJaggedMatrix) directly into QRDecomposition to resolve the issue. --- .../thealgorithms/matrix/QRDecomposition.java | 149 ++++++++++++++++++ .../matrix/QRDecompositionTest.java | 115 ++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 src/main/java/com/thealgorithms/matrix/QRDecomposition.java create mode 100644 src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java 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/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; + } +} From e514f728b9ca95f1e0bee2e7798b201815cfb51c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:14:23 +0200 Subject: [PATCH 17/63] chore(deps-dev): bump org.jacoco:jacoco-maven-plugin from 0.8.14 to 0.8.15 (#7448) chore(deps-dev): bump org.jacoco:jacoco-maven-plugin Bumps [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.14 to 0.8.15. - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.14...v0.8.15) --- updated-dependencies: - dependency-name: org.jacoco:jacoco-maven-plugin dependency-version: 0.8.15 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1cd864ff23f2..6edd36f446d8 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ org.jacoco jacoco-maven-plugin - 0.8.14 + 0.8.15 From ae861d6ceabc6921a44e8f188fd778ff06bc7d76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:26:31 +0200 Subject: [PATCH 18/63] chore(deps): bump codecov/codecov-action from 6 to 7 in /.github/workflows (#7449) chore(deps): bump codecov/codecov-action in /.github/workflows Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v6...v7) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c2c1ef828b7..4b1f0f376e25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) @@ -28,7 +28,7 @@ jobs: github.repository == 'TheAlgorithms/Java' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From d792409c4e2dddb612d1f4e96cc3a6bdd3b10dea Mon Sep 17 00:00:00 2001 From: Sai Chandu Vallaboju Date: Wed, 10 Jun 2026 03:41:06 -0400 Subject: [PATCH 19/63] Rename and relocate trie autocomplete implementation (#7459) refactor: rename trie autocomplete implementation --- pmd-exclude.properties | 2 +- .../trees/TrieAutocomplete.java} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/com/thealgorithms/{others/Implementing_auto_completing_features_using_trie.java => datastructures/trees/TrieAutocomplete.java} (98%) diff --git a/pmd-exclude.properties b/pmd-exclude.properties index 64562c524728..26653d9dc17b 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -86,7 +86,7 @@ com.thealgorithms.others.MosAlgorithm=UselessMainMethod com.thealgorithms.others.PageRank=UselessMainMethod,UselessParentheses com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses -com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses +com.thealgorithms.datastructures.trees.TrieAutocomplete=UselessMainMethod,UselessParentheses com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod com.thealgorithms.recursion.DiceThrower=UselessMainMethod com.thealgorithms.searches.HowManyTimesRotated=UselessMainMethod 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; From 34079f0aa02d8f4564a21fabd4326f3fd3eb8a8e Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 10 Jun 2026 13:17:20 +0530 Subject: [PATCH 20/63] =?UTF-8?q?test(dp):=20add=20tests=20for=20LongestPa?= =?UTF-8?q?lindromicSubsequence=20and=20remove=20main=E2=80=A6=20(#7462)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(dp): add tests for LongestPalindromicSubsequence and remove main method * fix: formatting and style fixes for clang-format compliance * fix: correct expected values in tests and add missing EOF newline --------- Co-authored-by: Deniz Altunkapan --- .../LongestPalindromicSubsequence.java | 72 ++++++++----------- .../LongestPalindromicSubsequenceTest.java | 54 ++++++++++++++ 2 files changed, 82 insertions(+), 44 deletions(-) create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java 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/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); + } +} From 6fbbc9407a9432b31edb525be87fc62a2aa80c20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:55:07 +0200 Subject: [PATCH 21/63] chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.8.3 to 4.10.2.0 (#7465) chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.8.3 to 4.10.2.0. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.8.3...spotbugs-maven-plugin-4.10.2.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.10.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6edd36f446d8..e54b83399e9f 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.8.3 + 4.10.2.0 spotbugs-exclude.xml true From 0388455908a643d7e69a2247fec191357338fa6f Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Mon, 15 Jun 2026 01:49:56 +0530 Subject: [PATCH 22/63] Add LongestCommonSubstring Implementation (#7464) feat: add LongestCommonSubstring implementation --- .../strings/LongestCommonSubstring.java | 55 +++++++++++++++++++ .../strings/LongestCommonSubstringTest.java | 36 ++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java create mode 100644 src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java 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/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java new file mode 100644 index 000000000000..e54abcf2f1f3 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class LongestCommonSubstringTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", LongestCommonSubstring.longestCommonSubstring(null, "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", null)); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("", "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "")); + } + + @Test + public void testNormalSubstrings() { + assertEquals("cde", LongestCommonSubstring.longestCommonSubstring("abcdef", "zcdemf")); + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abc", "abc")); + assertEquals("cdef", LongestCommonSubstring.longestCommonSubstring("abcdef", "cdefgh")); + } + + @Test + public void testSingleCharacterAndNoMatch() { + assertEquals("a", LongestCommonSubstring.longestCommonSubstring("a", "a")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "xyz")); + } + + @Test + public void testMultipleMatchesFirstLongest() { + // Keeps the first matched longest substring when lengths are tied + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abcXdef", "abcYdef")); + } +} From bf8f3bd490d78e1ed8f97fa9b851ce1d8f85e6c5 Mon Sep 17 00:00:00 2001 From: Abdullah Shah Date: Mon, 15 Jun 2026 13:59:03 +0500 Subject: [PATCH 23/63] feat: add count distinct elements in window algorithm (#7463) * feat: add count distinct elements in window algorithm * feat: add CountDistinctElementsInWindow * fix: address CI issues --- .../CountDistinctElementsInWindow.java | 57 +++++++++++++++++++ .../CountDistinctElementsInWindowTest.java | 34 +++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java create mode 100644 src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java 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/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)); + } +} From b3fcb12b9436a4699f4676c81ccb204c92c70dbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:56:21 +0200 Subject: [PATCH 24/63] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.5.0 to 13.6.0 (#7480) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.5.0 to 13.6.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.5.0...checkstyle-13.6.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e54b83399e9f..927395112259 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.5.0 + 13.6.0 From 8e374d3904e8557c3bb8e9f033a236b0f3e21cc5 Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Thu, 18 Jun 2026 12:53:03 +0530 Subject: [PATCH 25/63] Add PerrinNumber Implementation (#7478) feat: add PerrinNumber implementation --- .../com/thealgorithms/maths/PerrinNumber.java | 53 +++++++++++++++++++ .../thealgorithms/maths/PerrinNumberTest.java | 34 ++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/PerrinNumber.java create mode 100644 src/test/java/com/thealgorithms/maths/PerrinNumberTest.java 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/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)); + } +} From bf0ff70d21df09288d0f9f358798c4902652736a Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Thu, 18 Jun 2026 12:56:56 +0530 Subject: [PATCH 26/63] Add JacobsthalNumber Implementation (#7479) feat: add JacobsthalNumber implementation --- .../thealgorithms/maths/JacobsthalNumber.java | 44 +++++++++++++++++++ .../maths/JacobsthalNumberTest.java | 34 ++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/JacobsthalNumber.java create mode 100644 src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java 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/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)); + } +} From 8a3bd3aa02611255bf26793f4d158ea54a13fab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:33:41 +0200 Subject: [PATCH 27/63] chore(deps): bump actions/checkout from 6 to 7 in /.github/workflows (#7483) Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/clang-format-lint.yml | 2 +- .github/workflows/codeql.yml | 4 ++-- .github/workflows/infer.yml | 2 +- .github/workflows/project_structure.yml | 2 +- .github/workflows/update-directorymd.yml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b1f0f376e25..b8f4c8efa7e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 with: diff --git a/.github/workflows/clang-format-lint.yml b/.github/workflows/clang-format-lint.yml index dc0c9754ed1b..622679f73842 100644 --- a/.github/workflows/clang-format-lint.yml +++ b/.github/workflows/clang-format-lint.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: DoozyX/clang-format-lint-action@v0.20 with: source: './src' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 152d0d766fd2..14ea223946cd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 @@ -52,7 +52,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index fbc0f1f1bc7f..6bf5c56a91b1 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -15,7 +15,7 @@ jobs: run_infer: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml index 5aadc6353791..e7e703c27b70 100644 --- a/.github/workflows/project_structure.yml +++ b/.github/workflows/project_structure.yml @@ -15,7 +15,7 @@ jobs: check_structure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: '3.13' diff --git a/.github/workflows/update-directorymd.yml b/.github/workflows/update-directorymd.yml index 1cfee6e36e4e..3977bfda86bf 100644 --- a/.github/workflows/update-directorymd.yml +++ b/.github/workflows/update-directorymd.yml @@ -1,4 +1,4 @@ -name: Generate Directory Markdown +name: Generate Directory Markdown on: push: @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: persist-credentials: false From fafc5a2964d351b691d05df41994f4e05257cdeb Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 21 Jun 2026 01:21:46 +0530 Subject: [PATCH 28/63] chore: move HeavyLightDecomposition to datastructures/trees (#7485) --- .../{tree => datastructures/trees}/HeavyLightDecomposition.java | 2 +- .../trees}/HeavyLightDecompositionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/com/thealgorithms/{tree => datastructures/trees}/HeavyLightDecomposition.java (99%) rename src/test/java/com/thealgorithms/{tree => datastructures/trees}/HeavyLightDecompositionTest.java (97%) 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/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; From e57a675893fb6a8fbc725085a3b3039b7565a0b1 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Sun, 21 Jun 2026 01:26:26 +0530 Subject: [PATCH 29/63] Fix division by zero in interpolation search (#7484) * Fix division by zero in interpolation search * style: fix formatting in InterpolationSearchTest --- .../thealgorithms/searches/InterpolationSearch.java | 5 ++++- .../searches/InterpolationSearchTest.java | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index 272627fc48b4..52dbc0b7e2c5 100644 --- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java +++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java @@ -36,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/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)); + } } From 631eae3f1352f6ccfa6e9d7b76a420cc31e49e6a Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Sun, 21 Jun 2026 03:53:29 +0530 Subject: [PATCH 30/63] Add PadovanSequence Implementation (#7477) feat: Add PadovanSequence Implementation --- .../thealgorithms/maths/PadovanSequence.java | 43 +++++++++++++++++++ .../maths/PadovanSequenceTest.java | 36 ++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/PadovanSequence.java create mode 100644 src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java 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/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)); + } +} From a508fd2647c439d1986c3f914386d600632cc3bd Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Mon, 22 Jun 2026 12:54:40 +0530 Subject: [PATCH 31/63] Add FriendlyNumber Implementation (#7474) feat: Add FriendlyNumber Implementation --- .../thealgorithms/maths/FriendlyNumber.java | 49 +++++++++++++++++++ .../maths/FriendlyNumberTest.java | 33 +++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/FriendlyNumber.java create mode 100644 src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java 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/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)); + } +} From 0fe06acce7c8f519b66c34061229ac0d49c16fa7 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Mon, 22 Jun 2026 21:48:10 +0530 Subject: [PATCH 32/63] fix: validate that all characters after first padding character in Base64 decode (#7491) fix: validate that all characters after first padding character are also padding characters --- .../java/com/thealgorithms/conversions/Base64.java | 11 +++++++++-- .../com/thealgorithms/conversions/Base64Test.java | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) 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/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 From 00339c759c4a05800cd577613d737b019e2131b7 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Tue, 23 Jun 2026 15:24:14 +0530 Subject: [PATCH 33/63] refactor: unify Dijkstra on efficient PriorityQueue implementation (#7486) --- .../graphs/DijkstraAlgorithm.java | 50 +++++++------- .../graphs/DijkstraOptimizedAlgorithm.java | 66 ------------------- .../DijkstraOptimizedAlgorithmTest.java | 64 ------------------ 3 files changed, 26 insertions(+), 154 deletions(-) delete mode 100644 src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java delete mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java 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/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)); - } -} From 0e001b7dbadf1ea06056e7cbdfb2b943ec6452cd Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Wed, 24 Jun 2026 03:30:03 +0530 Subject: [PATCH 34/63] Add SociableNumber Implementation (#7475) feat: add SociableNumber implementation --- .../thealgorithms/maths/SociableNumber.java | 67 +++++++++++++++++++ .../maths/SociableNumberTest.java | 56 ++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/SociableNumber.java create mode 100644 src/test/java/com/thealgorithms/maths/SociableNumberTest.java 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/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)); + } +} From 51137a14ffa10dc24edbfe14ea94d1e1c397caa5 Mon Sep 17 00:00:00 2001 From: Priyanshu Date: Wed, 24 Jun 2026 22:36:54 +0530 Subject: [PATCH 35/63] refactor: review and group scattered palindrome implementation (#7487) * feat: add utility to find lowest base for palindromic numbers and implement palindrome check for singly linked lists * feat: add palindrome check for linked lists and find lowest base palindrome implementation * feat: add utility to check if an integer's binary representation is a palindrome * feat: add algorithm to check if a singly linked list is a palindrome * feat: add PalindromeNumber utility class to check for palindromic integers * style: remove redundant import to fix checkstyle error * style: add blank line between static and non-static imports --- .../bitmanipulation/BinaryPalindromeCheck.java | 6 ++++++ .../lists}/PalindromeSinglyLinkedList.java | 9 ++++++++- .../{others => maths}/LowestBasePalindrome.java | 8 +++++++- .../com/thealgorithms/maths/PalindromeNumber.java | 11 +++++++++++ .../{misc => maths}/PalindromePrime.java | 13 ++++++++++++- .../thealgorithms/stacks/PalindromeWithStack.java | 7 +++++++ .../java/com/thealgorithms/strings/Palindrome.java | 7 +++++++ .../lists}/PalindromeSinglyLinkedListTest.java | 3 +-- .../{others => maths}/LowestBasePalindromeTest.java | 2 +- .../{misc => maths}/PalindromePrimeTest.java | 2 +- 10 files changed, 61 insertions(+), 7 deletions(-) rename src/main/java/com/thealgorithms/{misc => datastructures/lists}/PalindromeSinglyLinkedList.java (84%) rename src/main/java/com/thealgorithms/{others => maths}/LowestBasePalindrome.java (94%) rename src/main/java/com/thealgorithms/{misc => maths}/PalindromePrime.java (73%) rename src/test/java/com/thealgorithms/{misc => datastructures/lists}/PalindromeSinglyLinkedListTest.java (98%) rename src/test/java/com/thealgorithms/{others => maths}/LowestBasePalindromeTest.java (99%) rename src/test/java/com/thealgorithms/{misc => maths}/PalindromePrimeTest.java (98%) 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/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/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/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/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/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/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/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/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; From 31caee6ef850e0610f1fd0b49668b395a5613f71 Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Thu, 25 Jun 2026 14:01:26 +0530 Subject: [PATCH 36/63] Add TitleCase Implementation (#7473) feat: add TitleCase implementation --- .../com/thealgorithms/strings/TitleCase.java | 54 +++++++++++++++++++ .../thealgorithms/strings/TitleCaseTest.java | 39 ++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/TitleCase.java create mode 100644 src/test/java/com/thealgorithms/strings/TitleCaseTest.java diff --git a/src/main/java/com/thealgorithms/strings/TitleCase.java b/src/main/java/com/thealgorithms/strings/TitleCase.java new file mode 100644 index 000000000000..eb6c73623681 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/TitleCase.java @@ -0,0 +1,54 @@ +package com.thealgorithms.strings; + +/** + * Title Case converts a string so that the first letter of each word + * is capitalized and the rest are lowercase. + * Example: "the quick brown fox" -> "The Quick Brown Fox" + * + * @see + * Wikipedia: Title Case + */ +public final class TitleCase { + + private TitleCase() { + // Utility class + } + + /** + * Converts a string to title case. + * + * @param input The string to convert + * @return The title-cased string, or empty string if input is null/empty. + * If input contains only whitespace, it is returned as is. + */ + public static String toTitleCase(final String input) { + if (input == null || input.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(input.length()); + boolean capitalizeNext = true; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (Character.isWhitespace(c)) { + capitalizeNext = true; + result.append(c); + continue; + } + + if (capitalizeNext) { + if (Character.isLetter(c)) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + // Keep capitalizeNext=true so the first *letter* after + // punctuation/digits is capitalized. + result.append(c); + } + } else { + result.append(Character.toLowerCase(c)); + } + } + return result.toString(); + } +} diff --git a/src/test/java/com/thealgorithms/strings/TitleCaseTest.java b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java new file mode 100644 index 000000000000..37bb44ed2c7b --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java @@ -0,0 +1,39 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TitleCaseTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", TitleCase.toTitleCase(null)); + assertEquals("", TitleCase.toTitleCase("")); + } + + @Test + public void testSingleWord() { + assertEquals("Hello", TitleCase.toTitleCase("hello")); + assertEquals("Hello", TitleCase.toTitleCase("HELLO")); + assertEquals("A", TitleCase.toTitleCase("a")); + } + + @Test + public void testMultipleWords() { + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("the quick brown fox")); + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("THE QUICK BROWN FOX")); + assertEquals("Already Title Case", TitleCase.toTitleCase("already Title Case")); + } + + @Test + public void testWhitespace() { + assertEquals(" Spaces ", TitleCase.toTitleCase(" spaces ")); + } + + @Test + public void testQuotedWords() { + assertEquals("\"Hello\"", TitleCase.toTitleCase("\"hello\"")); + } +} From ef986c4690e8ea1abda477e685e7133cca09c73d Mon Sep 17 00:00:00 2001 From: iamcodinghere22 Date: Fri, 26 Jun 2026 14:33:46 +0530 Subject: [PATCH 37/63] DisariumNumber (#7490) * Add SquareFreeInteger to maths * fix clang-format issues * add newline * Add new test file in test * modified * delete * Add DisariumNumbers with test --- .../thealgorithms/maths/DisariumNumber.java | 37 +++++++++++++++++++ .../maths/DisariumNumberTest.java | 32 ++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/DisariumNumber.java create mode 100644 src/test/java/com/thealgorithms/maths/DisariumNumberTest.java diff --git a/src/main/java/com/thealgorithms/maths/DisariumNumber.java b/src/main/java/com/thealgorithms/maths/DisariumNumber.java new file mode 100644 index 000000000000..0196d0797bcc --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/DisariumNumber.java @@ -0,0 +1,37 @@ +package com.thealgorithms.maths; + +/** + * Disarium number is a number where the sum of its digits powered + * with their respective positions is equal to the number itself. + * Example: 135 = 1^1 + 3^2 + 5^3 = 1 + 9 + 125 = 135 + * + * @see Disarium Number + */ +public final class DisariumNumber { + + private DisariumNumber() { + } + + /** + * Checks if a number is a Disarium number. + * + * @param number the number to check (must be positive) + * @return true if number is Disarium, false otherwise + * @throws IllegalArgumentException if number is not positive + */ + public static boolean isDisarium(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Input must be a positive integer."); + } + int digits = String.valueOf(number).length(); + int temp = number; + int sum = 0; + while (temp > 0) { + int lastDigit = temp % 10; + sum += (int) Math.pow(lastDigit, digits); + digits--; + temp /= 10; + } + return sum == number; + } +} diff --git a/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java new file mode 100644 index 000000000000..54e9e861ad44 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class DisariumNumberTest { + + @Test + void testDisariumNumbers() { + assertTrue(DisariumNumber.isDisarium(1)); + assertTrue(DisariumNumber.isDisarium(89)); + assertTrue(DisariumNumber.isDisarium(135)); + assertTrue(DisariumNumber.isDisarium(175)); + assertTrue(DisariumNumber.isDisarium(518)); + } + + @Test + void testNonDisariumNumbers() { + assertFalse(DisariumNumber.isDisarium(10)); + assertFalse(DisariumNumber.isDisarium(100)); + assertFalse(DisariumNumber.isDisarium(200)); + } + + @Test + void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(0)); + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(-5)); + } +} From 0d29e7c167db8dcf07032b9112515d8cc42bbc5f Mon Sep 17 00:00:00 2001 From: JulianGStudium Date: Sat, 27 Jun 2026 10:38:11 +0200 Subject: [PATCH 38/63] SelectionSort: add missing Javadoc for class and findIndexOfMin method (#7494) * SelectionSort: add missing Javadoc for class and findIndexOfMin method * fix: add missing newline at end of file --- .../com/thealgorithms/sorts/SelectionSort.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java index 2d1814441701..e6d1a16a7af7 100644 --- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java +++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java @@ -1,5 +1,10 @@ package com.thealgorithms.sorts; +/** + * Implementation of the Selection Sort algorithm. + * + * @see SortAlgorithm + */ public class SelectionSort implements SortAlgorithm { /** * Generic Selection Sort algorithm. @@ -11,11 +16,12 @@ public class SelectionSort implements SortAlgorithm { * * Space Complexity: O(1) – in-place sorting. * - * @see SortAlgorithm + * @param array the array to be sorted + * @param the type of elements in the array + * @return the sorted array */ @Override public > T[] sort(T[] array) { - for (int i = 0; i < array.length - 1; i++) { final int minIndex = findIndexOfMin(array, i); SortUtils.swap(array, i, minIndex); @@ -23,6 +29,14 @@ public > T[] sort(T[] array) { return array; } + /** + * Finds the index of the minimum element in the array starting from a given index. + * + * @param array the array to search + * @param startIndex the index to start searching from + * @param the type of elements in the array + * @return the index of the minimum element + */ private static > int findIndexOfMin(T[] array, final int startIndex) { int minIndex = startIndex; for (int i = startIndex + 1; i < array.length; i++) { From e3692c75d4e18750171ef3350c983bd91f890650 Mon Sep 17 00:00:00 2001 From: DishaSethi <142926792+DishaSethi@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:11:37 +0530 Subject: [PATCH 39/63] docs: cross-reference nth-fibonacci implementations via javadocs (#7497) * docs: cross-reference nth-fibonacci implementations via javadocs * style: remove trailing whitespaces in Javadocs --------- Co-authored-by: Deniz Altunkapan --- .../dynamicprogramming/Fibonacci.java | 18 +++++++++++++++--- .../maths/FibonacciJavaStreams.java | 18 ++++++++++++++++-- .../com/thealgorithms/maths/FibonacciLoop.java | 15 +++++++++++++-- .../maths/FibonacciNumberCheck.java | 12 ++++++++++++ .../maths/FibonacciNumberGoldenRation.java | 12 ++++++++++++ .../recursion/FibonacciSeries.java | 12 +++++++++++- 6 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java index 0d6aff2bbef3..df158112d233 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java @@ -1,10 +1,22 @@ package com.thealgorithms.dynamicprogramming; - import java.util.HashMap; import java.util.Map; - /** - * @author Varun Upadhyay (https://github.com/varunu28) + * Collection of Dynamic Programming techniques to solve for the n-th Fibonacci number. + *

+ * This file showcases Top-Down Memoization ({@code fibMemo}), Bottom-Up Tabulation ({@code fibBotUp}), + * and Space-Optimized Iteration ({@code fibOptimized}). + *

+ * For alternative structural paradigms, mathematical formulas, or verification steps, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ * * @author Varun Upadhyay (https://github.com/varunu28) */ public final class Fibonacci { private Fibonacci() { diff --git a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java index 84390860ccc4..8a93580a4b72 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java @@ -6,9 +6,23 @@ import java.util.stream.Stream; /** - * @author: caos321 - * @date: 14 October 2021 (Thursday) + * Calculates Fibonacci numbers using a functional programming paradigm with Java Streams. + *

+ * This specific implementation uses {@link java.util.stream.Stream#iterate} and reductions to generate terms. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ * * @author caos321 + * @date 14 October 2021 (Thursday) */ + public final class FibonacciJavaStreams { private FibonacciJavaStreams() { } diff --git a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java index de23a4305c3f..f19e3a6969c5 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java @@ -1,9 +1,20 @@ package com.thealgorithms.maths; - import java.math.BigInteger; - /** * This class provides methods for calculating Fibonacci numbers using BigInteger for large values of 'n'. + *

+ * This specific implementation uses an Iterative approach (Loop) with {@code O(n)} time complexity + * and {@code O(1)} space complexity. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ public final class FibonacciLoop { diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java index 781275d3130d..6bdc2d6ed9fb 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java @@ -4,6 +4,18 @@ * Fibonacci: 0 1 1 2 3 5 8 13 21 ... * This code checks Fibonacci Numbers up to 45th number. * Other checks fail because of 'long'-type overflow. + *

+ * This class serves as a verification utility rather than a generation algorithm. + *

+ * For approaches that actively compute the n-th Fibonacci number, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ public final class FibonacciNumberCheck { private FibonacciNumberCheck() { diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java index 4df37a40f541..eca6379b93bc 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java @@ -3,6 +3,18 @@ /** * This class provides methods for calculating Fibonacci numbers using Binet's formula. * Binet's formula is based on the golden ratio and allows computing Fibonacci numbers efficiently. + *

+ * This specific implementation provides a closed-form solution with an expected {@code O(1)} time complexity. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
* * @see Binet's formula on Wikipedia */ diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java index 9c809858099e..404adfdf0cf1 100644 --- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java +++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java @@ -7,8 +7,18 @@ * Example: * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ... *

+ *

+ * This specific implementation demonstrates a Naive Recursive approach with {@code O(2^n)} time complexity. + * For more performant variations or different programming paradigms, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming variants (Memoization / Bottom-Up)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ - public final class FibonacciSeries { private FibonacciSeries() { throw new UnsupportedOperationException("Utility class"); From d4ffef793a6c990977ca6a4d0f0d4dfae9c35c1d Mon Sep 17 00:00:00 2001 From: Simarpreet Singh Date: Mon, 29 Jun 2026 08:17:12 +0100 Subject: [PATCH 40/63] Remove duplicate Dijkstra implementation in others directory (#7500) --- .../com/thealgorithms/others/Dijkstra.java | 248 ------------------ 1 file changed, 248 deletions(-) delete mode 100644 src/main/java/com/thealgorithms/others/Dijkstra.java diff --git a/src/main/java/com/thealgorithms/others/Dijkstra.java b/src/main/java/com/thealgorithms/others/Dijkstra.java deleted file mode 100644 index a379100a2f3b..000000000000 --- a/src/main/java/com/thealgorithms/others/Dijkstra.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.thealgorithms.others; - -import java.util.HashMap; -import java.util.Map; -import java.util.NavigableSet; -import java.util.TreeSet; -/** - * Dijkstra's algorithm,is a graph search algorithm that solves the - * single-source shortest path problem for a graph with nonnegative edge path - * costs, producing a shortest path tree. - * - *

- * NOTE: The inputs to Dijkstra's algorithm are a directed and weighted graph - * consisting of 2 or more nodes, generally represented by an adjacency matrix - * or list, and a start node. - * - *

- * Original source of code: - * https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Java Also most of the - * comments are from RosettaCode. - */ -public final class Dijkstra { - private Dijkstra() { - } - - private static final Graph.Edge[] GRAPH = { - // Distance from node "a" to node "b" is 7. - // In the current Graph there is no way to move the other way (e,g, from "b" to "a"), - // a new edge would be needed for that - new Graph.Edge("a", "b", 7), - new Graph.Edge("a", "c", 9), - new Graph.Edge("a", "f", 14), - new Graph.Edge("b", "c", 10), - new Graph.Edge("b", "d", 15), - new Graph.Edge("c", "d", 11), - new Graph.Edge("c", "f", 2), - new Graph.Edge("d", "e", 6), - new Graph.Edge("e", "f", 9), - }; - private static final String START = "a"; - private static final String END = "e"; - - /** - * main function Will run the code with "GRAPH" that was defined above. - */ - public static void main(String[] args) { - Graph g = new Graph(GRAPH); - g.dijkstra(START); - g.printPath(END); - // g.printAllPaths(); - } -} - -class Graph { - - // mapping of vertex names to Vertex objects, built from a set of Edges - - private final Map graph; - - /** - * One edge of the graph (only used by Graph constructor) - */ - public static class Edge { - - public final String v1; - public final String v2; - public final int dist; - - Edge(String v1, String v2, int dist) { - this.v1 = v1; - this.v2 = v2; - this.dist = dist; - } - } - - /** - * One vertex of the graph, complete with mappings to neighbouring vertices - */ - public static class Vertex implements Comparable { - - public final String name; - // MAX_VALUE assumed to be infinity - public int dist = Integer.MAX_VALUE; - public Vertex previous = null; - public final Map neighbours = new HashMap<>(); - - Vertex(String name) { - this.name = name; - } - - private void printPath() { - if (this == this.previous) { - System.out.printf("%s", this.name); - } else if (this.previous == null) { - System.out.printf("%s(unreached)", this.name); - } else { - this.previous.printPath(); - System.out.printf(" -> %s(%d)", this.name, this.dist); - } - } - - public int compareTo(Vertex other) { - if (dist == other.dist) { - return name.compareTo(other.name); - } - - return Integer.compare(dist, other.dist); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - if (!super.equals(object)) { - return false; - } - - Vertex vertex = (Vertex) object; - - if (dist != vertex.dist) { - return false; - } - if (name != null ? !name.equals(vertex.name) : vertex.name != null) { - return false; - } - if (previous != null ? !previous.equals(vertex.previous) : vertex.previous != null) { - return false; - } - return neighbours != null ? neighbours.equals(vertex.neighbours) : vertex.neighbours == null; - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (name != null ? name.hashCode() : 0); - result = 31 * result + dist; - result = 31 * result + (previous != null ? previous.hashCode() : 0); - result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "(" + name + ", " + dist + ")"; - } - } - - /** - * Builds a graph from a set of edges - */ - Graph(Edge[] edges) { - graph = new HashMap<>(edges.length); - - // one pass to find all vertices - for (Edge e : edges) { - if (!graph.containsKey(e.v1)) { - graph.put(e.v1, new Vertex(e.v1)); - } - if (!graph.containsKey(e.v2)) { - graph.put(e.v2, new Vertex(e.v2)); - } - } - - // another pass to set neighbouring vertices - for (Edge e : edges) { - graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist); - // graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an - // undirected graph - } - } - - /** - * Runs dijkstra using a specified source vertex - */ - public void dijkstra(String startName) { - if (!graph.containsKey(startName)) { - System.err.printf("Graph doesn't contain start vertex \"%s\"%n", startName); - return; - } - final Vertex source = graph.get(startName); - NavigableSet q = new TreeSet<>(); - - // set-up vertices - for (Vertex v : graph.values()) { - v.previous = v == source ? source : null; - v.dist = v == source ? 0 : Integer.MAX_VALUE; - q.add(v); - } - - dijkstra(q); - } - - /** - * Implementation of dijkstra's algorithm using a binary heap. - */ - private void dijkstra(final NavigableSet q) { - Vertex u; - Vertex v; - while (!q.isEmpty()) { - // vertex with shortest distance (first iteration will return source) - u = q.pollFirst(); - if (u.dist == Integer.MAX_VALUE) { - break; // we can ignore u (and any other remaining vertices) since they are - // unreachable - } - // look at distances to each neighbour - for (Map.Entry a : u.neighbours.entrySet()) { - v = a.getKey(); // the neighbour in this iteration - - final int alternateDist = u.dist + a.getValue(); - if (alternateDist < v.dist) { // shorter path to neighbour found - q.remove(v); - v.dist = alternateDist; - v.previous = u; - q.add(v); - } - } - } - } - - /** - * Prints a path from the source to the specified vertex - */ - public void printPath(String endName) { - if (!graph.containsKey(endName)) { - System.err.printf("Graph doesn't contain end vertex \"%s\"%n", endName); - return; - } - - graph.get(endName).printPath(); - System.out.println(); - } - - /** - * Prints the path from the source to every vertex (output order is not - * guaranteed) - */ - public void printAllPaths() { - for (Vertex v : graph.values()) { - v.printPath(); - System.out.println(); - } - } -} From 676175dd3725f5ca3a6bac4de38e7612f9818907 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:46:43 +0200 Subject: [PATCH 41/63] chore(deps): bump org.junit:junit-bom from 6.1.0 to 6.1.1 (#7501) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.1.0 to 6.1.1. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.1.0...r6.1.1) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-version: 6.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 927395112259..2b17730cdffb 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 6.1.0 + 6.1.1 pom import From 12aab706ab3980efd968810c0489b146db23cec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:50:22 +0000 Subject: [PATCH 42/63] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.6.0 to 13.7.0 (#7502) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.6.0 to 13.7.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.6.0...checkstyle-13.7.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2b17730cdffb..5ba6c1848510 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.6.0 + 13.7.0 From c6fa50fb0c2970891de6bc30012ffa4b115695a9 Mon Sep 17 00:00:00 2001 From: Herley <33199364+herley-shaori@users.noreply.github.com> Date: Sat, 4 Jul 2026 04:09:47 +0700 Subject: [PATCH 43/63] =?UTF-8?q?docs:=20correct=20stale=20WiggleSort=20Ja?= =?UTF-8?q?vadoc=20=E2=80=94=20[1,=202,=202]=20is=20already=20detected,=20?= =?UTF-8?q?add=20regression=20tests=20(#7509)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs: correct stale WiggleSort Javadoc about undetected inputs The class Javadoc claimed [1, 2, 2] slips through undetected, but the odd-array median guard added later in wiggleSort() already catches exactly that case and throws IllegalArgumentException. Update the Javadoc to describe the current behavior and add regression tests asserting that [1, 2, 2] and arrays with too many duplicates throw instead of returning a wrongly ordered result. Fixes TheAlgorithms/Java#7507 --- .../com/thealgorithms/sorts/WiggleSort.java | 6 +++--- .../thealgorithms/sorts/WiggleSortTest.java | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/sorts/WiggleSort.java b/src/main/java/com/thealgorithms/sorts/WiggleSort.java index c272b820d07a..0349971d1c95 100644 --- a/src/main/java/com/thealgorithms/sorts/WiggleSort.java +++ b/src/main/java/com/thealgorithms/sorts/WiggleSort.java @@ -11,9 +11,9 @@ * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity * Also have a look at: * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity?noredirect=1&lq=1 - * Not all arrays are wiggle-sortable. This algorithm will find some obviously not wiggle-sortable - * arrays and throw an error, but there are some exceptions that won't be caught, for example [1, 2, - * 2]. + * Not all arrays are wiggle-sortable. This algorithm detects non-wiggle-sortable inputs — for + * example [1, 2, 2], or arrays where more than half the values are equal — and throws an + * IllegalArgumentException instead of returning a wrongly ordered result. */ public class WiggleSort implements SortAlgorithm { diff --git a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java index c5d57d63cf38..0d8b6acf9043 100644 --- a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.sorts; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -70,4 +71,22 @@ void wiggleTestStrings() { wiggleSort.sort(values); assertArrayEquals(values, result); } + + @Test + void wiggleTestNonWiggleSortableOddArrayThrows() { + // [1, 2, 2] is not wiggle-sortable: the median 2 appears ceil(3 / 2) = 2 times + // but is not the smallest value, so sorting must fail instead of returning + // a wrongly ordered array + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {1, 2, 2}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } + + @Test + void wiggleTestTooManyDuplicatesThrows() { + // more than half of the values are the same, which can never be wiggle-sorted + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {2, 2, 2, 1}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } } From 12ea4bb7aa1879c06be19f9ec5ccea67943d3d1e Mon Sep 17 00:00:00 2001 From: Herley <33199364+herley-shaori@users.noreply.github.com> Date: Sat, 4 Jul 2026 04:14:28 +0700 Subject: [PATCH 44/63] fix: Caesar cipher produces non-alphabetic output for negative shifts (#7508) fix: Caesar cipher produced non-alphabetic output for negative shifts normalizeShift cast a possibly-negative int directly to char, which wraps to a huge unsigned 16-bit value (e.g. (char) -1 == 65535). The subsequent char arithmetic then overflowed and emitted characters outside the Latin alphabet, e.g. encode("A", -1) returned '@' instead of 'Z', and the encode/decode round-trip was broken for negative shifts. Normalize the shift into the 0..25 range with ((shift % 26) + 26) % 26 and perform the character arithmetic in int, casting back to char only when appending. Add regression tests covering negative shifts and the encode/decode round-trip. Fixes TheAlgorithms/Java#7506 --- .../com/thealgorithms/ciphers/Caesar.java | 31 +++++++++---------- .../com/thealgorithms/ciphers/CaesarTest.java | 23 ++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java index 23535bc2b5d2..7a5e70e6eb78 100644 --- a/src/main/java/com/thealgorithms/ciphers/Caesar.java +++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java @@ -9,8 +9,8 @@ * @author khalil2535 */ public class Caesar { - private static char normalizeShift(final int shift) { - return (char) (shift % 26); + private static int normalizeShift(final int shift) { + return ((shift % 26) + 26) % 26; } /** @@ -22,21 +22,18 @@ private static char normalizeShift(final int shift) { public String encode(String message, int shift) { StringBuilder encoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = message.length(); for (int i = 0; i < length; i++) { - // int current = message.charAt(i); //using char to shift characters because - // ascii - // is in-order latin alphabet - char current = message.charAt(i); // Java law : char + int = char + final char current = message.charAt(i); if (isCapitalLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'Z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else { encoded.append(current); } @@ -53,17 +50,17 @@ public String encode(String message, int shift) { public String decode(String encryptedMessage, int shift) { StringBuilder decoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = encryptedMessage.length(); for (int i = 0; i < length; i++) { - char current = encryptedMessage.charAt(i); + final char current = encryptedMessage.charAt(i); if (isCapitalLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'A' ? shifted + 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'a' ? shifted + 26 : shifted)); // 26 = number of latin letters } else { decoded.append(current); } diff --git a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java index 7aa41c4cf423..c8b20ad8d8f9 100644 --- a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java +++ b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java @@ -32,6 +32,29 @@ void caesarDecryptTest() { assertEquals("Encrypt this text", cipherText); } + @Test + void caesarEncryptWithNegativeShiftTest() { + // a shift of -1 must wrap 'A' backwards to 'Z', like a shift of +25 would + assertEquals("Z", caesar.encode("A", -1)); + assertEquals("z", caesar.encode("a", -1)); + assertEquals("EBIIL", caesar.encode("HELLO", -3)); + } + + @Test + void caesarDecryptWithNegativeShiftTest() { + assertEquals("A", caesar.decode("Z", -1)); + assertEquals("HELLO", caesar.decode("EBIIL", -3)); + } + + @Test + void caesarNegativeShiftRoundTripTest() { + // encode followed by decode with the same shift must return the original text + for (int shift : new int[] {-1, -5, -25, -26, -27, -52}) { + String message = "The quick brown Fox"; + assertEquals(message, caesar.decode(caesar.encode(message, shift), shift)); + } + } + @Test void caesarBruteForce() { // given From 8304c1e93250fca3404046ee9914257c91a9d545 Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Sat, 4 Jul 2026 17:01:38 +0530 Subject: [PATCH 45/63] fix: this dependabot configuration does not set a co... in... (#7510) fix: package_managers.dependabot.dependabot-missing-cooldown.dependabot-missing-cooldown security vulnerability Automated security fix generated by OrbisAI Security --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2e5622f7b51d..1b91763b2d53 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,14 +5,20 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 - package-ecosystem: "github-actions" directory: "/.github/workflows/" schedule: interval: "daily" + cooldown: + default-days: 7 - package-ecosystem: "maven" directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 ... From c49837965842d0e5e647ed886f72305fc1dbf369 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:32:04 +0200 Subject: [PATCH 46/63] chore(deps): bump github/codeql-action from 4 to 4.36.2 in /.github/workflows (#7511) chore(deps): bump github/codeql-action in /.github/workflows Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4 to 4.36.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4...v4.36.2) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 14ea223946cd..9af40d55669f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.36.2 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.36.2 with: category: "/language:java-kotlin" @@ -55,12 +55,12 @@ jobs: uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.36.2 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.36.2 with: category: "/language:actions" ... From 80fc2bdcd637d9f03afe40f8e13b8e882c183cf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:47:18 +0200 Subject: [PATCH 47/63] chore(deps): bump actions/setup-java from 5 to 5.4.0 in /.github/workflows (#7516) chore(deps): bump actions/setup-java in /.github/workflows Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 5.4.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5...v5.4.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/infer.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8f4c8efa7e6..9cbb567747a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.4.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9af40d55669f..3fb71c5cf267 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.4.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 6bf5c56a91b1..9c095908d777 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.4.0 with: java-version: 21 distribution: 'temurin' From fd2858e7e6138d9f8940ee9820e172912a5acfb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 00:14:47 +0200 Subject: [PATCH 48/63] chore(deps): bump github/codeql-action from 4.36.2 to 4.36.3 in /.github/workflows (#7519) chore(deps): bump github/codeql-action in /.github/workflows Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.36.2...v4.36.3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3fb71c5cf267..a4389ee0ffcb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.2 + uses: github/codeql-action/init@v4.36.3 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.2 + uses: github/codeql-action/analyze@v4.36.3 with: category: "/language:java-kotlin" @@ -55,12 +55,12 @@ jobs: uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.2 + uses: github/codeql-action/init@v4.36.3 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.2 + uses: github/codeql-action/analyze@v4.36.3 with: category: "/language:actions" ... From 967348368ef77f387f1f634439ce378f89591bbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:16:57 +0300 Subject: [PATCH 49/63] chore(deps): bump actions/stale from 10 to 10.3.0 in /.github/workflows (#7522) Bumps [actions/stale](https://github.com/actions/stale) from 10 to 10.3.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v10...v10.3.0) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index bb613daf8f1d..2c8934bae274 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v10.3.0 with: stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution!' close-issue-message: 'Please reopen this issue once you have made the required changes. If you need help, feel free to ask in our [Discord](https://the-algorithms.com/discord) server or ping one of the maintainers here. Thank you for your contribution!' From 9b16a48f1f76b934ceb9ebd2c2f6886c6b9d6153 Mon Sep 17 00:00:00 2001 From: akankshanimmagadda Date: Mon, 13 Jul 2026 17:01:25 +0530 Subject: [PATCH 50/63] Enhance Javadoc for AccountMerge (#7523) add detailed Javadocs to AccountMerge file in graph Algorithms Co-authored-by: akanksha --- .../com/thealgorithms/graph/AccountMerge.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/AccountMerge.java b/src/main/java/com/thealgorithms/graph/AccountMerge.java index cf934a72eb68..86f0a24b6c1c 100644 --- a/src/main/java/com/thealgorithms/graph/AccountMerge.java +++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java @@ -10,13 +10,25 @@ /** * Merges account records using Disjoint Set Union (Union-Find) on shared emails. * - *

Input format: each account is a list where the first element is the user name and the - * remaining elements are emails. + *

Each account is expected to be a list where the first element is the user name and the + * remaining elements are email addresses. Accounts that share at least one email are merged into a + * single record. */ public final class AccountMerge { private AccountMerge() { + // Utility class; do not instantiate. } + /** + * Merges accounts that share one or more email addresses. + * + *

The returned list is sorted by account owner name, then by the first email address when + * multiple merged groups have the same owner name. Within each merged account, emails are + * returned in lexicographic order. + * + * @param accounts a list of accounts where each entry contains a user name followed by emails + * @return merged accounts, or an empty list when {@code accounts} is null or empty + */ public static List> mergeAccounts(List> accounts) { if (accounts == null || accounts.isEmpty()) { return List.of(); @@ -73,6 +85,9 @@ public static List> mergeAccounts(List> accounts) { return merged; } + /** + * Lightweight union-find structure with path compression and union by rank. + */ private static final class UnionFind { private final int[] parent; private final int[] rank; From 0125123383c450f3119dbd42ae0b4031f793e2f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:53:59 +0300 Subject: [PATCH 51/63] chore(deps): bump actions/setup-java from 5.4.0 to 5.5.0 in /.github/workflows (#7525) chore(deps): bump actions/setup-java in /.github/workflows Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.4.0 to 5.5.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5.4.0...v5.5.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/infer.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cbb567747a6..eb5657a9408c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.4.0 + uses: actions/setup-java@v5.5.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a4389ee0ffcb..69794d037aaa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.4.0 + uses: actions/setup-java@v5.5.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 9c095908d777..1cef578633de 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.4.0 + uses: actions/setup-java@v5.5.0 with: java-version: 21 distribution: 'temurin' From e45b6ac1400b37e80f9ccca446324d7f86f8f998 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 00:47:58 +0300 Subject: [PATCH 52/63] chore(deps): bump github/codeql-action from 4.36.3 to 4.37.0 in /.github/workflows (#7526) chore(deps): bump github/codeql-action in /.github/workflows Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.36.3...v4.37.0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 69794d037aaa..3cd8fc7dfa56 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.3 + uses: github/codeql-action/init@v4.37.0 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.3 + uses: github/codeql-action/analyze@v4.37.0 with: category: "/language:java-kotlin" @@ -55,12 +55,12 @@ jobs: uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.3 + uses: github/codeql-action/init@v4.37.0 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.3 + uses: github/codeql-action/analyze@v4.37.0 with: category: "/language:actions" ... From 406347ce46908a6e30f7a5b078b8769c588a7f49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 01:07:44 +0300 Subject: [PATCH 53/63] chore(deps): bump actions/stale from 10.3.0 to 10.4.0 in /.github/workflows (#7527) chore(deps): bump actions/stale in /.github/workflows Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to 10.4.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v10.3.0...v10.4.0) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 2c8934bae274..c94d2040aac4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v10.3.0 + - uses: actions/stale@v10.4.0 with: stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution!' close-issue-message: 'Please reopen this issue once you have made the required changes. If you need help, feel free to ask in our [Discord](https://the-algorithms.com/discord) server or ping one of the maintainers here. Thank you for your contribution!' From 93cf85e1f4e24a375588cc139a19f538ecb3db73 Mon Sep 17 00:00:00 2001 From: Rosander0 <213166773+Rosander0@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:55:04 +0530 Subject: [PATCH 54/63] Add LinearRegression Implementation (#7520) feat: Add Linear Regression Implementation --- .../machinelearning/LinearRegression.java | 102 ++++++++++++++++++ .../machinelearning/LinearRegressionTest.java | 95 ++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 src/main/java/com/thealgorithms/machinelearning/LinearRegression.java create mode 100644 src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java diff --git a/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java new file mode 100644 index 000000000000..134d4eee8c4c --- /dev/null +++ b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java @@ -0,0 +1,102 @@ +package com.thealgorithms.machinelearning; + +/** + * A simple Linear Regression model implemented from scratch using Gradient Descent. + * + * @see Linear Regression (Wikipedia) + * @author Vraj Prajapati (Rosander0) + */ +public class LinearRegression { + private double m; // Slope (weight) + private double b; // Y-intercept (bias) + private final double learningRate; + private final int epochs; + + /** + * Constructs a Linear Regression model with the given hyperparameters. + * + * @param learningRate controls the step size during gradient descent + * @param epochs the number of iterations to train the model + */ + public LinearRegression(double learningRate, int epochs) { + this.learningRate = learningRate; + this.epochs = epochs; + this.m = 0.0; + this.b = 0.0; + } + + /** + * Trains the model on the provided dataset using batch gradient descent. + * + * @param x the input feature values + * @param y the corresponding target values + * @throws IllegalArgumentException if the arrays are null, empty, or of differing lengths + */ + public void fit(double[] x, double[] y) { + if (x == null || y == null || x.length != y.length || x.length == 0) { + throw new IllegalArgumentException("X and Y must be non-null, non-empty, and of the same length."); + } + + int n = x.length; + + for (int epoch = 0; epoch < epochs; epoch++) { + double mGradient = 0; + double bGradient = 0; + + // Calculate gradients across the entire dataset + for (int i = 0; i < n; i++) { + double prediction = (m * x[i]) + b; + double error = prediction - y[i]; + + // Partial derivatives of the Mean Squared Error cost function + mGradient += error * x[i]; + bGradient += error; + } + + // Average the gradients and update the parameters + m -= 2.0 / n * mGradient * learningRate; + b -= 2.0 / n * bGradient * learningRate; + } + } + + /** + * Predicts the output for a given input x. + * + * @param x the input value + * @return the predicted output + */ + public double predict(double x) { + return (m * x) + b; + } + + /** + * Calculates the Mean Squared Error of the model against a dataset. + * + * @param x the input feature values + * @param y the corresponding target values + * @return the mean squared error + */ + public double calculateMSE(double[] x, double[] y) { + double totalSquaredError = 0; + int n = x.length; + for (int i = 0; i < n; i++) { + double error = predict(x[i]) - y[i]; + totalSquaredError += error * error; + } + return totalSquaredError / n; + } + + /** + * @return the learned slope of the regression line + */ + public double getSlope() { + return m; + } + + /** + * @return the learned y-intercept of the regression line + */ + public double getIntercept() { + return b; + } +} diff --git a/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java new file mode 100644 index 000000000000..e8a130d0ccb2 --- /dev/null +++ b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java @@ -0,0 +1,95 @@ +package com.thealgorithms.machinelearning; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class LinearRegressionTest { + + private static final double DELTA = 0.1; + + @Test + void fitLearnsCorrectSlopeAndIntercept() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(trueM, model.getSlope(), DELTA); + assertEquals(trueB, model.getIntercept(), DELTA); + } + + @Test + void predictMatchesExpectedOnUnseenData() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + double[] testInputs = {0.0, 3.5, 7.0}; + for (double testX : testInputs) { + double expectedY = (trueM * testX) + trueB; + assertEquals(expectedY, model.predict(testX), DELTA); + } + } + + @Test + void calculateMSEIsNearZeroAfterTraining() { + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0}; + double[] yTrain = {3.0, 5.0, 7.0, 9.0, 11.0}; // y = 2x + 1 + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(0.0, model.calculateMSE(xTrain, yTrain), 0.01); + } + + @Test + void fitThrowsExceptionOnMismatchedArrayLengths() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + double[] y = {1.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnEmptyArrays() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {}; + double[] y = {}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnNullX() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] y = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(null, y)); + } + + @Test + void fitThrowsExceptionOnNullY() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, null)); + } +} From 53fc7643cbddedb576a8e6d4af765703652069b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 06:06:09 +0000 Subject: [PATCH 55/63] chore(deps): bump actions/setup-python from 6 to 6.3.0 in /.github/workflows (#7528) chore(deps): bump actions/setup-python in /.github/workflows Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 6.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v6.3.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/project_structure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml index e7e703c27b70..3f27ad13a9cb 100644 --- a/.github/workflows/project_structure.yml +++ b/.github/workflows/project_structure.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v6.3.0 with: python-version: '3.13' From f34cbbfcae80d3d97e627e1dc4edb7f4db69213b Mon Sep 17 00:00:00 2001 From: Rosander0 <213166773+Rosander0@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:40:03 +0530 Subject: [PATCH 56/63] Add LibrarySort Implementation (#7481) * feat: add LibrarySort implementation * major: adding the missing algorithm --- .../com/thealgorithms/sorts/LibrarySort.java | 207 ++++++++++++++++++ .../thealgorithms/sorts/LibrarySortTest.java | 70 ++++++ 2 files changed, 277 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/LibrarySort.java create mode 100644 src/test/java/com/thealgorithms/sorts/LibrarySortTest.java diff --git a/src/main/java/com/thealgorithms/sorts/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java new file mode 100644 index 000000000000..28f16e3016ff --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java @@ -0,0 +1,207 @@ +package com.thealgorithms.sorts; + +import java.util.Arrays; + +/** + * Library Sort (also known as Gapped Insertion Sort) maintains a sparse + * working array with gaps distributed between elements, so that most + * insertions land directly in an empty gap without shifting anything. + * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...); + * after each round the array is rebalanced so gaps are spread out evenly + * again for the next round. + * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse + * Space Complexity: O(n) + * + * @see + * Wikipedia: Library Sort + * @author Vraj Prajapati (@Rosander0) + */ +public final class LibrarySort { + + private static final int GAP_FACTOR = 2; + + private LibrarySort() { + // Utility class + } + + /** + * Sorts an array using the Library Sort algorithm. + * + * @param array the array to sort (must not be null) + * @return the sorted array + * @throws IllegalArgumentException if {@code array} is {@code null} + */ + public static int[] sort(final int[] array) { + if (array == null) { + throw new IllegalArgumentException("Input array must not be null."); + } + if (array.length <= 1) { + return array; + } + + final int n = array.length; + final int capacity = GAP_FACTOR * n; + final int[] data = new int[capacity]; + final boolean[] occupied = new boolean[capacity]; + + final int mid = capacity / 2; + data[mid] = array[0]; + occupied[mid] = true; + + int filled = 1; + int nextToInsert = 1; + int round = 0; + while (nextToInsert < n) { + final int roundSize = Math.min(1 << round, n - nextToInsert); + for (int i = 0; i < roundSize; i++) { + insert(data, occupied, array[nextToInsert + i]); + filled++; + } + nextToInsert += roundSize; + round++; + if (nextToInsert < n) { + rebalance(data, occupied, filled); + } + } + + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + array[idx++] = data[i]; + } + } + return array; + } + + /** + * Inserts {@code value} into the gapped array, placing it directly in an + * empty gap when possible, otherwise shifting toward the nearest gap. + */ + private static void insert(final int[] data, final boolean[] occupied, final int value) { + final int pos = findInsertionIndex(data, occupied, value); + if (pos >= data.length) { + insertAtEnd(data, occupied, value); + return; + } + + if (!occupied[pos]) { + data[pos] = value; + occupied[pos] = true; + return; + } + + int right = pos; + while (right < data.length && occupied[right]) { + right++; + } + int left = pos - 1; + while (left >= 0 && occupied[left]) { + left--; + } + + final boolean canGoRight = right < data.length; + final boolean canGoLeft = left >= 0; + + if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) { + // Shift data[pos, right) one slot to the right, opening a gap at pos. + // occupied[pos] is untouched by the copy and was already true. + System.arraycopy(data, pos, data, pos + 1, right - pos); + occupied[right] = true; + data[pos] = value; + } else if (canGoLeft) { + // Shift data[left + 1, pos) one slot to the left, opening a gap at pos - 1. + // occupied[pos - 1] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, pos - 1 - left); + occupied[left] = true; + data[pos - 1] = value; + } else { + // Unreachable in practice: canGoRight and canGoLeft can only both be false if + // every slot in this capacity-2n array is occupied, but at most n elements are + // ever present at once. Kept as a defensive guard against that invariant breaking. + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + } + + /** + * Handles insertion of a new global maximum, which must land after every + * currently occupied slot. Since there is no room to its right, this + * shifts occupied slots left into the nearest gap instead. + */ + private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) { + final int last = data.length - 1; + // occupied[last] is unreachable as false here: insertAtEnd() is only called when + // findInsertionIndex() returns data.length, which requires data[last] to already be + // occupied. Kept as a defensive guard in case that invariant is ever broken. + if (!occupied[last]) { + data[last] = value; + occupied[last] = true; + return; + } + int left = last - 1; + while (left >= 0 && occupied[left]) { + left--; + } + // left < 0 is unreachable in practice: at most n elements ever occupy this + // capacity-2n array, so fewer than half the slots left of `last` can be filled, + // guaranteeing a gap exists before the scan reaches index -1. + if (left < 0) { + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + // Shift data[left + 1, last] one slot to the left, opening a gap at last. + // occupied[last] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, last - left); + occupied[left] = true; + data[last] = value; + } + + /** + * Finds the leftmost index at which {@code value} can be inserted so + * that occupied slots remain sorted. Empty slots are compared using the + * value of the nearest occupied slot at or after them, which is a + * monotonic function of index and therefore safe to binary search over. + */ + private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) { + int lo = 0; + int hi = data.length; + while (lo < hi) { + final int mid = lo + (hi - lo) / 2; + final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid); + if (probe != Integer.MAX_VALUE && probe <= value) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + } + + private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) { + for (int i = index; i < data.length; i++) { + if (occupied[i]) { + return data[i]; + } + } + return Integer.MAX_VALUE; + } + + /** + * Redistributes the {@code filled} occupied elements evenly across the + * full capacity of {@code data}, restoring uniform gaps between them. + */ + private static void rebalance(final int[] data, final boolean[] occupied, final int filled) { + final int capacity = data.length; + final int[] temp = new int[filled]; + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + temp[idx++] = data[i]; + } + } + Arrays.fill(occupied, false); + for (int k = 0; k < filled; k++) { + final int pos = (int) ((long) k * capacity / filled); + data[pos] = temp[k]; + occupied[pos] = true; + } + } +} diff --git a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java new file mode 100644 index 000000000000..49783f332e75 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class LibrarySortTest { + + @Test + public void testBasicSort() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 3, 1, 4, 2})); + } + + @Test + public void testAlreadySorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {1, 2, 3, 4, 5})); + } + + @Test + public void testReverseSorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 4, 3, 2, 1})); + } + + @Test + public void testDuplicates() { + assertArrayEquals(new int[] {1, 2, 2, 3, 3}, LibrarySort.sort(new int[] {3, 2, 1, 3, 2})); + } + + @Test + public void testSingleElement() { + assertArrayEquals(new int[] {1}, LibrarySort.sort(new int[] {1})); + } + + @Test + public void testEmptyArray() { + assertArrayEquals(new int[] {}, LibrarySort.sort(new int[] {})); + } + + @Test + public void testNullArray() { + assertThrows(IllegalArgumentException.class, () -> LibrarySort.sort(null)); + } + + // --- Added to cover branches the tests above never reach --- + + @Test + public void testShiftLeftWhenRightSideIsFull() { + // Right side of the target slot is completely occupied, forcing a left shift. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6}, LibrarySort.sort(new int[] {0, 1, 2, 6, 4, 5, 3})); + } + + @Test + public void testTieBreakPrefersRightWhenDistancesEqual() { + // A gap exists on both sides at equal distance; algorithm should favor the right shift. + assertArrayEquals(new int[] {0, 1, 2, 3}, LibrarySort.sort(new int[] {0, 1, 3, 2})); + } + + @Test + public void testRightSearchRunsOffTheEnd() { + // No gap anywhere to the right of the target slot, all the way to the array's end. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 7, 6})); + } + + @Test + public void testInsertAtEndWithNoTrailingGap() { + // A new global maximum arrives with no trailing gap left, forcing insertAtEnd(). + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 6, 7})); + } +} From b3776772dd7a9a7d18b42466dabd70109fb5bd35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:34:21 +0200 Subject: [PATCH 57/63] chore(deps): bump org.junit:junit-bom from 6.1.1 to 6.1.2 (#7529) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.1.1 to 6.1.2. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.1.1...r6.1.2) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-version: 6.1.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ba6c1848510..048b8a0a75b9 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 6.1.1 + 6.1.2 pom import From 742162c55b7ec6ecd810af680cc83af76e1691dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:39:07 +0000 Subject: [PATCH 58/63] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.7.0 to 13.8.0 (#7530) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.7.0 to 13.8.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.7.0...checkstyle-13.8.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 048b8a0a75b9..d42b38d0bf87 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.7.0 + 13.8.0 From ceb7839beec84bec3518223e84578e435c9974bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:27:58 +0300 Subject: [PATCH 59/63] chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin from 4.10.2.0 to 4.10.3.0 (#7531) * chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.10.2.0 to 4.10.3.0. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.10.2.0...spotbugs-maven-plugin-4.10.3.0) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.10.3.0 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * fix: exclude new warnings --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: vil02 <65706193+vil02@users.noreply.github.com> --- pom.xml | 2 +- spotbugs-exclude.xml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d42b38d0bf87..80fb3a6f8b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.10.2.0 + 4.10.3.0 spotbugs-exclude.xml true diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 8c42802520e3..1c34e47fb651 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -59,6 +59,12 @@ + + + + + + From 8a20fa9ee4ada879d7df93bdaccb10f4d8d2f8ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:28:12 +0300 Subject: [PATCH 60/63] chore(deps): bump actions/setup-java from 5.5.0 to 5.6.0 in /.github/workflows (#7533) chore(deps): bump actions/setup-java in /.github/workflows Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.5.0 to 5.6.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5.5.0...v5.6.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/infer.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb5657a9408c..03ca693a5af0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.5.0 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3cd8fc7dfa56..88674a00c7ba 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.5.0 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 1cef578633de..cc15da8d0b00 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5.5.0 + uses: actions/setup-java@v5.6.0 with: java-version: 21 distribution: 'temurin' From e9f11bb79c0cc5b4e4d31032f82c9b4796ea6a24 Mon Sep 17 00:00:00 2001 From: Bohdan Ovchar Date: Thu, 23 Jul 2026 11:22:02 +0300 Subject: [PATCH 61/63] Fix incorrect absolute minimum calculation (#7536) * Fix incorrect absolute minimum calculation * Fix incorrect absolute minimum calculation * Handle Integer.MIN_VALUE overflow in AbsoluteMin --- .../com/thealgorithms/maths/AbsoluteMin.java | 19 ++++++++++--------- .../thealgorithms/maths/AbsoluteMinTest.java | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java index 1b9575a330dd..aab6fe0f426d 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java @@ -1,7 +1,5 @@ package com.thealgorithms.maths; -import java.util.Arrays; - public final class AbsoluteMin { private AbsoluteMin() { } @@ -13,14 +11,17 @@ private AbsoluteMin() { * @return The absolute min value */ public static int getMinValue(int... numbers) { - if (numbers.length == 0) { - throw new IllegalArgumentException("Numbers array cannot be empty"); + if (numbers == null || numbers.length == 0) { + throw new IllegalArgumentException("Numbers array cannot be empty or null"); } - var absMinWrapper = new Object() { int value = numbers[0]; }; - - Arrays.stream(numbers).skip(1).filter(number -> Math.abs(number) <= Math.abs(absMinWrapper.value)).forEach(number -> absMinWrapper.value = Math.min(absMinWrapper.value, number)); - - return absMinWrapper.value; + long absMin = numbers[0]; + for (int i = 1; i < numbers.length; i++) { + long current = numbers[i]; + if (Math.abs(current) < Math.abs(absMin) || (Math.abs(current) == Math.abs(absMin) && current < absMin)) { + absMin = current; + } + } + return (int) absMin; } } diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java index dfca757fd877..070ff4ae3147 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java @@ -11,12 +11,15 @@ public class AbsoluteMinTest { void testGetMinValue() { assertEquals(0, AbsoluteMin.getMinValue(4, 0, 16)); assertEquals(-2, AbsoluteMin.getMinValue(3, -10, -2)); + assertEquals(-2, AbsoluteMin.getMinValue(-3, -10, -2)); + assertEquals(2, AbsoluteMin.getMinValue(-3, -10, 2)); + assertEquals(2, AbsoluteMin.getMinValue(-5, 2)); + assertEquals(2, AbsoluteMin.getMinValue(2, -5)); } @Test void testGetMinValueWithNoArguments() { - Exception exception = assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); - assertEquals("Numbers array cannot be empty", exception.getMessage()); + assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); } @Test @@ -24,4 +27,13 @@ void testGetMinValueWithSameAbsoluteValues() { assertEquals(-5, AbsoluteMin.getMinValue(-5, 5)); assertEquals(-5, AbsoluteMin.getMinValue(5, -5)); } + + @Test + void testIntegerMinValueOverflow() { + assertEquals(1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 1)); + assertEquals(-1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, -1)); + assertEquals(0, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 0)); + assertEquals(Integer.MIN_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE)); + assertEquals(Integer.MAX_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE, Integer.MAX_VALUE)); + } } From af9aad308151ffeda699acdabed09a7e90e194a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:05:04 +0300 Subject: [PATCH 62/63] chore(deps): bump github/codeql-action from 4.37.0 to 4.37.1 in /.github/workflows (#7541) chore(deps): bump github/codeql-action in /.github/workflows Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4.37.0...v4.37.1) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 88674a00c7ba..59cfcb3e43f2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.0 + uses: github/codeql-action/init@v4.37.1 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.0 + uses: github/codeql-action/analyze@v4.37.1 with: category: "/language:java-kotlin" @@ -55,12 +55,12 @@ jobs: uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.0 + uses: github/codeql-action/init@v4.37.1 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.0 + uses: github/codeql-action/analyze@v4.37.1 with: category: "/language:actions" ... From ef61ee8920d3db606d94160af4307f5d1a80459f Mon Sep 17 00:00:00 2001 From: Rohit Date: Sun, 26 Jul 2026 22:22:36 +0530 Subject: [PATCH 63/63] fix: reject negative input in SumOfSquares and add tests (#7543) * fix: reject negative input in SumOfSquares and add tests * style: apply clang-format to SumOfSquares and its test --- .../java/com/thealgorithms/maths/SumOfSquares.java | 8 ++++++-- .../java/com/thealgorithms/maths/SumOfSquaresTest.java | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/SumOfSquares.java b/src/main/java/com/thealgorithms/maths/SumOfSquares.java index c050d5a75f7b..77acbcc2a609 100644 --- a/src/main/java/com/thealgorithms/maths/SumOfSquares.java +++ b/src/main/java/com/thealgorithms/maths/SumOfSquares.java @@ -5,7 +5,6 @@ * Find minimum number of perfect squares that sum to given number * * @see Lagrange's Four Square Theorem - * @author BEASTSHRIRAM */ public final class SumOfSquares { @@ -16,10 +15,15 @@ private SumOfSquares() { /** * Find minimum number of perfect squares that sum to n * - * @param n the target number + * @param n the target number (must be non-negative) * @return minimum number of squares needed + * @throws IllegalArgumentException if n is negative */ public static int minSquares(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); + } + if (isPerfectSquare(n)) { return 1; } diff --git a/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java index 834fe61a049e..02b3f614ca9a 100644 --- a/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java +++ b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java @@ -1,13 +1,12 @@ 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; /** * Test class for SumOfSquares - * - * @author BEASTSHRIRAM */ class SumOfSquaresTest { @@ -65,4 +64,11 @@ void testEdgeCases() { // Test edge case assertEquals(1, SumOfSquares.minSquares(0)); // 0^2 } + + @Test + void testNegativeInput() { + // Negative inputs should throw IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> SumOfSquares.minSquares(-1)); + assertThrows(IllegalArgumentException.class, () -> SumOfSquares.minSquares(-10)); + } }