From 79bc6201358862fd14fd13307b2bc52fcd89b201 Mon Sep 17 00:00:00 2001
From: Nick Zerjeski <57059725+nickzerjeski@users.noreply.github.com>
Date: Mon, 13 Apr 2026 13:02:04 +0200
Subject: [PATCH 01/96] feat(geometry): add line segment intersection utility
(#7376)
* feat(geometry): add line segment intersection utility
* test(geometry): cover more line intersection edge cases
* Address line intersection edge cases from review
* Apply clang-format fixes for line intersection
---
.../geometry/LineIntersection.java | 105 ++++++++++++++++++
.../geometry/LineIntersectionTest.java | 101 +++++++++++++++++
2 files changed, 206 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/geometry/LineIntersection.java
create mode 100644 src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java
diff --git a/src/main/java/com/thealgorithms/geometry/LineIntersection.java b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
new file mode 100644
index 000000000000..8d65833816b3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.geometry;
+
+import java.awt.geom.Point2D;
+import java.util.Optional;
+
+/**
+ * Utility methods for checking and computing 2D line segment intersections.
+ */
+public final class LineIntersection {
+ private LineIntersection() {
+ }
+
+ /**
+ * Checks whether two line segments intersect.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return true when the segments intersect (including touching endpoints)
+ */
+ public static boolean intersects(Point p1, Point p2, Point q1, Point q2) {
+ int o1 = orientation(p1, p2, q1);
+ int o2 = orientation(p1, p2, q2);
+ int o3 = orientation(q1, q2, p1);
+ int o4 = orientation(q1, q2, p2);
+
+ if (o1 != o2 && o3 != o4) {
+ return true;
+ }
+
+ if (o1 == 0 && onSegment(p1, q1, p2)) {
+ return true;
+ }
+ if (o2 == 0 && onSegment(p1, q2, p2)) {
+ return true;
+ }
+ if (o3 == 0 && onSegment(q1, p1, q2)) {
+ return true;
+ }
+ if (o4 == 0 && onSegment(q1, p2, q2)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes the single geometric intersection point between two non-parallel
+ * segments when it exists.
+ *
+ *
For parallel/collinear overlap, this method returns {@code Optional.empty()}.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return the intersection point when uniquely defined and on both segments
+ */
+ public static Optional intersectionPoint(Point p1, Point p2, Point q1, Point q2) {
+ if (!intersects(p1, p2, q1, q2)) {
+ return Optional.empty();
+ }
+
+ long x1 = p1.x();
+ long y1 = p1.y();
+ long x2 = p2.x();
+ long y2 = p2.y();
+ long x3 = q1.x();
+ long y3 = q1.y();
+ long x4 = q2.x();
+ long y4 = q2.y();
+
+ long denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (denominator == 0L) {
+ return sharedEndpoint(p1, p2, q1, q2);
+ }
+
+ long determinant1 = x1 * y2 - y1 * x2;
+ long determinant2 = x3 * y4 - y3 * x4;
+ long numeratorX = determinant1 * (x3 - x4) - (x1 - x2) * determinant2;
+ long numeratorY = determinant1 * (y3 - y4) - (y1 - y2) * determinant2;
+
+ return Optional.of(new Point2D.Double(numeratorX / (double) denominator, numeratorY / (double) denominator));
+ }
+
+ private static int orientation(Point a, Point b, Point c) {
+ long cross = ((long) b.x() - a.x()) * ((long) c.y() - a.y()) - ((long) b.y() - a.y()) * ((long) c.x() - a.x());
+ return Long.compare(cross, 0L);
+ }
+
+ private static Optional sharedEndpoint(Point p1, Point p2, Point q1, Point q2) {
+ if (p1.equals(q1) || p1.equals(q2)) {
+ return Optional.of(new Point2D.Double(p1.x(), p1.y()));
+ }
+ if (p2.equals(q1) || p2.equals(q2)) {
+ return Optional.of(new Point2D.Double(p2.x(), p2.y()));
+ }
+ return Optional.empty();
+ }
+
+ private static boolean onSegment(Point a, Point b, Point c) {
+ return b.x() >= Math.min(a.x(), c.x()) && b.x() <= Math.max(a.x(), c.x()) && b.y() >= Math.min(a.y(), c.y()) && b.y() <= Math.max(a.y(), c.y());
+ }
+}
diff --git a/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java
new file mode 100644
index 000000000000..9f60df51b65f
--- /dev/null
+++ b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.geometry;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.awt.geom.Point2D;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
+class LineIntersectionTest {
+
+ @Test
+ void testCrossingSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(4, 4);
+ Point q1 = new Point(0, 4);
+ Point q2 = new Point(4, 0);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testParallelSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(3, 3);
+ Point q1 = new Point(0, 1);
+ Point q2 = new Point(3, 4);
+
+ assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testTouchingAtEndpoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(4, 0);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testCollinearOverlapHasNoUniquePoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(4, 4);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(6, 6);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testCollinearDisjointSegments() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(3, 3);
+ Point q2 = new Point(5, 5);
+
+ assertFalse(LineIntersection.intersects(p1, p2, q1, q2));
+ assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty());
+ }
+
+ @Test
+ void testCollinearSegmentsTouchingAtEndpointHaveUniquePoint() {
+ Point p1 = new Point(0, 0);
+ Point p2 = new Point(2, 2);
+ Point q1 = new Point(2, 2);
+ Point q2 = new Point(4, 4);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+
+ @Test
+ void testVerticalAndHorizontalCrossingSegments() {
+ Point p1 = new Point(2, 0);
+ Point p2 = new Point(2, 5);
+ Point q1 = new Point(0, 3);
+ Point q2 = new Point(4, 3);
+
+ assertTrue(LineIntersection.intersects(p1, p2, q1, q2));
+ Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2);
+ assertTrue(intersection.isPresent());
+ assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9);
+ assertEquals(3.0, intersection.orElseThrow().getY(), 1e-9);
+ }
+}
From df8fd850584077c8a15039301d52c9efd1400dbc Mon Sep 17 00:00:00 2001
From: Prashant Maurya
Date: Tue, 14 Apr 2026 15:29:22 +0530
Subject: [PATCH 02/96] docs: add edge cases to JumpSearch documentation
(#7379)
* docs: add edge cases to JumpSearch documentation
* fix: remove trailing whitespace (checkstyle)
---
src/main/java/com/thealgorithms/searches/JumpSearch.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java
index cbc494c8c16a..5074aa7845c8 100644
--- a/src/main/java/com/thealgorithms/searches/JumpSearch.java
+++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java
@@ -36,6 +36,13 @@
* Space Complexity: O(1) - only uses a constant amount of extra space
*
*
+ * Edge Cases:
+ *
+ *
Empty array → returns -1
+ *
Element not present → returns -1
+ *
Single element array
+ *
+ *
* Note: Jump Search requires a sorted array. For unsorted arrays, use Linear Search.
* Compared to Linear Search (O(n)), Jump Search is faster for large arrays.
* Compared to Binary Search (O(log n)), Jump Search is less efficient but may be
From 14b6f9924216e5e0c4c2c70683c930bac369c1b9 Mon Sep 17 00:00:00 2001
From: Nick Zerjeski <57059725+nickzerjeski@users.noreply.github.com>
Date: Tue, 14 Apr 2026 12:03:00 +0200
Subject: [PATCH 03/96] test(searches): cover null input cases in
IterativeBinarySearch (#7375)
---
.../searches/IterativeBinarySearchTest.java | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
index b2e121ac1ba0..f291610b298b 100644
--- a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java
@@ -87,6 +87,26 @@ void testBinarySearchEmptyArray() {
assertEquals(-1, binarySearch.find(array, key), "The element should not be found in an empty array.");
}
+ /**
+ * Test for binary search with a null array.
+ */
+ @Test
+ void testBinarySearchNullArray() {
+ IterativeBinarySearch binarySearch = new IterativeBinarySearch();
+ Integer key = 1;
+ assertEquals(-1, binarySearch.find(null, key), "The element should not be found in a null array.");
+ }
+
+ /**
+ * Test for binary search with a null key.
+ */
+ @Test
+ void testBinarySearchNullKey() {
+ IterativeBinarySearch binarySearch = new IterativeBinarySearch();
+ Integer[] array = {1, 2, 4, 8, 16};
+ assertEquals(-1, binarySearch.find(array, null), "A null search key should return -1.");
+ }
+
/**
* Test for binary search on a large array.
*/
From b3e31b5a5cd1465e474b71d87b44d4659fcfda23 Mon Sep 17 00:00:00 2001
From: Nick Zerjeski <57059725+nickzerjeski@users.noreply.github.com>
Date: Wed, 15 Apr 2026 16:07:03 +0200
Subject: [PATCH 04/96] feat(graph): add DSU-based account merge algorithm
(#7377)
* feat(graph): add DSU-based account merge algorithm
* test(graph): add null and transitive account merge cases
* Handle no-email accounts in account merge
* Apply clang-format style to account merge tests
---
.../com/thealgorithms/graph/AccountMerge.java | 112 ++++++++++++++++++
.../thealgorithms/graph/AccountMergeTest.java | 61 ++++++++++
2 files changed, 173 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/graph/AccountMerge.java
create mode 100644 src/test/java/com/thealgorithms/graph/AccountMergeTest.java
diff --git a/src/main/java/com/thealgorithms/graph/AccountMerge.java b/src/main/java/com/thealgorithms/graph/AccountMerge.java
new file mode 100644
index 000000000000..cf934a72eb68
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java
@@ -0,0 +1,112 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Merges account records using Disjoint Set Union (Union-Find) on shared emails.
+ *
+ *
Input format: each account is a list where the first element is the user name and the
+ * remaining elements are emails.
+ */
+public final class AccountMerge {
+ private AccountMerge() {
+ }
+
+ public static List> mergeAccounts(List> accounts) {
+ if (accounts == null || accounts.isEmpty()) {
+ return List.of();
+ }
+
+ UnionFind dsu = new UnionFind(accounts.size());
+ Map emailToAccount = new HashMap<>();
+
+ for (int i = 0; i < accounts.size(); i++) {
+ List account = accounts.get(i);
+ for (int j = 1; j < account.size(); j++) {
+ String email = account.get(j);
+ Integer previous = emailToAccount.putIfAbsent(email, i);
+ if (previous != null) {
+ dsu.union(i, previous);
+ }
+ }
+ }
+
+ Map> rootToEmails = new LinkedHashMap<>();
+ for (Map.Entry entry : emailToAccount.entrySet()) {
+ int root = dsu.find(entry.getValue());
+ rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey());
+ }
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).size() <= 1) {
+ int root = dsu.find(i);
+ rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>());
+ }
+ }
+
+ List> merged = new ArrayList<>();
+ for (Map.Entry> entry : rootToEmails.entrySet()) {
+ int root = entry.getKey();
+ List emails = entry.getValue();
+ Collections.sort(emails);
+
+ List mergedAccount = new ArrayList<>();
+ mergedAccount.add(accounts.get(root).getFirst());
+ mergedAccount.addAll(emails);
+ merged.add(mergedAccount);
+ }
+
+ merged.sort((a, b) -> {
+ int cmp = a.getFirst().compareTo(b.getFirst());
+ if (cmp != 0) {
+ return cmp;
+ }
+ if (a.size() == 1 || b.size() == 1) {
+ return Integer.compare(a.size(), b.size());
+ }
+ return a.get(1).compareTo(b.get(1));
+ });
+ return merged;
+ }
+
+ private static final class UnionFind {
+ private final int[] parent;
+ private final int[] rank;
+
+ private UnionFind(int size) {
+ this.parent = new int[size];
+ this.rank = new int[size];
+ for (int i = 0; i < size; i++) {
+ parent[i] = i;
+ }
+ }
+
+ private int find(int x) {
+ if (parent[x] != x) {
+ parent[x] = find(parent[x]);
+ }
+ return parent[x];
+ }
+
+ private void union(int x, int y) {
+ int rootX = find(x);
+ int rootY = find(y);
+ if (rootX == rootY) {
+ return;
+ }
+
+ if (rank[rootX] < rank[rootY]) {
+ parent[rootX] = rootY;
+ } else if (rank[rootX] > rank[rootY]) {
+ parent[rootY] = rootX;
+ } else {
+ parent[rootY] = rootX;
+ rank[rootX]++;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/AccountMergeTest.java b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java
new file mode 100644
index 000000000000..291be677d894
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java
@@ -0,0 +1,61 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class AccountMergeTest {
+
+ @Test
+ void testMergeAccountsWithSharedEmails() {
+ List> accounts = List.of(List.of("abc", "abc@mail.com", "abx@mail.com"), List.of("abc", "abc@mail.com", "aby@mail.com"), List.of("Mary", "mary@mail.com"), List.of("John", "johnnybravo@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+
+ List> expected = List.of(List.of("John", "johnnybravo@mail.com"), List.of("Mary", "mary@mail.com"), List.of("abc", "abc@mail.com", "abx@mail.com", "aby@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testAccountsWithSameNameButNoSharedEmailStaySeparate() {
+ List> accounts = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+ List> expected = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testEmptyInput() {
+ assertEquals(List.of(), AccountMerge.mergeAccounts(List.of()));
+ }
+
+ @Test
+ void testNullInput() {
+ assertEquals(List.of(), AccountMerge.mergeAccounts(null));
+ }
+
+ @Test
+ void testTransitiveMergeAndDuplicateEmails() {
+ List> accounts = List.of(List.of("A", "a1@mail.com", "a2@mail.com"), List.of("A", "a2@mail.com", "a3@mail.com"), List.of("A", "a3@mail.com", "a4@mail.com", "a4@mail.com"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+
+ List> expected = List.of(List.of("A", "a1@mail.com", "a2@mail.com", "a3@mail.com", "a4@mail.com"));
+
+ assertEquals(expected, merged);
+ }
+
+ @Test
+ void testAccountsWithNoEmailsArePreserved() {
+ List> accounts = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
+
+ List> merged = AccountMerge.mergeAccounts(accounts);
+ List> expected = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob"));
+
+ assertEquals(expected, merged);
+ }
+}
From 0ad5d90012095fc90f3fba4ec28560421f7e0844 Mon Sep 17 00:00:00 2001
From: Senrian <47714364+Senrian@users.noreply.github.com>
Date: Wed, 22 Apr 2026 17:26:20 +0800
Subject: [PATCH 05/96] fix: remove malformed javadoc to fix -Werror build
failure (#7393) (#7394)
* fix: prevent NPE when array contains null elements
When searching for a non-null key in an array that contains null elements,
the sentinel linear search would throw a NullPointerException because it
called array[i].compareTo(key) without checking if array[i] is null.
Added null check for array[i] in the while loop condition to prevent NPE
and return the correct index when array elements themselves are null.
Issue: #7318 (related)
* fix: remove malformed @author javadoc in AnyBaseToAnyBase (issue #7393)
* fix: remove malformed javadoc in ReverseString (part of issue #7393)
---------
Co-authored-by: OpenClaw Agent
Co-authored-by: OpenClaw Bot
Co-authored-by: Deniz Altunkapan
---
.../java/com/thealgorithms/conversions/AnyBaseToAnyBase.java | 2 +-
.../java/com/thealgorithms/searches/SentinelLinearSearch.java | 3 ++-
src/main/java/com/thealgorithms/strings/ReverseString.java | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
index 7698cc832981..3d31cb3e7f6c 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
@@ -3,7 +3,7 @@
*
* Time Complexity: O(n) [or appropriate complexity]
* Space Complexity: O(n)
- * * @author Reshma Kakkirala
+ * @author Reshma Kakkirala
*/
package com.thealgorithms.conversions;
diff --git a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
index 1a5903a5d134..473fc2c3f094 100644
--- a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
+++ b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java
@@ -65,7 +65,8 @@ public > int find(T[] array, T key) {
int i = 0;
// Search without bound checking since sentinel guarantees we'll find the key
- while (array[i].compareTo(key) != 0) {
+ // Null check for array element to prevent NPE when array contains null elements
+ while (array[i] != null && array[i].compareTo(key) != 0) {
i++;
}
diff --git a/src/main/java/com/thealgorithms/strings/ReverseString.java b/src/main/java/com/thealgorithms/strings/ReverseString.java
index 7b918ebe1a59..e373dd0b7174 100644
--- a/src/main/java/com/thealgorithms/strings/ReverseString.java
+++ b/src/main/java/com/thealgorithms/strings/ReverseString.java
@@ -62,7 +62,7 @@ public static String reverse3(String string) {
/**
* Reverses the given string using a stack.
* This method uses a stack to reverse the characters of the string.
- * * @param str The input string to be reversed.
+ * @param str The input string to be reversed.
* @return The reversed string.
*/
public static String reverseStringUsingStack(String str) {
From 763b95b69b33f790093d1eae3599dfcc47f2a4c7 Mon Sep 17 00:00:00 2001
From: orbisai0security
Date: Wed, 22 Apr 2026 22:53:28 +0530
Subject: [PATCH 06/96] fix: aesencryption in AESEncryption.java (#7392)
fix: V-001 security vulnerability
Automated security fix generated by Orbis Security AI
---
src/main/java/com/thealgorithms/ciphers/AESEncryption.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/ciphers/AESEncryption.java b/src/main/java/com/thealgorithms/ciphers/AESEncryption.java
index 14582205442f..6f155e73f47d 100644
--- a/src/main/java/com/thealgorithms/ciphers/AESEncryption.java
+++ b/src/main/java/com/thealgorithms/ciphers/AESEncryption.java
@@ -38,7 +38,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Original Text:" + plainText);
System.out.println("AES Key (Hex Form):" + bytesToHex(secKey.getEncoded()));
System.out.println("Encrypted Text (Hex Form):" + bytesToHex(cipherText));
- System.out.println("Descrypted Text:" + decryptedText);
+ System.out.println("Decryption successful. Decrypted text matches original: " + decryptedText.equals(plainText));
}
/**
From 35b94ab4f8214b1a939ae504b7b83ed5d071625e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Apr 2026 20:42:24 +0200
Subject: [PATCH 07/96] chore(deps): bump com.puppycrawl.tools:checkstyle from
13.4.0 to 13.4.1 (#7404)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.4.0 to 13.4.1.
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.4.0...checkstyle-13.4.1)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 13.4.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 74b6cd9bd485..2a543a6549d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,7 +112,7 @@
com.puppycrawl.toolscheckstyle
- 13.4.0
+ 13.4.1
From 6db8e207669e7f1b689615222e7ee54c68dc4981 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 2 May 2026 22:16:55 +0200
Subject: [PATCH 08/96] chore(deps): bump com.puppycrawl.tools:checkstyle from
13.4.1 to 13.4.2 (#7411)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.4.1 to 13.4.2.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.4.1...checkstyle-13.4.2)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 13.4.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 2a543a6549d0..e0a3486b23bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,7 +112,7 @@
com.puppycrawl.toolscheckstyle
- 13.4.1
+ 13.4.2
From 2616e0950feaa3ff2e4a1bc1d9e0530eb979e442 Mon Sep 17 00:00:00 2001
From: Abdul-Rehman-svg
Date: Sun, 3 May 2026 17:30:16 +0500
Subject: [PATCH 09/96] Update Anagrams.java (#7409)
fix: remove invalid reference [1] in Anagrams.java
Co-authored-by: Deniz Altunkapan
---
src/main/java/com/thealgorithms/strings/Anagrams.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java
index 5b97af0758f2..7bd84d47508f 100644
--- a/src/main/java/com/thealgorithms/strings/Anagrams.java
+++ b/src/main/java/com/thealgorithms/strings/Anagrams.java
@@ -5,7 +5,7 @@
/**
* An anagram is a word or phrase formed by rearranging the letters of a different word or phrase,
- * typically using all the original letters exactly once.[1]
+ * typically using all the original letters exactly once.
* For example, the word anagram itself can be rearranged into nag a ram,
* also the word binary into brainy and the word adobe into abode.
* Reference from https://en.wikipedia.org/wiki/Anagram
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 10/96] 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 11/96] 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 12/96] 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.
+ *
+ *
+ *
+ * @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 13/96] [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 14/96] 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 15/96] 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 16/96] 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
+ *
+ * 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
*/
-
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 49/96] 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 50/96] 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.junitjunit-bom
- 6.1.0
+ 6.1.1pomimport
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 51/96] 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.toolscheckstyle
- 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 52/96] =?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 53/96] 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 54/96] 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 55/96] 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 56/96] 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 57/96] 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 58/96] 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 59/96] 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 60/96] 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 61/96] 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 62/96] 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 63/96] 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 64/96] 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 65/96] 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 66/96] 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.junitjunit-bom
- 6.1.1
+ 6.1.2pomimport
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 67/96] 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.toolscheckstyle
- 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 68/96] 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.spotbugsspotbugs-maven-plugin
- 4.10.2.0
+ 4.10.3.0spotbugs-exclude.xmltrue
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 69/96] 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 70/96] 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 71/96] 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 72/96] 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));
+ }
}
From ad1de6b0bc2c7a6892c83eaf2a3b1a3755e82289 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 28 Jul 2026 00:57:11 +0300
Subject: [PATCH 73/96] chore(deps): bump actions/setup-python from 6.3.0 to
7.0.0 in /.github/workflows (#7545)
chore(deps): bump actions/setup-python in /.github/workflows
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.3.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6.3.0...v7.0.0)
---
updated-dependencies:
- dependency-name: actions/setup-python
dependency-version: 7.0.0
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/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 3f27ad13a9cb..a70c3240ce08 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.3.0
+ - uses: actions/setup-python@v7.0.0
with:
python-version: '3.13'
From cceeac75e8e2aa2525f7a22a3600117e5562e383 Mon Sep 17 00:00:00 2001
From: Rajat Semwal
Date: Wed, 29 Jul 2026 02:44:46 +0530
Subject: [PATCH 74/96] Add rotting oranges (#7542)
* Add Rotting Oranges BFS solution
* Add reference URL to RottingOranges documentation
* style: apply clang-format to RottingOranges.java
* style: format 2D array initializers in RottingOrangesTest.java
---
.../datastructures/graphs/RottingOranges.java | 114 +++++++++++++++
.../graphs/RottingOrangesTest.java | 134 ++++++++++++++++++
2 files changed, 248 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java
create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java
new file mode 100644
index 000000000000..3ce8696f55ff
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java
@@ -0,0 +1,114 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Multi-source Breadth-First Search (BFS) implementation for the Rotting Oranges problem.
+ *
+ *
Suited to discrete, count-based features (e.g. word frequencies in text
+ * classification). Class priors and feature likelihoods are estimated from
+ * training data with Laplace (add-alpha) smoothing to avoid zero
+ * probabilities for unseen feature/class combinations. Predictions are made
+ * by comparing summed log-probabilities across classes, which avoids the
+ * numerical underflow that repeated multiplication of small probabilities
+ * would cause.
+ *
+ *
Reference:
+ * Naive Bayes classifier
+ *
+ * @author Vraj Prajapati(Rosander0)
+ */
+public final class MultinomialNaiveBayesClassifier {
+
+ private final double alpha;
+ private final Map logPriors;
+ private final Map logLikelihoods;
+ private int numFeatures;
+
+ /**
+ * Constructs a classifier with the given Laplace smoothing parameter.
+ *
+ * @param alpha smoothing constant; must be greater than 0. A value of 1.0
+ * corresponds to standard Laplace smoothing.
+ */
+ public MultinomialNaiveBayesClassifier(double alpha) {
+ if (alpha <= 0) {
+ throw new IllegalArgumentException("alpha must be greater than 0");
+ }
+ this.alpha = alpha;
+ this.logPriors = new HashMap<>();
+ this.logLikelihoods = new HashMap<>();
+ }
+
+ /** Constructs a classifier using the standard Laplace smoothing constant of 1.0. */
+ public MultinomialNaiveBayesClassifier() {
+ this(1.0);
+ }
+
+ /**
+ * Fits the classifier on the given feature matrix and labels.
+ *
+ * @param features training samples, each row a vector of non-negative
+ * feature counts
+ * @param labels class label for each row of {@code features}
+ */
+ public void fit(double[][] features, int[] labels) {
+ if (features.length == 0 || features.length != labels.length) {
+ throw new IllegalArgumentException("features and labels must be non-empty and of equal length");
+ }
+ logPriors.clear();
+ logLikelihoods.clear();
+ numFeatures = features[0].length;
+
+ Map classCounts = new HashMap<>();
+ Map featureSums = new HashMap<>();
+ Map totalFeatureCount = new HashMap<>();
+
+ for (int i = 0; i < features.length; i++) {
+ int label = labels[i];
+ classCounts.merge(label, 1, Integer::sum);
+ double[] sums = featureSums.computeIfAbsent(label, k -> new double[numFeatures]);
+ double total = totalFeatureCount.getOrDefault(label, 0.0);
+ for (int j = 0; j < numFeatures; j++) {
+ sums[j] += features[i][j];
+ total += features[i][j];
+ }
+ totalFeatureCount.put(label, total);
+ }
+
+ int totalSamples = features.length;
+ for (Map.Entry entry : featureSums.entrySet()) {
+ int label = entry.getKey();
+ double[] sums = entry.getValue();
+ int count = classCounts.getOrDefault(label, 0);
+ double total = totalFeatureCount.getOrDefault(label, 0.0);
+
+ logPriors.put(label, Math.log((double) count / totalSamples));
+
+ double denom = total + alpha * numFeatures;
+ double[] logLikelihood = new double[numFeatures];
+ for (int j = 0; j < numFeatures; j++) {
+ logLikelihood[j] = Math.log((sums[j] + alpha) / denom);
+ }
+ logLikelihoods.put(label, logLikelihood);
+ }
+ }
+
+ /**
+ * Predicts the most likely class for a single sample.
+ *
+ * @param sample feature vector of non-negative counts
+ * @return the predicted class label
+ */
+ public int predict(double[] sample) {
+ if (logPriors.isEmpty()) {
+ throw new IllegalStateException("classifier has not been fitted");
+ }
+ if (sample.length != numFeatures) {
+ throw new IllegalArgumentException("sample length must match training feature count");
+ }
+
+ int bestLabel = -1;
+ double bestScore = Double.NEGATIVE_INFINITY;
+
+ for (Map.Entry entry : logLikelihoods.entrySet()) {
+ int label = entry.getKey();
+ double[] logLikelihood = entry.getValue();
+ double score = logPriors.getOrDefault(label, Double.NEGATIVE_INFINITY);
+ for (int j = 0; j < numFeatures; j++) {
+ score += sample[j] * logLikelihood[j];
+ }
+ if (score > bestScore) {
+ bestScore = score;
+ bestLabel = label;
+ }
+ }
+ return bestLabel;
+ }
+
+ /**
+ * Predicts class labels for a batch of samples.
+ *
+ * @param samples feature vectors of non-negative counts
+ * @return predicted class label for each row of {@code samples}
+ */
+ public int[] predict(double[][] samples) {
+ int[] predictions = new int[samples.length];
+ for (int i = 0; i < samples.length; i++) {
+ predictions[i] = predict(samples[i]);
+ }
+ return predictions;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java b/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java
new file mode 100644
index 000000000000..5ccd31147ac3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java
@@ -0,0 +1,141 @@
+package com.thealgorithms.machinelearning;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class MultinomialNaiveBayesClassifierTest {
+
+ @Test
+ void predictsCorrectClassOnSeparableToyDataset() {
+ // Class 0 samples are dominated by feature 0; class 1 samples by feature 1.
+ double[][] features = {
+ {5, 1},
+ {6, 0},
+ {4, 1},
+ {1, 5},
+ {0, 6},
+ {1, 4},
+ };
+ int[] labels = {0, 0, 0, 1, 1, 1};
+
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ classifier.fit(features, labels);
+
+ assertEquals(0, classifier.predict(new double[] {5, 0}));
+ assertEquals(1, classifier.predict(new double[] {0, 5}));
+ }
+
+ @Test
+ void predictBatchMatchesIndividualPredictions() {
+ double[][] features = {
+ {3, 0},
+ {2, 0},
+ {0, 3},
+ {0, 2},
+ };
+ int[] labels = {0, 0, 1, 1};
+
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ classifier.fit(features, labels);
+
+ double[][] samples = {{4, 0}, {0, 4}};
+ int[] predictions = classifier.predict(samples);
+
+ assertEquals(classifier.predict(samples[0]), predictions[0]);
+ assertEquals(classifier.predict(samples[1]), predictions[1]);
+ }
+
+ @Test
+ void laplaceSmoothingKeepsZeroCountFeatureLogProbabilityFinite() {
+ // Feature index 1 never appears for class 0 in training data.
+ double[][] features = {
+ {2, 0},
+ {3, 0},
+ {0, 2},
+ {0, 3},
+ };
+ int[] labels = {0, 0, 1, 1};
+
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ classifier.fit(features, labels);
+
+ // A sample that hits class 0's zero-count feature should still produce
+ // a finite, usable prediction instead of -Infinity collapsing the score.
+ int prediction = classifier.predict(new double[] {1, 1});
+ assertTrue(prediction == 0 || prediction == 1);
+ }
+
+ @Test
+ void predictBeforeFitThrowsIllegalStateException() {
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ assertThrows(IllegalStateException.class, () -> classifier.predict(new double[] {1, 2}));
+ }
+
+ @Test
+ void nonPositiveAlphaThrowsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new MultinomialNaiveBayesClassifier(0.0));
+ assertThrows(IllegalArgumentException.class, () -> new MultinomialNaiveBayesClassifier(-1.0));
+ }
+
+ @Test
+ void mismatchedSampleLengthThrowsIllegalArgumentException() {
+ double[][] features = {
+ {1, 2},
+ {3, 4},
+ };
+ int[] labels = {0, 1};
+
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ classifier.fit(features, labels);
+
+ assertThrows(IllegalArgumentException.class, () -> classifier.predict(new double[] {1, 2, 3}));
+ }
+
+ @Test
+ void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() {
+ double[][] features = {
+ {1, 2},
+ {3, 4},
+ };
+ int[] labels = {0};
+
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ assertThrows(IllegalArgumentException.class, () -> classifier.fit(features, labels));
+ }
+
+ @Test
+ void emptyFeaturesArrayThrowsIllegalArgumentException() {
+ double[][] features = {};
+ int[] labels = {};
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+ assertThrows(IllegalArgumentException.class, () -> classifier.fit(features, labels));
+ }
+
+ @Test
+ void refittingReplacesPreviousModelState() {
+ MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier();
+
+ double[][] firstFeatures = {
+ {5, 0, 0},
+ {0, 5, 0},
+ {0, 0, 5},
+ };
+ int[] firstLabels = {0, 1, 2};
+ classifier.fit(firstFeatures, firstLabels);
+
+ double[][] secondFeatures = {
+ {5, 0},
+ {0, 5},
+ };
+ int[] secondLabels = {0, 1};
+ classifier.fit(secondFeatures, secondLabels);
+
+ // Class 2 existed in the first fit but not the second — it must not
+ // survive into predictions after refitting.
+ int prediction = classifier.predict(new double[] {2.5, 2.5});
+ assertTrue(prediction == 0 || prediction == 1);
+ }
+}
From 1f4e2f8d5bf5cd47c24e91dbe33a9b503a6f5c26 Mon Sep 17 00:00:00 2001
From: anshul kumar
Date: Fri, 31 Jul 2026 16:44:21 +0530
Subject: [PATCH 78/96] Add Concurrent Merge Sort Implementation (#7544)
* feat: add ConcurrentMergeSort implementation
* fix: resolve checkstyle, dead code, and add test coverage
* style: fix clang-format issues
* fix: resolve maven build failure
* fix: resolve spotbugs exception softening violation
* refactor: use CompletableFuture to bypass SpotBugs exception softening rule
* style: fix trailing blank line to satisfy clang-format
---
.../sorts/ConcurrentMergeSort.java | 145 ++++++++++++++++++
.../sorts/ConcurrentMergeSortTest.java | 93 +++++++++++
2 files changed, 238 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java
create mode 100644 src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java
diff --git a/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java
new file mode 100644
index 000000000000..062da01f380e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java
@@ -0,0 +1,145 @@
+package com.thealgorithms.sorts;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A concurrent implementation of the Merge Sort algorithm.
+ *
+ *
This implementation utilizes a divide-and-conquer strategy, distributing
+ * the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}.
+ * To prevent the overhead of thread creation and context switching from outweighing
+ * the benefits of concurrency, it falls back to a standard sequential merge sort
+ * when the sub-array size drops below a predefined threshold, or when the maximum
+ * concurrency depth is reached (preventing thread starvation deadlocks).
+ *
+ *
Complexity:
+ *
+ *
Time Complexity: $O(N \log N)$
+ *
Space Complexity: $O(N)$
+ *
+ */
+public final class ConcurrentMergeSort {
+
+ private ConcurrentMergeSort() {
+ }
+
+ /**
+ * Fallback threshold where the algorithm switches to standard sequential
+ * Merge Sort to prevent thread-creation overhead from ruining performance.
+ */
+ private static final int SEQUENTIAL_THRESHOLD = 8192;
+
+ /**
+ * Sorts the specified array of integers concurrently using Merge Sort.
+ *
+ * @param array the array to be sorted
+ */
+ public static void sort(int[] array) {
+ if (array == null || array.length <= 1) {
+ return;
+ }
+
+ int availableProcessors = Runtime.getRuntime().availableProcessors();
+
+ // Calculate a safe maximum depth to prevent creating more tasks than the pool can handle.
+ // This effectively prevents thread starvation deadlock in fixed-size thread pools,
+ // by forcing leaf tasks to run sequentially and eventually complete.
+ int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1;
+
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
+
+ try {
+ int[] tempArray = new int[array.length];
+ concurrentMergeSort(array, tempArray, 0, array.length - 1, executor, maxDepth);
+ } finally {
+ // Ensure the executor is gracefully shut down
+ executor.shutdown();
+ }
+ }
+
+ /**
+ * Recursively sorts the array utilizing the provided executor for concurrency.
+ *
+ * @param array the array to sort
+ * @param temp a temporary array for merging
+ * @param left the starting index of the sub-array
+ * @param right the ending index of the sub-array
+ * @param executor the {@link ThreadPoolExecutor} to handle concurrent tasks
+ * @param depth the remaining depth for allowing concurrent execution
+ */
+ private static void concurrentMergeSort(int[] array, int[] temp, int left, int right, ThreadPoolExecutor executor, int depth) {
+ int length = right - left + 1;
+
+ // Switch to sequential sort if the array is small or we have reached the maximum concurrent depth
+ if (length < SEQUENTIAL_THRESHOLD || depth <= 0) {
+ sequentialMergeSort(array, temp, left, right);
+ return;
+ }
+
+ int mid = left + (right - left) / 2;
+
+ // Submit the left half for concurrent execution
+ CompletableFuture leftTask = CompletableFuture.runAsync(() -> concurrentMergeSort(array, temp, left, mid, executor, depth - 1), executor);
+
+ // Process the right half in the current thread to optimize resource usage
+ concurrentMergeSort(array, temp, mid + 1, right, executor, depth - 1);
+
+ // Wait for the concurrently executed left half to complete
+ leftTask.join();
+
+ merge(array, temp, left, mid, right);
+ }
+
+ /**
+ * Sorts the specified sub-array sequentially using standard Merge Sort.
+ *
+ * @param array the array to sort
+ * @param temp a temporary array for merging
+ * @param left the starting index of the sub-array
+ * @param right the ending index of the sub-array
+ */
+ private static void sequentialMergeSort(int[] array, int[] temp, int left, int right) {
+ if (left >= right) {
+ return;
+ }
+
+ int mid = left + (right - left) / 2;
+ sequentialMergeSort(array, temp, left, mid);
+ sequentialMergeSort(array, temp, mid + 1, right);
+ merge(array, temp, left, mid, right);
+ }
+
+ /**
+ * Merges two sorted sub-arrays into a single sorted sub-array.
+ *
+ * @param array the original array containing the sub-arrays
+ * @param temp a temporary array used for merging
+ * @param left the starting index of the first sub-array
+ * @param mid the ending index of the first sub-array (and the partition point)
+ * @param right the ending index of the second sub-array
+ */
+ private static void merge(int[] array, int[] temp, int left, int mid, int right) {
+ System.arraycopy(array, left, temp, left, right - left + 1);
+
+ int i = left;
+ int j = mid + 1;
+ int k = left;
+
+ while (i <= mid && j <= right) {
+ if (temp[i] <= temp[j]) {
+ array[k++] = temp[i++];
+ } else {
+ array[k++] = temp[j++];
+ }
+ }
+
+ while (i <= mid) {
+ array[k++] = temp[i++];
+ }
+
+ // Remaining elements from the right half are already in their correct relative positions
+ }
+}
diff --git a/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java b/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java
new file mode 100644
index 000000000000..454d0bd26929
--- /dev/null
+++ b/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java
@@ -0,0 +1,93 @@
+package com.thealgorithms.sorts;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.util.Arrays;
+import java.util.Random;
+import org.junit.jupiter.api.Test;
+
+/**
+ * JUnit 5 test class for {@link ConcurrentMergeSort}.
+ */
+public class ConcurrentMergeSortTest {
+
+ @Test
+ public void testAlreadySortedArray() {
+ int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Already sorted array should remain unchanged.");
+ }
+
+ @Test
+ public void testReverseSortedArray() {
+ int[] array = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+ int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Reverse sorted array should be sorted correctly.");
+ }
+
+ @Test
+ public void testIdenticalElementsArray() {
+ int[] array = {5, 5, 5, 5, 5, 5, 5};
+ int[] expected = {5, 5, 5, 5, 5, 5, 5};
+
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Array with identical elements should be sorted correctly (unchanged).");
+ }
+
+ @Test
+ public void testLargeRandomArray() {
+ int size = 100_000;
+ int[] array = new int[size];
+ int[] expected = new int[size];
+ // Using a fixed seed for deterministic testing
+ Random random = new Random(42);
+
+ for (int i = 0; i < size; i++) {
+ int value = random.nextInt();
+ array[i] = value;
+ expected[i] = value;
+ }
+
+ // Generate the expected result using Java's highly optimized built-in sort
+ Arrays.sort(expected);
+
+ // This will easily trigger the concurrency threshold (8192) in the implementation
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Large random array should be sorted correctly utilizing concurrency.");
+ }
+
+ @Test
+ public void testEmptyArray() {
+ int[] array = {};
+ int[] expected = {};
+
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Empty array should be handled without errors.");
+ }
+
+ @Test
+ public void testSingleElementArray() {
+ int[] array = {42};
+ int[] expected = {42};
+
+ ConcurrentMergeSort.sort(array);
+
+ assertArrayEquals(expected, array, "Single element array should be handled without errors.");
+ }
+
+ @Test
+ public void testNullArray() {
+ int[] array = null;
+ ConcurrentMergeSort.sort(array);
+ org.junit.jupiter.api.Assertions.assertNull(array, "Null array should be handled without errors.");
+ }
+}
From 7c934add6ef8ce1090d6add0403e2dc381b66b1a Mon Sep 17 00:00:00 2001
From: Chaiyong Ragkhitwetsagul
Date: Sun, 2 Aug 2026 16:33:44 +0700
Subject: [PATCH 79/96] Reject null BitonicSort input explicitly (#7550)
Update BitonicSort.java
Added a descriptive null-input validation and a focused regression test.
---
src/main/java/com/thealgorithms/sorts/BitonicSort.java | 3 +++
.../java/com/thealgorithms/sorts/BitonicSortTest.java | 9 +++++++++
2 files changed, 12 insertions(+)
diff --git a/src/main/java/com/thealgorithms/sorts/BitonicSort.java b/src/main/java/com/thealgorithms/sorts/BitonicSort.java
index 1c1a3ac45540..a714809ea5b2 100644
--- a/src/main/java/com/thealgorithms/sorts/BitonicSort.java
+++ b/src/main/java/com/thealgorithms/sorts/BitonicSort.java
@@ -21,6 +21,9 @@ private enum Direction {
*/
@Override
public > T[] sort(T[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("The input array cannot be null");
+ }
if (array.length == 0) {
return array;
}
diff --git a/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java b/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java
index 60c4bbe9d342..2ee30c44e282 100644
--- a/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java
+++ b/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java
@@ -1,8 +1,17 @@
package com.thealgorithms.sorts;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
public class BitonicSortTest extends SortingAlgorithmTest {
@Override
SortAlgorithm getSortAlgorithm() {
return new BitonicSort();
}
+
+ @Test
+ void shouldRejectNullArray() {
+ assertThrows(IllegalArgumentException.class, () -> getSortAlgorithm().sort((Integer[]) null));
+ }
}
From 90f4231d9e072ec1dc855f22256a219f16e2edd8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 4 Aug 2026 00:54:41 +0300
Subject: [PATCH 80/96] chore(deps): bump com.puppycrawl.tools:checkstyle from
13.8.0 to 13.9.0 (#7554)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.8.0 to 13.9.0.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.8.0...checkstyle-13.9.0)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 13.9.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 80fb3a6f8b2c..31ca9a59e025 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,7 +112,7 @@
com.puppycrawl.toolscheckstyle
- 13.8.0
+ 13.9.0
From ec0f2cddccdeb484b8fa4728b2f93032a3ca610f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 4 Aug 2026 01:00:34 +0300
Subject: [PATCH 81/96] chore(deps): bump actions/stale from 10.4.0 to 11.0.0
in /.github/workflows (#7553)
chore(deps): bump actions/stale in /.github/workflows
Bumps [actions/stale](https://github.com/actions/stale) from 10.4.0 to 11.0.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.4.0...v11.0.0)
---
updated-dependencies:
- dependency-name: actions/stale
dependency-version: 11.0.0
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>
Co-authored-by: Oleksandr Klymenko
---
.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 c94d2040aac4..b961d15c240e 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.4.0
+ - uses: actions/stale@v11.0.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 171bdc5cb06df69d9070d5ed1f95a3544a4276e9 Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Wed, 5 Aug 2026 12:47:13 +0530
Subject: [PATCH 82/96] Fix infinite loop in JumpSearch when key exceeds last
element (#7555)
---
.../thealgorithms/searches/JumpSearch.java | 2 +-
.../searches/JumpSearchTest.java | 48 +++++++++++++++++++
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java
index 5074aa7845c8..4253f4182db1 100644
--- a/src/main/java/com/thealgorithms/searches/JumpSearch.java
+++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java
@@ -73,7 +73,7 @@ public > int find(T[] array, T key) {
int limit = blockSize;
// Jumping ahead to find the block where the key may be located
while (limit < length && key.compareTo(array[limit]) > 0) {
- limit = Math.min(limit + blockSize, length - 1);
+ limit += blockSize;
}
// Perform linear search within the identified block
diff --git a/src/test/java/com/thealgorithms/searches/JumpSearchTest.java b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java
index 3fa319b66a41..a5ce93b8a3af 100644
--- a/src/test/java/com/thealgorithms/searches/JumpSearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java
@@ -2,7 +2,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
/**
* Unit tests for the JumpSearch class.
@@ -91,4 +93,50 @@ void testJumpSearchLargeArrayNotFound() {
Integer key = 999; // Key not present
assertEquals(-1, jumpSearch.find(array, key), "The element should not be found in the array.");
}
+
+ /**
+ * A key greater than every element used to make the jumping loop spin forever, because the
+ * cursor was clamped to the last index and therefore stopped advancing.
+ */
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
+ void testJumpSearchKeyGreaterThanLastElement() {
+ JumpSearch jumpSearch = new JumpSearch();
+ Integer[] array = {1, 2, 3, 4};
+ assertEquals(-1, jumpSearch.find(array, 5), "A key above the maximum should not be found.");
+ }
+
+ /**
+ * The same regression across several lengths, since the jump size depends on the array length.
+ */
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
+ void testJumpSearchKeyGreaterThanLastElementForEveryLength() {
+ JumpSearch jumpSearch = new JumpSearch();
+ for (int length = 1; length <= 50; length++) {
+ Integer[] array = new Integer[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = i;
+ }
+ assertEquals(-1, jumpSearch.find(array, length), "A key above the maximum should not be found for length " + length + ".");
+ }
+ }
+
+ /**
+ * Every element must be found regardless of the array length, including the ones that sit
+ * exactly on a jump boundary.
+ */
+ @Test
+ void testJumpSearchFindsEveryElement() {
+ JumpSearch jumpSearch = new JumpSearch();
+ for (int length = 1; length <= 50; length++) {
+ Integer[] array = new Integer[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = i * 2;
+ }
+ for (int i = 0; i < length; i++) {
+ assertEquals(i, jumpSearch.find(array, i * 2), "Element at index " + i + " should be found for length " + length + ".");
+ }
+ }
+ }
}
From 55f551b17bd0c48b7cb1ba7ecfb7092803c7a150 Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Wed, 5 Aug 2026 13:07:32 +0530
Subject: [PATCH 83/96] Fix ExponentialSearch missing boundary elements and not
returning -1 (#7556)
Co-authored-by: Oleksandr Klymenko
---
.../searches/ExponentialSearch.java | 5 ++-
.../searches/ExponentialSearchTest.java | 42 +++++++++++++++++++
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
index 9187dcbc2f4b..e666b9148aaa 100644
--- a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
+++ b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java
@@ -46,6 +46,9 @@ public > int find(T[] array, T key) {
range = range * 2;
}
- return Arrays.binarySearch(array, range / 2, Math.min(range, array.length), key);
+ // The candidate block is the inclusive index range [range / 2, range], so the
+ // exclusive upper bound handed to binarySearch has to be range + 1.
+ final int index = Arrays.binarySearch(array, range / 2, Math.min(range + 1, array.length), key);
+ return index >= 0 ? index : -1;
}
}
diff --git a/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java
index c84da531e8a4..c6b07ca2b4d5 100644
--- a/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java
@@ -81,4 +81,46 @@ void testExponentialSearchLargeArray() {
int expectedIndex = 9999;
assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the last element should be 9999.");
}
+
+ /**
+ * An element sitting exactly on the doubling boundary used to be reported as missing, because
+ * the binary search was handed {@code range} as its exclusive upper bound instead of
+ * {@code range + 1}.
+ */
+ @Test
+ void testExponentialSearchElementOnRangeBoundary() {
+ ExponentialSearch exponentialSearch = new ExponentialSearch();
+ Integer[] array = {-25, -9, 8, 21};
+ assertEquals(2, exponentialSearch.find(array, 8), "The index of the found element should be 2.");
+ }
+
+ /**
+ * Every element must be found regardless of the array length.
+ */
+ @Test
+ void testExponentialSearchFindsEveryElement() {
+ ExponentialSearch exponentialSearch = new ExponentialSearch();
+ for (int length = 1; length <= 50; length++) {
+ Integer[] array = new Integer[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = i * 2;
+ }
+ for (int i = 0; i < length; i++) {
+ assertEquals(i, exponentialSearch.find(array, i * 2), "Element at index " + i + " should be found for length " + length + ".");
+ }
+ }
+ }
+
+ /**
+ * A missing key has to yield -1 rather than the negative insertion point that
+ * {@link java.util.Arrays#binarySearch} returns.
+ */
+ @Test
+ void testExponentialSearchNotFoundReturnsMinusOne() {
+ ExponentialSearch exponentialSearch = new ExponentialSearch();
+ Integer[] array = {1, 3, 5, 7, 9, 11};
+ assertEquals(-1, exponentialSearch.find(array, 4), "A key inside the range but absent should give -1.");
+ assertEquals(-1, exponentialSearch.find(array, 0), "A key below the minimum should give -1.");
+ assertEquals(-1, exponentialSearch.find(array, 12), "A key above the maximum should give -1.");
+ }
}
From 0b0f9218ddeec71548363ac4d5eddf354988f127 Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Thu, 6 Aug 2026 01:10:27 +0530
Subject: [PATCH 84/96] Fix out-of-bounds access and identity comparison in
FibonacciSearch (#7557)
---
.../searches/FibonacciSearch.java | 2 +-
.../searches/FibonacciSearchTest.java | 49 +++++++++++++++++++
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
index 78dac0f0a712..fa91cd14a1af 100644
--- a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
+++ b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java
@@ -69,7 +69,7 @@ public > int find(T[] array, T key) {
}
}
- if (fibMinus1 == 1 && array[offset + 1] == key) {
+ if (fibMinus1 == 1 && offset + 1 < n && array[offset + 1].compareTo(key) == 0) {
return offset + 1;
}
diff --git a/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java
index 801c33b1d09a..04a270864223 100644
--- a/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java
+++ b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java
@@ -121,4 +121,53 @@ void testFibonacciSearchLargeArray() {
int expectedIndex = 9999;
assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the last element should be 9999.");
}
+
+ /**
+ * A key greater than every element used to throw {@link ArrayIndexOutOfBoundsException},
+ * because the final probe read {@code array[offset + 1]} without checking the bound.
+ */
+ @Test
+ void testFibonacciSearchKeyGreaterThanLastElement() {
+ FibonacciSearch fibonacciSearch = new FibonacciSearch();
+ for (int length = 1; length <= 50; length++) {
+ Integer[] array = new Integer[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = i;
+ }
+ assertEquals(-1, fibonacciSearch.find(array, length), "A key above the maximum should not be found for length " + length + ".");
+ }
+ }
+
+ /**
+ * The final probe used reference equality, so a key that is equal but not identical to the
+ * stored element was reported as missing. Values above 127 are outside the {@link Integer}
+ * cache and therefore are not the same object as the boxed array element.
+ */
+ @Test
+ void testFibonacciSearchFindsEqualButNotIdenticalKey() {
+ FibonacciSearch fibonacciSearch = new FibonacciSearch();
+ Integer[] array = {10, 20, 300};
+ assertEquals(2, fibonacciSearch.find(array, Integer.valueOf(300)), "The index of the found element should be 2.");
+
+ String[] words = {"a", "b", "c"};
+ String equalButDistinct = new StringBuilder("c").toString();
+ assertEquals(2, fibonacciSearch.find(words, equalButDistinct), "The index of the found element should be 2.");
+ }
+
+ /**
+ * Every element must be found regardless of the array length.
+ */
+ @Test
+ void testFibonacciSearchFindsEveryElement() {
+ FibonacciSearch fibonacciSearch = new FibonacciSearch();
+ for (int length = 1; length <= 50; length++) {
+ Integer[] array = new Integer[length];
+ for (int i = 0; i < length; i++) {
+ array[i] = 1000 + i * 2;
+ }
+ for (int i = 0; i < length; i++) {
+ assertEquals(i, fibonacciSearch.find(array, Integer.valueOf(1000 + i * 2)), "Element at index " + i + " should be found for length " + length + ".");
+ }
+ }
+ }
}
From a521d93ea6adfb1c4e2da7b5b6173a998617f42b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 9 Aug 2026 19:50:33 +0200
Subject: [PATCH 85/96] chore(deps): bump actions/setup-java from 5.6.0 to
5.7.0 in /.github/workflows (#7566)
chore(deps): bump actions/setup-java in /.github/workflows
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.6.0 to 5.7.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0)
---
updated-dependencies:
- dependency-name: actions/setup-java
dependency-version: 5.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>
---
.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 03ca693a5af0..a1395d87ec8d 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.6.0
+ uses: actions/setup-java@v5.7.0
with:
java-version: 21
distribution: 'temurin'
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d8e87d5363d5..6c51bf268b0c 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.6.0
+ uses: actions/setup-java@v5.7.0
with:
java-version: 21
distribution: 'temurin'
diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml
index cc15da8d0b00..3bfc509ebf47 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.6.0
+ uses: actions/setup-java@v5.7.0
with:
java-version: 21
distribution: 'temurin'
From 77be010940237389a33b14c8ed0debf6c2b7bceb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 9 Aug 2026 17:53:45 +0000
Subject: [PATCH 86/96] chore(deps): bump github/codeql-action from 4.37.3 to
4.37.4 in /.github/workflows (#7565)
chore(deps): bump github/codeql-action in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.3 to 4.37.4.
- [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.3...v4.37.4)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.37.4
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 6c51bf268b0c..18ac8b1215de 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.3
+ uses: github/codeql-action/init@v4.37.4
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.3
+ uses: github/codeql-action/analyze@v4.37.4
with:
category: "/language:java-kotlin"
@@ -55,12 +55,12 @@ jobs:
uses: actions/checkout@v7
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4.37.3
+ uses: github/codeql-action/init@v4.37.4
with:
languages: 'actions'
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4.37.3
+ uses: github/codeql-action/analyze@v4.37.4
with:
category: "/language:actions"
...
From 8e456f64a734011cc7c3d92edc66e6e082e1aa9f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 11 Aug 2026 08:21:46 +0300
Subject: [PATCH 87/96] chore(deps): bump github/codeql-action from 4.37.4 to
4.37.5 in /.github/workflows (#7569)
chore(deps): bump github/codeql-action in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.4 to 4.37.5.
- [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.4...v4.37.5)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.37.5
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 18ac8b1215de..995993df653f 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.4
+ uses: github/codeql-action/init@v4.37.5
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.4
+ uses: github/codeql-action/analyze@v4.37.5
with:
category: "/language:java-kotlin"
@@ -55,12 +55,12 @@ jobs:
uses: actions/checkout@v7
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4.37.4
+ uses: github/codeql-action/init@v4.37.5
with:
languages: 'actions'
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4.37.4
+ uses: github/codeql-action/analyze@v4.37.5
with:
category: "/language:actions"
...
From bc41b6465e78652b89173a77856cdc44e5d2c5a0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 11 Aug 2026 08:30:04 +0300
Subject: [PATCH 88/96] chore(deps): bump
org.apache.commons:commons-collections4 from 4.5.0 to 4.6.0 (#7568)
chore(deps): bump org.apache.commons:commons-collections4
Bumps org.apache.commons:commons-collections4 from 4.5.0 to 4.6.0.
---
updated-dependencies:
- dependency-name: org.apache.commons:commons-collections4
dependency-version: 4.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>
Co-authored-by: Oleksandr Klymenko
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 31ca9a59e025..dfc6e1056cc4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,7 +53,7 @@
org.apache.commonscommons-collections4
- 4.5.0
+ 4.6.0
From fdfb9a395b310167a66bd29e311e36e0e3e9b964 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 12 Aug 2026 09:09:38 +0300
Subject: [PATCH 89/96] chore(deps): bump github/codeql-action from 4.37.5 to
4.37.6 in /.github/workflows (#7570)
chore(deps): bump github/codeql-action in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.5 to 4.37.6.
- [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.5...v4.37.6)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.37.6
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 995993df653f..4861e5df2b29 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.5
+ uses: github/codeql-action/init@v4.37.6
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.5
+ uses: github/codeql-action/analyze@v4.37.6
with:
category: "/language:java-kotlin"
@@ -55,12 +55,12 @@ jobs:
uses: actions/checkout@v7
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4.37.5
+ uses: github/codeql-action/init@v4.37.6
with:
languages: 'actions'
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4.37.5
+ uses: github/codeql-action/analyze@v4.37.6
with:
category: "/language:actions"
...
From 346f591578705ff7493972c82ec327f7e217d238 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 15 Aug 2026 00:52:49 +0300
Subject: [PATCH 90/96] chore(deps): bump org.junit:junit-bom from 6.1.2 to
6.1.3 (#7571)
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.1.2 to 6.1.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.1.2...r6.1.3)
---
updated-dependencies:
- dependency-name: org.junit:junit-bom
dependency-version: 6.1.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>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index dfc6e1056cc4..68b72189fdb3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
org.junitjunit-bom
- 6.1.2
+ 6.1.3pomimport
From a0f6b0df7afc4555958a5f55a4e51501eb88bb86 Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Sat, 15 Aug 2026 22:26:25 +0530
Subject: [PATCH 91/96] fix: RailFenceCipher silently drops newline characters
(#7572)
Fix character loss in RailFenceCipher for inputs containing newlines
---
.../ciphers/RailFenceCipher.java | 56 ++++++++-------
.../ciphers/RailFenceCipherTest.java | 70 +++++++++++++++++++
2 files changed, 99 insertions(+), 27 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java
diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
index f81252980468..324dc88e4f19 100644
--- a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
@@ -1,7 +1,5 @@
package com.thealgorithms.ciphers;
-import java.util.Arrays;
-
/**
* The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher.
* It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails.
@@ -14,28 +12,27 @@ public class RailFenceCipher {
// Encrypts the input string using the rail fence cipher method with the given number of rails.
public String encrypt(String str, int rails) {
+ checkInput(str, rails);
+
// Base case of single rail or rails are more than the number of characters in the string
if (rails == 1 || rails >= str.length()) {
return str;
}
- // Boolean flag to determine if the movement is downward or upward in the rail matrix.
+ // Boolean flag to determine if the movement is downward or upward in the rail pattern.
boolean down = true;
- // Create a 2D array to represent the rails (rows) and the length of the string (columns).
- char[][] strRail = new char[rails][str.length()];
-
- // Initialize all positions in the rail matrix with a placeholder character ('\n').
+ // Collect the characters of every rail separately. Using one buffer per rail (instead of a
+ // rails x length matrix with a placeholder character) keeps every character of the input,
+ // including characters that would otherwise be indistinguishable from the placeholder.
+ StringBuilder[] railBuffers = new StringBuilder[rails];
for (int i = 0; i < rails; i++) {
- Arrays.fill(strRail[i], '\n');
+ railBuffers[i] = new StringBuilder();
}
- int row = 0; // Start at the first row
- int col = 0; // Start at the first column
+ int row = 0; // Start at the first rail
- int i = 0;
-
- // Fill the rail matrix with characters from the string based on the rail pattern.
- while (col < str.length()) {
+ // Distribute the characters of the string over the rails following the zigzag pattern.
+ for (int i = 0; i < str.length(); i++) {
// Change direction to down when at the first row.
if (row == 0) {
down = true;
@@ -45,33 +42,28 @@ else if (row == rails - 1) {
down = false;
}
- // Place the character in the current position of the rail matrix.
- strRail[row][col] = str.charAt(i);
- col++; // Move to the next column.
+ // Append the character to the rail it belongs to.
+ railBuffers[row].append(str.charAt(i));
// Move to the next row based on the direction.
if (down) {
row++;
} else {
row--;
}
-
- i++;
}
- // Construct the encrypted string by reading characters row by row.
- StringBuilder encryptedString = new StringBuilder();
- for (char[] chRow : strRail) {
- for (char ch : chRow) {
- if (ch != '\n') {
- encryptedString.append(ch);
- }
- }
+ // Construct the encrypted string by reading the rails top to bottom.
+ StringBuilder encryptedString = new StringBuilder(str.length());
+ for (StringBuilder railBuffer : railBuffers) {
+ encryptedString.append(railBuffer);
}
return encryptedString.toString();
}
// Decrypts the input string using the rail fence cipher method with the given number of rails.
public String decrypt(String str, int rails) {
+ checkInput(str, rails);
+
// Base case of single rail or rails are more than the number of characters in the string
if (rails == 1 || rails >= str.length()) {
return str;
@@ -144,4 +136,14 @@ else if (row == rails - 1) {
return decryptedString.toString();
}
+
+ // Rejects inputs the zigzag pattern is not defined for.
+ private static void checkInput(String str, int rails) {
+ if (str == null) {
+ throw new IllegalArgumentException("Input string must not be null");
+ }
+ if (rails <= 0) {
+ throw new IllegalArgumentException("Number of rails must be positive, but was " + rails);
+ }
+ }
}
diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java
new file mode 100644
index 000000000000..041f8c0dd4c1
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java
@@ -0,0 +1,70 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class RailFenceCipherTest {
+
+ private final RailFenceCipher railFenceCipher = new RailFenceCipher();
+
+ @Test
+ void testEncrypt() {
+ assertEquals("WECRLTEERDSOEEFEAOCAIVDEN", railFenceCipher.encrypt("WEAREDISCOVEREDFLEEATONCE", 3));
+ }
+
+ @Test
+ void testDecrypt() {
+ assertEquals("WEAREDISCOVEREDFLEEATONCE", railFenceCipher.decrypt("WECRLTEERDSOEEFEAOCAIVDEN", 3));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"HELLOWORLD, 2", "HELLOWORLD, 3", "HELLOWORLD, 4", "ATTACKATDAWN, 5", "abcdefghij, 6"})
+ void testRoundTrip(String message, int rails) {
+ assertEquals(message, railFenceCipher.decrypt(railFenceCipher.encrypt(message, rails), rails));
+ }
+
+ /**
+ * Every character of the input must survive encryption, including the ones that used to collide
+ * with the placeholder that marked unused cells of the rail matrix.
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {"ab\ncdef", "line1\nline2\nline3", "\n\n\n\n\n", "a\nb", "tabs\tand\nnewlines\r\n"})
+ void testControlCharactersArePreserved(String message) {
+ for (int rails = 2; rails <= 5; rails++) {
+ String encrypted = railFenceCipher.encrypt(message, rails);
+ assertEquals(message.length(), encrypted.length(), "characters were dropped with " + rails + " rails");
+ assertEquals(message, railFenceCipher.decrypt(encrypted, rails), "round trip failed with " + rails + " rails");
+ }
+ }
+
+ @Test
+ void testEncryptWithNewlineMatchesReferencePattern() {
+ // Rails of "ab\ncdef" with 3 rails: {a, d} / {b, c, e} / {\n, f}
+ assertEquals("adbce\nf", railFenceCipher.encrypt("ab\ncdef", 3));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"HELLO, 1", "HELLO, 5", "HELLO, 9", "'', 1", "'', 4"})
+ void testDegenerateRailCountsReturnInput(String message, int rails) {
+ assertEquals(message, railFenceCipher.encrypt(message, rails));
+ assertEquals(message, railFenceCipher.decrypt(message, rails));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, -1, -7})
+ void testNonPositiveRailCountThrows(int rails) {
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt("HELLO", rails));
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt("HELLO", rails));
+ }
+
+ @Test
+ void testNullInputThrows() {
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt(null, 3));
+ assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt(null, 3));
+ }
+}
From a050916b9f833630b05b15db07685bee92635d5f Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Mon, 17 Aug 2026 01:39:19 +0530
Subject: [PATCH 92/96] fix: off-by-one bounds guards in SegmentTree (#7573)
Fix off-by-one bounds guards in SegmentTree update and getSum
---
.../datastructures/trees/SegmentTree.java | 13 ++-
.../datastructures/trees/SegmentTreeTest.java | 96 +++++++++++++++++++
2 files changed, 106 insertions(+), 3 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
index 57b3edc163ca..af6acb0cbb2b 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
@@ -8,13 +8,18 @@ public class SegmentTree {
/* Constructor which takes the size of the array and the array as a parameter*/
public SegmentTree(int n, int[] arr) {
+ if (arr == null) {
+ throw new IllegalArgumentException("Input array must not be null");
+ }
+ if (n <= 0 || n > arr.length) {
+ throw new IllegalArgumentException("Size must be in the range [1, " + arr.length + "], but was " + n);
+ }
this.n = n;
int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
int segSize = 2 * (int) Math.pow(2, x) - 1;
this.segTree = new int[segSize];
this.arr = arr;
- this.n = n;
constructTree(arr, 0, n - 1, 0);
}
@@ -47,7 +52,8 @@ private void updateTree(int start, int end, int index, int diff, int segIndex) {
/* A function to update the value at a particular index*/
public void update(int index, int value) {
- if (index < 0 || index > n) {
+ // Valid positions are 0..n-1; index == n is out of bounds and must not reach arr[index].
+ if (index < 0 || index >= n) {
return;
}
@@ -73,7 +79,8 @@ private int getSumTree(int start, int end, int qStart, int qEnd, int segIndex) {
/* A function to query the sum of the subarray [start...end]*/
public int getSum(int start, int end) {
- if (start < 0 || end > n || start > end) {
+ // The last queryable position is n-1, so end == n is an out of range query.
+ if (start < 0 || end >= n || start > end) {
return 0;
}
return getSumTree(0, n - 1, start, end, 0);
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java
new file mode 100644
index 000000000000..196c32575709
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTreeTest.java
@@ -0,0 +1,96 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class SegmentTreeTest {
+
+ private static SegmentTree treeOf(int... values) {
+ return new SegmentTree(values.length, values);
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0, 4, 15", "0, 0, 1", "4, 4, 5", "1, 3, 9", "2, 4, 12"})
+ void testRangeSums(int start, int end, int expected) {
+ assertEquals(expected, treeOf(1, 2, 3, 4, 5).getSum(start, end));
+ }
+
+ @Test
+ void testSingleElementTree() {
+ SegmentTree tree = treeOf(42);
+ assertEquals(42, tree.getSum(0, 0));
+ tree.update(0, 7);
+ assertEquals(7, tree.getSum(0, 0));
+ }
+
+ @Test
+ void testUpdateIsReflectedInSubsequentQueries() {
+ SegmentTree tree = treeOf(1, 2, 3, 4, 5);
+ tree.update(2, 10);
+ assertEquals(22, tree.getSum(0, 4));
+ assertEquals(16, tree.getSum(1, 3));
+ tree.update(0, -1);
+ assertEquals(20, tree.getSum(0, 4));
+ }
+
+ @Test
+ void testNegativeValues() {
+ SegmentTree tree = treeOf(-5, 3, -2, 8);
+ assertEquals(4, tree.getSum(0, 3));
+ assertEquals(-4, tree.getSum(0, 2));
+ }
+
+ /**
+ * index == n is past the last element, so it must be rejected by the guard instead of reaching
+ * the backing array and throwing {@link ArrayIndexOutOfBoundsException}.
+ */
+ @ParameterizedTest
+ @ValueSource(ints = {5, 6, 100, -1})
+ void testUpdateOutOfRangeIndexIsIgnored(int index) {
+ SegmentTree tree = treeOf(1, 2, 3, 4, 5);
+ assertDoesNotThrow(() -> tree.update(index, 99));
+ assertEquals(15, tree.getSum(0, 4), "out of range update must not modify the tree");
+ }
+
+ @ParameterizedTest
+ @CsvSource({"0, 5", "0, 6", "3, 2", "-1, 3", "5, 5"})
+ void testOutOfRangeQueriesReturnZero(int start, int end) {
+ assertEquals(0, treeOf(1, 2, 3, 4, 5).getSum(start, end));
+ }
+
+ @Test
+ void testConstructorRejectsInvalidSize() {
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(0, new int[] {1, 2, 3}));
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(-1, new int[] {1, 2, 3}));
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(4, new int[] {1, 2, 3}));
+ }
+
+ @Test
+ void testConstructorRejectsNullArray() {
+ assertThrows(IllegalArgumentException.class, () -> new SegmentTree(3, null));
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17})
+ void testMatchesBruteForceForVariousSizes(int size) {
+ int[] values = new int[size];
+ for (int i = 0; i < size; i++) {
+ values[i] = i * 3 - 4;
+ }
+ SegmentTree tree = new SegmentTree(size, values.clone());
+
+ for (int start = 0; start < size; start++) {
+ int expected = 0;
+ for (int end = start; end < size; end++) {
+ expected += values[end];
+ assertEquals(expected, tree.getSum(start, end), "sum of [" + start + ", " + end + "] with size " + size);
+ }
+ }
+ }
+}
From 56e2699defe911cf72cd421a1dffc7eb5adae816 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 17 Aug 2026 20:39:45 +0000
Subject: [PATCH 93/96] chore(deps): bump com.puppycrawl.tools:checkstyle from
13.9.0 to 13.10.0 (#7576)
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.9.0 to 13.10.0.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.9.0...checkstyle-13.10.0)
---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
dependency-version: 13.10.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 68b72189fdb3..df36cfb6cdc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,7 +112,7 @@
com.puppycrawl.toolscheckstyle
- 13.9.0
+ 13.10.0
From 3ddd05229e736cb5c0c42dae25bb45b9df4ad194 Mon Sep 17 00:00:00 2001
From: Sepuri Sai Krishna
Date: Wed, 19 Aug 2026 14:16:57 +0530
Subject: [PATCH 94/96] fix: integer overflow in MobiusFunction (#7574)
Fix integer overflow in MobiusFunction squared-factor check
---
.../maths/Prime/MobiusFunction.java | 27 ++++++++++---------
.../maths/prime/MobiusFunctionTest.java | 13 +++++++++
2 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
index 3d4e4eff0f03..ec1785a916c7 100644
--- a/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
@@ -31,27 +31,28 @@ public static int mobius(int number) {
throw new IllegalArgumentException("Number must be greater than zero.");
}
- if (number == 1) {
- // return 1 if number passed is less or is 1
- return 1;
- }
-
int primeFactorCount = 0;
+ int remaining = number;
- for (int i = 1; i <= number; i++) {
- // find prime factors of number
- if (number % i == 0 && PrimeCheck.isPrime(i)) {
- // check if number is divisible by square of prime factor
- if (number % (i * i) == 0) {
- // if number is divisible by square of prime factor
+ /* Divide out every prime factor in turn. Trial division only has to run up to the square
+ root of the remaining value, and the multiplication is widened to long so that the bound
+ does not overflow for numbers close to Integer.MAX_VALUE. */
+ for (int factor = 2; (long) factor * factor <= remaining; factor++) {
+ if (remaining % factor == 0) {
+ remaining /= factor;
+ if (remaining % factor == 0) {
+ // number is divisible by the square of this prime factor
return 0;
}
- /*increment primeFactorCount by 1
- if number is not divisible by square of found prime factor*/
primeFactorCount++;
}
}
+ /* Whatever is left is either 1 or a single prime factor larger than the square root. */
+ if (remaining > 1) {
+ primeFactorCount++;
+ }
+
return (primeFactorCount % 2 == 0) ? 1 : -1;
}
}
diff --git a/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java b/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java
index 734d02477ba2..98ac29e81a5b 100644
--- a/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java
+++ b/src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java
@@ -5,6 +5,8 @@
import com.thealgorithms.maths.Prime.MobiusFunction;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
class MobiusFunctionTest {
@@ -152,4 +154,15 @@ void testMobiusFunction() {
assertEquals(expectedValue, actualValue);
}
}
+
+ /**
+ * Large inputs whose smallest square divisor test used to overflow, most notably
+ * {@code Integer.MAX_VALUE}, whose square wraps around to 1 and made every number look like it
+ * had a squared prime factor.
+ */
+ @ParameterizedTest
+ @CsvSource({"2147483647, -1", "2147483646, 0", "2147483645, -1", "2147483644, 0", "2147483629, -1", "2147395600, 0", "1073741824, 0", "1073741789, -1", "999999937, -1", "999999999, 0", "2146689000, 0"})
+ void testMobiusForLargeNumbers(int number, int expected) {
+ assertEquals(expected, MobiusFunction.mobius(number));
+ }
}
From 65b977e0fe7244314326a62f17e2a8c0f596ba43 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 21 Aug 2026 08:00:29 +0300
Subject: [PATCH 95/96] chore(deps): bump github/codeql-action from 4.37.6 to
4.37.7 in /.github/workflows (#7578)
chore(deps): bump github/codeql-action in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7.
- [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.6...v4.37.7)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.37.7
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 4861e5df2b29..db2c7f5a1b46 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.6
+ uses: github/codeql-action/init@v4.37.7
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.6
+ uses: github/codeql-action/analyze@v4.37.7
with:
category: "/language:java-kotlin"
@@ -55,12 +55,12 @@ jobs:
uses: actions/checkout@v7
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4.37.6
+ uses: github/codeql-action/init@v4.37.7
with:
languages: 'actions'
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4.37.6
+ uses: github/codeql-action/analyze@v4.37.7
with:
category: "/language:actions"
...
From 9d05db2d14702454a418ff31158905e502df83e0 Mon Sep 17 00:00:00 2001
From: iamcodinghere22
Date: Fri, 21 Aug 2026 14:23:26 +0530
Subject: [PATCH 96/96] feat(datastructures): add SelfOrganizingLinkedList
implementation and tests (#7575)
* Add SquareFreeInteger to maths
* fix clang-format issues
* add newline
* Add new test file in test
* modified
* delete
* Add DisariumNumbers with test
* feat(datastructures): add SelfOrganizingLinkedList implementation and tests
* fix checkstyle formatting errors in SelfOrganizingLinkedList including test file.
* Add new Line as per the format
* fix(datastructures): prevent null dereference in SelfOrganizingLinkedList
* format correction
* format
* build format
* "
* format finally
* maybe
* done
* make corrections and add tests
* format
* build correction
* done
* pmd done
---------
Co-authored-by: Deniz Altunkapan
---
.../lists/SelfOrganizingLinkedList.java | 105 ++++++++++++++++
.../lists/SelfOrganizingLinkedListTest.java | 119 ++++++++++++++++++
2 files changed, 224 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
create mode 100644 src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
new file mode 100644
index 000000000000..200c636ce1ab
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.datastructures.lists;
+
+import java.util.Objects;
+
+/**
+ * A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy.
+ * When an element is searched, it is automatically moved to the head of the list
+ * to optimize subsequent lookups.
+ *
+ * @param the type of elements held in this list
+ */
+public class SelfOrganizingLinkedList {
+
+ /**
+ * Node structure for the self-organizing linked list.
+ *
+ * @param the type of element held in this node
+ */
+ private static class Node {
+ E value;
+ Node next;
+
+ Node(E value) {
+ this.value = value;
+ this.next = null;
+ }
+ }
+
+ private Node head;
+ private int size;
+
+ public SelfOrganizingLinkedList() {
+ this.size = 0;
+ this.head = null;
+ }
+
+ /**
+ * Inserts a new value at the end of the list.
+ *
+ * @param value the element to add
+ */
+ public void insert(E value) {
+ Node newNode = new Node<>(value);
+ if (head == null) {
+ head = newNode;
+ } else {
+ Node temp = head;
+ while (temp.next != null) {
+ temp = temp.next;
+ }
+ temp.next = newNode;
+ }
+ size++;
+ }
+
+ /**
+ * Searches for a value in the list.
+ * If found, moves the node to the front (head) of the list.
+ *
+ * @param key the value to search for
+ * @return true if the element is present, false otherwise
+ */
+ public boolean search(E key) {
+ if (head == null) {
+ return false;
+ }
+ // If the key is already at the head, no pointers need to be rewired
+ if (Objects.equals(head.value, key)) {
+ return true;
+ }
+
+ Node prev = head;
+ Node curr = head.next;
+
+ while (curr != null && !Objects.equals(curr.value, key)) {
+ prev = curr;
+ curr = curr.next;
+ }
+
+ if (curr == null) {
+ return false;
+ }
+
+ // Unlink curr from its current position and move it to head
+ prev.next = curr.next;
+ curr.next = head;
+ head = curr;
+ return true;
+ }
+
+ /** Gets the current head value of the list. */
+ public E getHeadValue() {
+ return head != null ? head.value : null;
+ }
+
+ /** Returns the size of the list. */
+ public int getSize() {
+ return size;
+ }
+
+ /** Returns true if the list contains no elements. */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java
new file mode 100644
index 000000000000..1d397cf09301
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedListTest.java
@@ -0,0 +1,119 @@
+package com.thealgorithms.datastructures.lists;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class SelfOrganizingLinkedListTest {
+
+ private SelfOrganizingLinkedList list;
+
+ @BeforeEach
+ void setUp() {
+ list = new SelfOrganizingLinkedList<>();
+ }
+
+ @Test
+ void testEmptyListAndGetters() {
+ assertTrue(list.isEmpty());
+ assertEquals(0, list.getSize());
+ assertNull(list.getHeadValue());
+ assertFalse(list.search(10));
+ }
+
+ @Test
+ void testInsertAndSizeState() {
+ assertTrue(list.isEmpty());
+ list.insert(10);
+ assertFalse(list.isEmpty());
+ assertEquals(1, list.getSize());
+
+ list.insert(20);
+ assertEquals(2, list.getSize());
+ }
+
+ @Test
+ void testMoveMiddleElementToFrontPreservesFullListStructure() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+ list.insert(40);
+
+ // Initial order: [10, 20, 30, 40]
+ assertTrue(list.search(30));
+
+ // Expected order: [30, 10, 20, 40]
+ assertEquals(4, list.getSize());
+ assertEquals(30, list.getHeadValue());
+
+ // Sequential head tracking to verify middle and tail pointers didn't break
+ assertTrue(list.search(10)); // [10, 30, 20, 40]
+ assertEquals(10, list.getHeadValue());
+
+ assertTrue(list.search(20)); // [20, 10, 30, 40]
+ assertEquals(20, list.getHeadValue());
+
+ assertTrue(list.search(40)); // [40, 20, 10, 30]
+ assertEquals(40, list.getHeadValue());
+ assertEquals(4, list.getSize());
+ }
+
+ @Test
+ void testMoveLastElementToFrontPreservesFullListStructure() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+
+ // Search tail element '30'
+ assertTrue(list.search(30)); // Order becomes [30, 10, 20]
+
+ assertEquals(30, list.getHeadValue());
+ assertEquals(3, list.getSize());
+
+ // Verify remaining chain order [10, 20]
+ assertTrue(list.search(20)); // [20, 30, 10]
+ assertEquals(20, list.getHeadValue());
+
+ assertTrue(list.search(10)); // [10, 20, 30]
+ assertEquals(10, list.getHeadValue());
+ assertEquals(3, list.getSize());
+ }
+
+ @Test
+ void testSearchNonExistentElementPreservesStructureAndSize() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(30);
+
+ assertFalse(list.search(99));
+ assertEquals(3, list.getSize());
+ assertEquals(10, list.getHeadValue());
+ }
+
+ @Test
+ void testDuplicateValuesMovesFirstMatchedToFront() {
+ list.insert(10);
+ list.insert(20);
+ list.insert(10); // Duplicate '10' at tail
+ list.insert(30);
+
+ // Initial list state: [10, 20, 10, 30]
+ // Searching '10' hits the head immediately -> no re-linking
+ assertTrue(list.search(10));
+ assertEquals(10, list.getHeadValue());
+ assertEquals(4, list.getSize());
+
+ // Searching '20' moves middle element to head: [20, 10, 10, 30]
+ assertTrue(list.search(20));
+ assertEquals(20, list.getHeadValue());
+
+ // Searching '10' moves the FIRST instance of '10' (index 1) to head: [10, 20, 10, 30]
+ assertTrue(list.search(10));
+ assertEquals(10, list.getHeadValue());
+ assertEquals(4, list.getSize());
+ }
+}