From f3fd9ca3851c974afb7bfafc197a45d7e3678594 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:32:08 +0100 Subject: [PATCH 01/68] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.0.0 to 13.1.0 (#7251) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.0.0 to 13.1.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.0.0...checkstyle-13.1.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.1.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 170e3900b77f..65a8fc229647 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.0.0 + 13.1.0 From dfaa4956392b06bb2e55578e883213a9ea946b8f Mon Sep 17 00:00:00 2001 From: Divyansh Saxena <119129875+divyanshsaxena002@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:21:13 +0530 Subject: [PATCH 02/68] Refactor KMP and RabinKarp: Improve Reusability and Test Coverage (#7250) * first commit * Running KMPTest and RabinKarpTest with fixed formatting * now build failed error resolved * now build failed error resolved 2 --------- Co-authored-by: Divyansh Saxena Co-authored-by: Deniz Altunkapan --- .../java/com/thealgorithms/strings/KMP.java | 27 +++++--- .../com/thealgorithms/strings/RabinKarp.java | 62 ++++++++----------- .../com/thealgorithms/strings/KMPTest.java | 29 +++++++++ .../thealgorithms/strings/RabinKarpTest.java | 46 ++++++++++++++ 4 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 src/test/java/com/thealgorithms/strings/KMPTest.java create mode 100644 src/test/java/com/thealgorithms/strings/RabinKarpTest.java diff --git a/src/main/java/com/thealgorithms/strings/KMP.java b/src/main/java/com/thealgorithms/strings/KMP.java index 07d3b0415006..0317abe6f39a 100644 --- a/src/main/java/com/thealgorithms/strings/KMP.java +++ b/src/main/java/com/thealgorithms/strings/KMP.java @@ -1,5 +1,8 @@ package com.thealgorithms.strings; +import java.util.ArrayList; +import java.util.List; + /** * Implementation of Knuth–Morris–Pratt algorithm Usage: see the main function * for an example @@ -8,16 +11,19 @@ public final class KMP { private KMP() { } - // a working example - - public static void main(String[] args) { - final String haystack = "AAAAABAAABA"; // This is the full string - final String needle = "AAAA"; // This is the substring that we want to find - kmpMatcher(haystack, needle); - } + /** + * find the starting index in string haystack[] that matches the search word P[] + * + * @param haystack The text to be searched + * @param needle The pattern to be searched for + * @return A list of starting indices where the pattern is found + */ + public static List kmpMatcher(final String haystack, final String needle) { + List occurrences = new ArrayList<>(); + if (haystack == null || needle == null || needle.isEmpty()) { + return occurrences; + } - // find the starting index in string haystack[] that matches the search word P[] - public static void kmpMatcher(final String haystack, final String needle) { final int m = haystack.length(); final int n = needle.length(); final int[] pi = computePrefixFunction(needle); @@ -32,10 +38,11 @@ public static void kmpMatcher(final String haystack, final String needle) { } if (q == n) { - System.out.println("Pattern starts: " + (i + 1 - n)); + occurrences.add(i + 1 - n); q = pi[q - 1]; } } + return occurrences; } // return the prefix function diff --git a/src/main/java/com/thealgorithms/strings/RabinKarp.java b/src/main/java/com/thealgorithms/strings/RabinKarp.java index bb8df3358453..be17f87c3656 100644 --- a/src/main/java/com/thealgorithms/strings/RabinKarp.java +++ b/src/main/java/com/thealgorithms/strings/RabinKarp.java @@ -1,32 +1,30 @@ package com.thealgorithms.strings; -import java.util.Scanner; +import java.util.ArrayList; +import java.util.List; /** * @author Prateek Kumar Oraon (https://github.com/prateekKrOraon) * - An implementation of Rabin-Karp string matching algorithm - Program will simply end if there is no match + * An implementation of Rabin-Karp string matching algorithm + * Program will simply end if there is no match */ public final class RabinKarp { private RabinKarp() { } - public static Scanner scanner = null; - public static final int ALPHABET_SIZE = 256; + private static final int ALPHABET_SIZE = 256; - public static void main(String[] args) { - scanner = new Scanner(System.in); - System.out.println("Enter String"); - String text = scanner.nextLine(); - System.out.println("Enter pattern"); - String pattern = scanner.nextLine(); - - int q = 101; - searchPat(text, pattern, q); + public static List search(String text, String pattern) { + return search(text, pattern, 101); } - private static void searchPat(String text, String pattern, int q) { + public static List search(String text, String pattern, int q) { + List occurrences = new ArrayList<>(); + if (text == null || pattern == null || pattern.isEmpty()) { + return occurrences; + } + int m = pattern.length(); int n = text.length(); int t = 0; @@ -35,48 +33,42 @@ private static void searchPat(String text, String pattern, int q) { int j = 0; int i = 0; - h = (int) Math.pow(ALPHABET_SIZE, m - 1) % q; + if (m > n) { + return new ArrayList<>(); + } + + // h = pow(ALPHABET_SIZE, m-1) % q + for (i = 0; i < m - 1; i++) { + h = h * ALPHABET_SIZE % q; + } for (i = 0; i < m; i++) { - // hash value is calculated for each character and then added with the hash value of the - // next character for pattern as well as the text for length equal to the length of - // pattern p = (ALPHABET_SIZE * p + pattern.charAt(i)) % q; t = (ALPHABET_SIZE * t + text.charAt(i)) % q; } for (i = 0; i <= n - m; i++) { - // if the calculated hash value of the pattern and text matches then - // all the characters of the pattern is matched with the text of length equal to length - // of the pattern if all matches then pattern exist in string if not then the hash value - // of the first character of the text is subtracted and hash value of the next character - // after the end of the evaluated characters is added if (p == t) { - // if hash value matches then the individual characters are matched for (j = 0; j < m; j++) { - // if not matched then break out of the loop if (text.charAt(i + j) != pattern.charAt(j)) { break; } } - // if all characters are matched then pattern exist in the string if (j == m) { - System.out.println("Pattern found at index " + i); + occurrences.add(i); } } - // if i Date: Sun, 1 Feb 2026 20:56:57 +0530 Subject: [PATCH 03/68] add Subarray Sum Equals K using prefix sum (#7252) Co-authored-by: Deniz Altunkapan --- .../prefixsum/SubarraySumEqualsK.java | 72 +++++++++++++++++++ .../prefixsum/SubarraySumEqualskTest.java | 59 +++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java create mode 100644 src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java diff --git a/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java new file mode 100644 index 000000000000..d6a6bbc01663 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java @@ -0,0 +1,72 @@ +package com.thealgorithms.prefixsum; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implements an algorithm to count the number of continuous subarrays + * whose sum equals a given value k. + * + *

+ * This algorithm uses the Prefix Sum technique combined with a HashMap + * to achieve O(N) time complexity. + *

+ * + *

+ * Let prefixSum[i] be the sum of elements from index 0 to i. + * A subarray (j + 1) to i has sum k if: + * + *

+ * prefixSum[i] - prefixSum[j] = k
+ * 
+ *

+ * + *

+ * The HashMap stores the frequency of each prefix sum encountered so far. + *

+ * + *

+ * Time Complexity: O(N)
+ * Space Complexity: O(N) + *

+ * + * @see Prefix Sum (Wikipedia) + * @author Ruturaj Jadhav, ruturajjadhav07 + */ +public final class SubarraySumEqualsK { + + private SubarraySumEqualsK() { + // Utility class; prevent instantiation + } + + /** + * Counts the number of subarrays whose sum equals k. + * + * @param nums The input integer array. + * @param k The target sum. + * @return The number of continuous subarrays summing to k. + * @throws IllegalArgumentException if nums is null. + */ + public static int countSubarrays(int[] nums, int k) { + if (nums == null) { + throw new IllegalArgumentException("Input array cannot be null"); + } + + Map prefixSumFrequency = new HashMap<>(); + prefixSumFrequency.put(0L, 1); + + long prefixSum = 0; + int count = 0; + + for (int num : nums) { + prefixSum += num; + + long requiredSum = prefixSum - k; + count += prefixSumFrequency.getOrDefault(requiredSum, 0); + + prefixSumFrequency.put(prefixSum, prefixSumFrequency.getOrDefault(prefixSum, 0) + 1); + } + + return count; + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java new file mode 100644 index 000000000000..68f85b713046 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java @@ -0,0 +1,59 @@ +package com.thealgorithms.prefixsum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link SubarraySumEqualsK}. + */ +class SubarraySumEqualsKTest { + + @Test + void testBasicExample() { + int[] nums = {1, 1, 1}; + int k = 2; + assertEquals(2, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testWithNegativeNumbers() { + int[] nums = {1, -1, 0}; + int k = 0; + assertEquals(3, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testSingleElementEqualToK() { + int[] nums = {5}; + int k = 5; + assertEquals(1, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testSingleElementNotEqualToK() { + int[] nums = {5}; + int k = 3; + assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testAllZeros() { + int[] nums = {0, 0, 0}; + int k = 0; + assertEquals(6, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testEmptyArray() { + int[] nums = {}; + int k = 0; + assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testNullArrayThrowsException() { + assertThrows(IllegalArgumentException.class, () -> SubarraySumEqualsK.countSubarrays(null, 0)); + } +} From c6703d337edb74783406d5cafa4a363a2415d8eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:55:55 +0100 Subject: [PATCH 04/68] chore(deps-dev): bump org.apache.maven.plugins:maven-compiler-plugin from 3.14.1 to 3.15.0 (#7254) chore(deps-dev): bump org.apache.maven.plugins:maven-compiler-plugin Bumps [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.14.1 to 3.15.0. - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.14.1...maven-compiler-plugin-3.15.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-version: 3.15.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 65a8fc229647..f13169cece97 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 21 From 8e30bcbb02f14f118cc63f24b3c44b7ff7f66ba8 Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+GziXnine@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:32:37 +0200 Subject: [PATCH 05/68] refactor: clean up duplicate algorithm implementations to reduce maintenance overhead (#7256) refactor: remove duplicate algorithm implementations Removed the following duplicate implementations: - searches/PerfectBinarySearch.java (duplicate of IterativeBinarySearch) - searches/SortOrderAgnosticBinarySearch.java (duplicate of OrderAgnosticBinarySearch) - strings/LongestPalindromicSubstring.java (duplicate of dynamicprogramming version) - strings/ValidParentheses.java (duplicate of stacks version) - others/cn/HammingDistance.java (duplicate - strings version handles text) - others/NewManShanksPrimeTest.java (orphan test in wrong package) Updated DIRECTORY.md to reflect the changes. Fixes #7253 Co-authored-by: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> --- DIRECTORY.md | 13 --- .../others/cn/HammingDistance.java | 32 -------- .../searches/PerfectBinarySearch.java | 54 ------------ .../SortOrderAgnosticBinarySearch.java | 30 ------- .../strings/LongestPalindromicSubstring.java | 37 --------- .../strings/ValidParentheses.java | 53 ------------ .../others/NewManShanksPrimeTest.java | 49 ----------- .../others/cn/HammingDistanceTest.java | 82 ------------------- .../searches/PerfectBinarySearchTest.java | 44 ---------- .../SortOrderAgnosticBinarySearchTest.java | 26 ------ .../LongestPalindromicSubstringTest.java | 21 ----- .../strings/ValidParenthesesTest.java | 33 -------- 12 files changed, 474 deletions(-) delete mode 100644 src/main/java/com/thealgorithms/others/cn/HammingDistance.java delete mode 100644 src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java delete mode 100644 src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java delete mode 100644 src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java delete mode 100644 src/main/java/com/thealgorithms/strings/ValidParentheses.java delete mode 100644 src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java delete mode 100644 src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java delete mode 100644 src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java delete mode 100644 src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java delete mode 100644 src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java delete mode 100644 src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index deaf59636fa4..585c634c3429 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -626,8 +626,6 @@ - πŸ“„ [SkylineProblem](src/main/java/com/thealgorithms/others/SkylineProblem.java) - πŸ“„ [TwoPointers](src/main/java/com/thealgorithms/others/TwoPointers.java) - πŸ“„ [Verhoeff](src/main/java/com/thealgorithms/others/Verhoeff.java) - - πŸ“ **cn** - - πŸ“„ [HammingDistance](src/main/java/com/thealgorithms/others/cn/HammingDistance.java) - πŸ“ **physics** - πŸ“„ [CoulombsLaw](src/main/java/com/thealgorithms/physics/CoulombsLaw.java) - πŸ“„ [DampedOscillator](src/main/java/com/thealgorithms/physics/DampedOscillator.java) @@ -701,7 +699,6 @@ - πŸ“„ [LowerBound](src/main/java/com/thealgorithms/searches/LowerBound.java) - πŸ“„ [MonteCarloTreeSearch](src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java) - πŸ“„ [OrderAgnosticBinarySearch](src/main/java/com/thealgorithms/searches/OrderAgnosticBinarySearch.java) - - πŸ“„ [PerfectBinarySearch](src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java) - πŸ“„ [QuickSelect](src/main/java/com/thealgorithms/searches/QuickSelect.java) - πŸ“„ [RabinKarpAlgorithm](src/main/java/com/thealgorithms/searches/RabinKarpAlgorithm.java) - πŸ“„ [RandomSearch](src/main/java/com/thealgorithms/searches/RandomSearch.java) @@ -710,7 +707,6 @@ - πŸ“„ [SaddlebackSearch](src/main/java/com/thealgorithms/searches/SaddlebackSearch.java) - πŸ“„ [SearchInARowAndColWiseSortedMatrix](src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java) - πŸ“„ [SentinelLinearSearch](src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java) - - πŸ“„ [SortOrderAgnosticBinarySearch](src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java) - πŸ“„ [SquareRootBinarySearch](src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java) - πŸ“„ [TernarySearch](src/main/java/com/thealgorithms/searches/TernarySearch.java) - πŸ“„ [UnionFind](src/main/java/com/thealgorithms/searches/UnionFind.java) @@ -817,7 +813,6 @@ - πŸ“„ [LetterCombinationsOfPhoneNumber](src/main/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumber.java) - πŸ“„ [LongestCommonPrefix](src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java) - πŸ“„ [LongestNonRepetitiveSubstring](src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java) - - πŸ“„ [LongestPalindromicSubstring](src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java) - πŸ“„ [Lower](src/main/java/com/thealgorithms/strings/Lower.java) - πŸ“„ [Manacher](src/main/java/com/thealgorithms/strings/Manacher.java) - πŸ“„ [MyAtoi](src/main/java/com/thealgorithms/strings/MyAtoi.java) @@ -834,7 +829,6 @@ - πŸ“„ [StringMatchFiniteAutomata](src/main/java/com/thealgorithms/strings/StringMatchFiniteAutomata.java) - πŸ“„ [SuffixArray](src/main/java/com/thealgorithms/strings/SuffixArray.java) - πŸ“„ [Upper](src/main/java/com/thealgorithms/strings/Upper.java) - - πŸ“„ [ValidParentheses](src/main/java/com/thealgorithms/strings/ValidParentheses.java) - πŸ“„ [WordLadder](src/main/java/com/thealgorithms/strings/WordLadder.java) - πŸ“„ [ZAlgorithm](src/main/java/com/thealgorithms/strings/ZAlgorithm.java) - πŸ“ **zigZagPattern** @@ -1395,7 +1389,6 @@ - πŸ“„ [MaximumSumOfDistinctSubarraysWithLengthKTest](src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java) - πŸ“„ [MiniMaxAlgorithmTest](src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java) - πŸ“„ [MosAlgorithmTest](src/test/java/com/thealgorithms/others/MosAlgorithmTest.java) - - πŸ“„ [NewManShanksPrimeTest](src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java) - πŸ“„ [NextFitTest](src/test/java/com/thealgorithms/others/NextFitTest.java) - πŸ“„ [PageRankTest](src/test/java/com/thealgorithms/others/PageRankTest.java) - πŸ“„ [PasswordGenTest](src/test/java/com/thealgorithms/others/PasswordGenTest.java) @@ -1404,8 +1397,6 @@ - πŸ“„ [SkylineProblemTest](src/test/java/com/thealgorithms/others/SkylineProblemTest.java) - πŸ“„ [TwoPointersTest](src/test/java/com/thealgorithms/others/TwoPointersTest.java) - πŸ“„ [WorstFitCPUTest](src/test/java/com/thealgorithms/others/WorstFitCPUTest.java) - - πŸ“ **cn** - - πŸ“„ [HammingDistanceTest](src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java) - πŸ“ **physics** - πŸ“„ [CoulombsLawTest](src/test/java/com/thealgorithms/physics/CoulombsLawTest.java) - πŸ“„ [DampedOscillatorTest](src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java) @@ -1479,7 +1470,6 @@ - πŸ“„ [LowerBoundTest](src/test/java/com/thealgorithms/searches/LowerBoundTest.java) - πŸ“„ [MonteCarloTreeSearchTest](src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java) - πŸ“„ [OrderAgnosticBinarySearchTest](src/test/java/com/thealgorithms/searches/OrderAgnosticBinarySearchTest.java) - - πŸ“„ [PerfectBinarySearchTest](src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java) - πŸ“„ [QuickSelectTest](src/test/java/com/thealgorithms/searches/QuickSelectTest.java) - πŸ“„ [RabinKarpAlgorithmTest](src/test/java/com/thealgorithms/searches/RabinKarpAlgorithmTest.java) - πŸ“„ [RandomSearchTest](src/test/java/com/thealgorithms/searches/RandomSearchTest.java) @@ -1488,7 +1478,6 @@ - πŸ“„ [SaddlebackSearchTest](src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java) - πŸ“„ [SearchInARowAndColWiseSortedMatrixTest](src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java) - πŸ“„ [SentinelLinearSearchTest](src/test/java/com/thealgorithms/searches/SentinelLinearSearchTest.java) - - πŸ“„ [SortOrderAgnosticBinarySearchTest](src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java) - πŸ“„ [SquareRootBinarySearchTest](src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java) - πŸ“„ [TernarySearchTest](src/test/java/com/thealgorithms/searches/TernarySearchTest.java) - πŸ“„ [TestSearchInARowAndColWiseSortedMatrix](src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java) @@ -1593,7 +1582,6 @@ - πŸ“„ [LetterCombinationsOfPhoneNumberTest](src/test/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumberTest.java) - πŸ“„ [LongestCommonPrefixTest](src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java) - πŸ“„ [LongestNonRepetitiveSubstringTest](src/test/java/com/thealgorithms/strings/LongestNonRepetitiveSubstringTest.java) - - πŸ“„ [LongestPalindromicSubstringTest](src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java) - πŸ“„ [LowerTest](src/test/java/com/thealgorithms/strings/LowerTest.java) - πŸ“„ [ManacherTest](src/test/java/com/thealgorithms/strings/ManacherTest.java) - πŸ“„ [MyAtoiTest](src/test/java/com/thealgorithms/strings/MyAtoiTest.java) @@ -1609,7 +1597,6 @@ - πŸ“„ [StringMatchFiniteAutomataTest](src/test/java/com/thealgorithms/strings/StringMatchFiniteAutomataTest.java) - πŸ“„ [SuffixArrayTest](src/test/java/com/thealgorithms/strings/SuffixArrayTest.java) - πŸ“„ [UpperTest](src/test/java/com/thealgorithms/strings/UpperTest.java) - - πŸ“„ [ValidParenthesesTest](src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java) - πŸ“„ [WordLadderTest](src/test/java/com/thealgorithms/strings/WordLadderTest.java) - πŸ“„ [ZAlgorithmTest](src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java) - πŸ“ **zigZagPattern** diff --git a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java b/src/main/java/com/thealgorithms/others/cn/HammingDistance.java deleted file mode 100644 index c8239d53d606..000000000000 --- a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.thealgorithms.others.cn; - -public final class HammingDistance { - private HammingDistance() { - } - - private static void checkChar(char inChar) { - if (inChar != '0' && inChar != '1') { - throw new IllegalArgumentException("Input must be a binary string."); - } - } - - public static int compute(char charA, char charB) { - checkChar(charA); - checkChar(charB); - return charA == charB ? 0 : 1; - } - - public static int compute(String bitsStrA, String bitsStrB) { - if (bitsStrA.length() != bitsStrB.length()) { - throw new IllegalArgumentException("Input strings must have the same length."); - } - - int totalErrorBitCount = 0; - - for (int i = 0; i < bitsStrA.length(); i++) { - totalErrorBitCount += compute(bitsStrA.charAt(i), bitsStrB.charAt(i)); - } - - return totalErrorBitCount; - } -} diff --git a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java b/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java deleted file mode 100644 index 495e2e41bc5b..000000000000 --- a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.thealgorithms.searches; - -import com.thealgorithms.devutils.searches.SearchAlgorithm; - -/** - * Binary search is one of the most popular algorithms The algorithm finds the - * position of a target value within a sorted array - * - *

- * Worst-case performance O(log n) Best-case performance O(1) Average - * performance O(log n) Worst-case space complexity O(1) - * - * @author D Sunil (https://github.com/sunilnitdgp) - * @see SearchAlgorithm - */ - -public class PerfectBinarySearch implements SearchAlgorithm { - - /** - * @param array is an array where the element should be found - * @param key is an element which should be found - * @param is any comparable type - * @return index of the element - */ - @Override - public > int find(T[] array, T key) { - return search(array, key, 0, array.length - 1); - } - - /** - * This method implements the Generic Binary Search iteratively. - * - * @param array The array to make the binary search - * @param key The number you are looking for - * @return the location of the key, or -1 if not found - */ - private static > int search(T[] array, T key, int left, int right) { - while (left <= right) { - int median = (left + right) >>> 1; - int comp = key.compareTo(array[median]); - - if (comp == 0) { - return median; // Key found - } - - if (comp < 0) { - right = median - 1; // Adjust the right bound - } else { - left = median + 1; // Adjust the left bound - } - } - return -1; // Key not found - } -} diff --git a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java b/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java deleted file mode 100644 index 6a2a46c2821f..000000000000 --- a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.thealgorithms.searches; -public final class SortOrderAgnosticBinarySearch { - private SortOrderAgnosticBinarySearch() { - } - public static int find(int[] arr, int key) { - int start = 0; - int end = arr.length - 1; - boolean arrDescending = arr[start] > arr[end]; // checking for Array is in ascending order or descending order. - while (start <= end) { - int mid = end - start / 2; - if (arr[mid] == key) { - return mid; - } - if (arrDescending) { // boolean is true then our array is in descending order - if (key < arr[mid]) { - start = mid + 1; - } else { - end = mid - 1; - } - } else { // otherwise our array is in ascending order - if (key > arr[mid]) { - start = mid + 1; - } else { - end = mid - 1; - } - } - } - return -1; - } -} diff --git a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java b/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java deleted file mode 100644 index ca500357ba77..000000000000 --- a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.thealgorithms.strings; - -final class LongestPalindromicSubstring { - private LongestPalindromicSubstring() { - } - - /** - * Finds the longest palindromic substring in the given string. - * - * @param s the input string - * @return the longest palindromic substring - */ - public static String longestPalindrome(String s) { - if (s == null || s.isEmpty()) { - return ""; - } - String maxStr = ""; - for (int i = 0; i < s.length(); ++i) { - for (int j = i; j < s.length(); ++j) { - if (isValid(s, i, j) && (j - i + 1 > maxStr.length())) { - maxStr = s.substring(i, j + 1); - } - } - } - return maxStr; - } - - private static boolean isValid(String s, int lo, int hi) { - int n = hi - lo + 1; - for (int i = 0; i < n / 2; ++i) { - if (s.charAt(lo + i) != s.charAt(hi - i)) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java deleted file mode 100644 index 25a72f379dec..000000000000 --- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.thealgorithms.strings; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Map; - -/** - * Validates if a given string has valid matching parentheses. - *

- * A string is considered valid if: - *

    - *
  • Open brackets are closed by the same type of brackets.
  • - *
  • Brackets are closed in the correct order.
  • - *
  • Every closing bracket has a corresponding open bracket of the same type.
  • - *
- * - * Allowed characters: '(', ')', '{', '}', '[', ']' - */ -public final class ValidParentheses { - private ValidParentheses() { - } - - private static final Map BRACKET_PAIRS = Map.of(')', '(', '}', '{', ']', '['); - - /** - * Checks if the input string has valid parentheses. - * - * @param s the string containing only bracket characters - * @return true if valid, false otherwise - * @throws IllegalArgumentException if the string contains invalid characters or is null - */ - public static boolean isValid(String s) { - if (s == null) { - throw new IllegalArgumentException("Input string cannot be null"); - } - - Deque stack = new ArrayDeque<>(); - - for (char c : s.toCharArray()) { - if (BRACKET_PAIRS.containsValue(c)) { - stack.push(c); // opening bracket - } else if (BRACKET_PAIRS.containsKey(c)) { - if (stack.isEmpty() || stack.pop() != BRACKET_PAIRS.get(c)) { - return false; - } - } else { - throw new IllegalArgumentException("Unexpected character: " + c); - } - } - - return stack.isEmpty(); - } -} diff --git a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java b/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java deleted file mode 100644 index 3b657e441b1c..000000000000 --- a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.thealgorithms.dynamicprogramming.NewManShanksPrime; -import org.junit.jupiter.api.Test; - -public class NewManShanksPrimeTest { - - @Test - void testOne() { - assertTrue(NewManShanksPrime.nthManShanksPrime(1, 1)); - } - - @Test - void testTwo() { - assertTrue(NewManShanksPrime.nthManShanksPrime(2, 3)); - } - - @Test - void testThree() { - assertTrue(NewManShanksPrime.nthManShanksPrime(3, 7)); - } - - @Test - void testFour() { - assertTrue(NewManShanksPrime.nthManShanksPrime(4, 17)); - } - - @Test - void testFive() { - assertTrue(NewManShanksPrime.nthManShanksPrime(5, 41)); - } - - @Test - void testSix() { - assertTrue(NewManShanksPrime.nthManShanksPrime(6, 99)); - } - - @Test - void testSeven() { - assertTrue(NewManShanksPrime.nthManShanksPrime(7, 239)); - } - - @Test - void testEight() { - assertTrue(NewManShanksPrime.nthManShanksPrime(8, 577)); - } -} diff --git a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java b/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java deleted file mode 100644 index 669f928cd247..000000000000 --- a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.thealgorithms.others.cn; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -public class HammingDistanceTest { - @Test - public void checkForDifferentBits() { - int answer = HammingDistance.compute("000", "011"); - Assertions.assertThat(answer).isEqualTo(2); - } - - /* - - 1 0 1 0 1 - 1 1 1 1 0 - ---------- - 0 1 0 1 1 - - - */ - @Test - public void checkForDifferentBitsLength() { - int answer = HammingDistance.compute("10101", "11110"); - Assertions.assertThat(answer).isEqualTo(3); - } - - @Test - public void checkForSameBits() { - String someBits = "111"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForLongDataBits() { - int answer = HammingDistance.compute("10010101101010000100110100", "00110100001011001100110101"); - Assertions.assertThat(answer).isEqualTo(7); - } - - @Test - public void mismatchDataBits() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("100010", "00011"); }); - - Assertions.assertThat(ex.getMessage()).contains("must have the same length"); - } - - @Test - public void mismatchDataBits2() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1", "11"); }); - - Assertions.assertThat(ex.getMessage()).contains("must have the same length"); - } - - @Test - public void checkForLongDataBitsSame() { - String someBits = "10010101101010000100110100"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForEmptyInput() { - String someBits = ""; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForInputOfLength1() { - String someBits = "0"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void computeThrowsExceptionWhenInputsAreNotBitStrs() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1A", "11"); }); - - Assertions.assertThat(ex.getMessage()).contains("must be a binary string"); - } -} diff --git a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java deleted file mode 100644 index 6eab20f45467..000000000000 --- a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -/** - * @author D Sunil (https://github.com/sunilnitdgp) - * @see PerfectBinarySearch - */ -public class PerfectBinarySearchTest { - - @Test - public void testIntegerBinarySearch() { - Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - PerfectBinarySearch binarySearch = new PerfectBinarySearch<>(); - - // Test cases for elements present in the array - assertEquals(0, binarySearch.find(array, 1)); // First element - assertEquals(4, binarySearch.find(array, 5)); // Middle element - assertEquals(9, binarySearch.find(array, 10)); // Last element - assertEquals(6, binarySearch.find(array, 7)); // Element in the middle - - // Test cases for elements not in the array - assertEquals(-1, binarySearch.find(array, 0)); // Element before the array - assertEquals(-1, binarySearch.find(array, 11)); // Element after the array - assertEquals(-1, binarySearch.find(array, 100)); // Element not in the array - } - - @Test - public void testStringBinarySearch() { - String[] array = {"apple", "banana", "cherry", "date", "fig"}; - PerfectBinarySearch binarySearch = new PerfectBinarySearch<>(); - - // Test cases for elements not in the array - assertEquals(-1, binarySearch.find(array, "apricot")); // Element not in the array - assertEquals(-1, binarySearch.find(array, "bananaa")); // Element not in the array - - // Test cases for elements present in the array - assertEquals(0, binarySearch.find(array, "apple")); // First element - assertEquals(2, binarySearch.find(array, "cherry")); // Middle element - assertEquals(4, binarySearch.find(array, "fig")); // Last element - } -} diff --git a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java deleted file mode 100644 index e2917733d1d9..000000000000 --- a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -public class SortOrderAgnosticBinarySearchTest { - - @Test - public void testAscending() { - int[] arr = {1, 2, 3, 4, 5}; // for ascending order. - int target = 2; - int ans = SortOrderAgnosticBinarySearch.find(arr, target); - int excepted = 1; - assertEquals(excepted, ans); - } - - @Test - public void testDescending() { - int[] arr = {5, 4, 3, 2, 1}; // for descending order. - int target = 2; - int ans = SortOrderAgnosticBinarySearch.find(arr, target); - int excepted = 3; - assertEquals(excepted, ans); - } -} diff --git a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java deleted file mode 100644 index aa13c0f4a474..000000000000 --- a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.thealgorithms.strings; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class LongestPalindromicSubstringTest { - - @ParameterizedTest - @MethodSource("provideTestCasesForLongestPalindrome") - void testLongestPalindrome(String input, String expected) { - assertEquals(expected, LongestPalindromicSubstring.longestPalindrome(input)); - } - - private static Stream provideTestCasesForLongestPalindrome() { - return Stream.of(Arguments.of("babad", "bab"), Arguments.of("cbbd", "bb"), Arguments.of("a", "a"), Arguments.of("", ""), Arguments.of("abc", "a"), Arguments.of(null, ""), Arguments.of("aaaaa", "aaaaa")); - } -} diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java deleted file mode 100644 index 411b11e743b8..000000000000 --- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.thealgorithms.strings; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -public class ValidParenthesesTest { - - @ParameterizedTest(name = "Input: \"{0}\" β†’ Expected: {1}") - @CsvSource({"'()', true", "'()[]{}', true", "'(]', false", "'{[]}', true", "'([{}])', true", "'([)]', false", "'', true", "'(', false", "')', false", "'{{{{}}}}', true", "'[({})]', true", "'[(])', false", "'[', false", "']', false", "'()()()()', true", "'(()', false", "'())', false", - "'{[()()]()}', true"}) - void - testIsValid(String input, boolean expected) { - assertEquals(expected, ValidParentheses.isValid(input)); - } - - @Test - void testNullInputThrows() { - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(null)); - assertEquals("Input string cannot be null", ex.getMessage()); - } - - @ParameterizedTest(name = "Input: \"{0}\" β†’ throws IllegalArgumentException") - @CsvSource({"'a'", "'()a'", "'[123]'", "'{hello}'", "'( )'", "'\t'", "'\n'", "'@#$%'"}) - void testInvalidCharactersThrow(String input) { - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(input)); - assertTrue(ex.getMessage().startsWith("Unexpected character")); - } -} From a14b2345f0a40a043ce78ebaf1ff056bbb362891 Mon Sep 17 00:00:00 2001 From: Chahat Sandhu Date: Wed, 4 Feb 2026 09:49:15 -0600 Subject: [PATCH 06/68] feat: add ElGamalCipher with Safe Prime generation and stateless design (#7257) --- .../thealgorithms/ciphers/ElGamalCipher.java | 174 ++++++++++++++++++ .../ciphers/ElGamalCipherTest.java | 145 +++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java create mode 100644 src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java diff --git a/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java b/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java new file mode 100644 index 000000000000..6383caa59b1f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java @@ -0,0 +1,174 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * ElGamal Encryption Algorithm Implementation. + * + *

+ * ElGamal is an asymmetric key encryption algorithm for public-key cryptography + * based on the Diffie–Hellman key exchange. It relies on the difficulty + * of computing discrete logarithms in a cyclic group. + *

+ * + *

+ * Key Features: + *

    + *
  • Uses Safe Primes (p = 2q + 1) to ensure group security.
  • + *
  • Verifies the generator is a primitive root modulo p.
  • + *
  • Stateless design using Java Records.
  • + *
  • SecureRandom for all cryptographic operations.
  • + *
+ *

+ * + * @author Chahat Sandhu, singhc7 + * @see ElGamal Encryption (Wikipedia) + * @see Safe Primes + */ +public final class ElGamalCipher { + + private static final SecureRandom RANDOM = new SecureRandom(); + private static final int PRIME_CERTAINTY = 40; + private static final int MIN_BIT_LENGTH = 256; + + private ElGamalCipher() { + } + + /** + * A container for the Public and Private keys. + * + * @param p The prime modulus. + * @param g The generator (primitive root). + * @param y The public key component (g^x mod p). + * @param x The private key. + */ + public record KeyPair(BigInteger p, BigInteger g, BigInteger y, BigInteger x) { + } + + /** + * Container for the encryption result. + * + * @param a The first component (g^k mod p). + * @param b The second component (y^k * m mod p). + */ + public record CipherText(BigInteger a, BigInteger b) { + } + + /** + * Generates a valid ElGamal KeyPair using a Safe Prime. + * + * @param bitLength The bit length of the prime modulus p. Must be at least 256. + * @return A valid KeyPair (p, g, y, x). + * @throws IllegalArgumentException if bitLength is too small. + */ + public static KeyPair generateKeys(int bitLength) { + if (bitLength < MIN_BIT_LENGTH) { + throw new IllegalArgumentException("Bit length must be at least " + MIN_BIT_LENGTH + " for security."); + } + + BigInteger p; + BigInteger q; + BigInteger g; + BigInteger x; + BigInteger y; + + // Generate Safe Prime p = 2q + 1 + do { + q = new BigInteger(bitLength - 1, PRIME_CERTAINTY, RANDOM); + p = q.multiply(BigInteger.TWO).add(BigInteger.ONE); + } while (!p.isProbablePrime(PRIME_CERTAINTY)); + + // Find a Generator g (Primitive Root modulo p) + do { + g = new BigInteger(bitLength, RANDOM).mod(p.subtract(BigInteger.TWO)).add(BigInteger.TWO); + } while (!isValidGenerator(g, p, q)); + + // Generate Private Key x in range [2, p-2] + do { + x = new BigInteger(bitLength, RANDOM); + } while (x.compareTo(BigInteger.TWO) < 0 || x.compareTo(p.subtract(BigInteger.TWO)) > 0); + + // Compute Public Key y = g^x mod p + y = g.modPow(x, p); + + return new KeyPair(p, g, y, x); + } + + /** + * Encrypts a message using the public key. + * + * @param message The message converted to BigInteger. + * @param p The prime modulus. + * @param g The generator. + * @param y The public key component. + * @return The CipherText pair (a, b). + * @throws IllegalArgumentException if inputs are null, negative, or message >= p. + */ + public static CipherText encrypt(BigInteger message, BigInteger p, BigInteger g, BigInteger y) { + if (message == null || p == null || g == null || y == null) { + throw new IllegalArgumentException("Inputs cannot be null."); + } + if (message.compareTo(BigInteger.ZERO) < 0) { + throw new IllegalArgumentException("Message must be non-negative."); + } + if (message.compareTo(p) >= 0) { + throw new IllegalArgumentException("Message must be smaller than the prime modulus p."); + } + + BigInteger k; + BigInteger pMinus1 = p.subtract(BigInteger.ONE); + + // Select ephemeral key k such that 1 < k < p-1 and gcd(k, p-1) = 1 + do { + k = new BigInteger(p.bitLength(), RANDOM); + } while (k.compareTo(BigInteger.ONE) <= 0 || k.compareTo(pMinus1) >= 0 || !k.gcd(pMinus1).equals(BigInteger.ONE)); + + BigInteger a = g.modPow(k, p); + BigInteger b = y.modPow(k, p).multiply(message).mod(p); + + return new CipherText(a, b); + } + + /** + * Decrypts a ciphertext using the private key. + * + * @param cipher The CipherText (a, b). + * @param x The private key. + * @param p The prime modulus. + * @return The decrypted message as BigInteger. + * @throws IllegalArgumentException if inputs are null. + */ + public static BigInteger decrypt(CipherText cipher, BigInteger x, BigInteger p) { + if (cipher == null || x == null || p == null) { + throw new IllegalArgumentException("Inputs cannot be null."); + } + + BigInteger a = cipher.a(); + BigInteger b = cipher.b(); + + BigInteger s = a.modPow(x, p); + BigInteger sInverse = s.modInverse(p); + + return b.multiply(sInverse).mod(p); + } + + /** + * Verifies if g is a valid generator for safe prime p = 2q + 1. + * + * @param g The candidate generator. + * @param p The safe prime. + * @param q The Sophie Germain prime (p-1)/2. + * @return True if g is a primitive root, False otherwise. + */ + private static boolean isValidGenerator(BigInteger g, BigInteger p, BigInteger q) { + // Fix: Must use braces {} for all if statements + if (g.equals(BigInteger.ONE)) { + return false; + } + if (g.modPow(BigInteger.TWO, p).equals(BigInteger.ONE)) { + return false; + } + return !g.modPow(q, p).equals(BigInteger.ONE); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java new file mode 100644 index 000000000000..63dec4846bbc --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java @@ -0,0 +1,145 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for ElGamalCipher. + * Includes property-based testing (homomorphism), probabilistic checks, + * and boundary validation. + */ +class ElGamalCipherTest { + + private static ElGamalCipher.KeyPair sharedKeys; + + @BeforeAll + static void setup() { + // Generate 256-bit keys for efficient unit testing + sharedKeys = ElGamalCipher.generateKeys(256); + } + + @Test + @DisplayName("Test Key Generation Validity") + void testKeyGeneration() { + Assertions.assertNotNull(sharedKeys.p()); + Assertions.assertNotNull(sharedKeys.g()); + Assertions.assertNotNull(sharedKeys.x()); + Assertions.assertNotNull(sharedKeys.y()); + + // Verify generator bounds: 1 < g < p + Assertions.assertTrue(sharedKeys.g().compareTo(BigInteger.ONE) > 0); + Assertions.assertTrue(sharedKeys.g().compareTo(sharedKeys.p()) < 0); + + // Verify private key bounds: 1 < x < p-1 + Assertions.assertTrue(sharedKeys.x().compareTo(BigInteger.ONE) > 0); + Assertions.assertTrue(sharedKeys.x().compareTo(sharedKeys.p().subtract(BigInteger.ONE)) < 0); + } + + @Test + @DisplayName("Security Check: Probabilistic Encryption") + void testSemanticSecurity() { + // Encrypting the same message twice MUST yield different ciphertexts + // due to the random ephemeral key 'k'. + BigInteger message = new BigInteger("123456789"); + + ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Check that the ephemeral keys (and thus 'a' components) were different + Assertions.assertNotEquals(c1.a(), c2.a(), "Ciphertexts must be randomized (Semantic Security violation)"); + Assertions.assertNotEquals(c1.b(), c2.b()); + + // But both must decrypt to the original message + Assertions.assertEquals(ElGamalCipher.decrypt(c1, sharedKeys.x(), sharedKeys.p()), message); + Assertions.assertEquals(ElGamalCipher.decrypt(c2, sharedKeys.x(), sharedKeys.p()), message); + } + + @ParameterizedTest + @MethodSource("provideMessages") + @DisplayName("Parameterized Test: Encrypt and Decrypt various messages") + void testEncryptDecrypt(String messageStr) { + BigInteger message = new BigInteger(messageStr.getBytes()); + + // Skip if message exceeds the test key size (256 bits) + if (message.compareTo(sharedKeys.p()) >= 0) { + return; + } + + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(message, decrypted, "Decrypted BigInteger must match original"); + Assertions.assertEquals(messageStr, new String(decrypted.toByteArray()), "Decrypted string must match original"); + } + + static Stream provideMessages() { + return Stream.of("Hello World", "TheAlgorithms", "A", "1234567890", "!@#$%^&*()"); + } + + @Test + @DisplayName("Edge Case: Message equals 0") + void testMessageZero() { + BigInteger zero = BigInteger.ZERO; + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(zero, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(zero, decrypted, "Should successfully encrypt/decrypt zero"); + } + + @Test + @DisplayName("Edge Case: Message equals p-1") + void testMessageMaxBound() { + BigInteger pMinus1 = sharedKeys.p().subtract(BigInteger.ONE); + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(pMinus1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(pMinus1, decrypted, "Should successfully encrypt/decrypt p-1"); + } + + @Test + @DisplayName("Negative Test: Message >= p should fail") + void testMessageTooLarge() { + BigInteger tooLarge = sharedKeys.p(); + Assertions.assertThrows(IllegalArgumentException.class, () -> ElGamalCipher.encrypt(tooLarge, sharedKeys.p(), sharedKeys.g(), sharedKeys.y())); + } + + @Test + @DisplayName("Negative Test: Decrypt with wrong private key") + void testWrongKeyDecryption() { + BigInteger message = new BigInteger("99999"); + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Generate a fake private key + BigInteger wrongX = sharedKeys.x().add(BigInteger.ONE); + + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, wrongX, sharedKeys.p()); + + Assertions.assertNotEquals(message, decrypted, "Decryption with wrong key must yield incorrect result"); + } + + @Test + @DisplayName("Property Test: Multiplicative Homomorphism") + void testHomomorphism() { + BigInteger m1 = new BigInteger("50"); + BigInteger m2 = BigInteger.TEN; // Fix: Replaced new BigInteger("10") with BigInteger.TEN + + ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(m1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(m2, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Multiply ciphertexts component-wise: (a1*a2, b1*b2) + BigInteger aNew = c1.a().multiply(c2.a()).mod(sharedKeys.p()); + BigInteger bNew = c1.b().multiply(c2.b()).mod(sharedKeys.p()); + ElGamalCipher.CipherText cCombined = new ElGamalCipher.CipherText(aNew, bNew); + + BigInteger decrypted = ElGamalCipher.decrypt(cCombined, sharedKeys.x(), sharedKeys.p()); + BigInteger expected = m1.multiply(m2).mod(sharedKeys.p()); + + Assertions.assertEquals(expected, decrypted, "Cipher must satisfy multiplicative homomorphism"); + } +} From 249b88fea2d8e7d425af8db053196e9b2cf1ed2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:03:26 +0100 Subject: [PATCH 07/68] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.1.0 to 13.2.0 (#7259) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.1.0 to 13.2.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.1.0...checkstyle-13.2.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.2.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 f13169cece97..3716323bd115 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.1.0 + 13.2.0
From 3835c4822a651f90be03830f778bbc41c898c64c Mon Sep 17 00:00:00 2001 From: swativ15 <122958079+swativ15@users.noreply.github.com> Date: Fri, 6 Feb 2026 03:37:11 +0530 Subject: [PATCH 08/68] Refactor: simplify validation and improve backtracking cleanup (#7258) ### Summary This PR makes small readability and maintainability improvements to the algorithm implementation. ### Changes - Removed a redundant `n < 0` validation check since the method contract already ensures valid `n` - Replaced `current.remove(current.size() - 1)` with `current.removeLast()` to better express backtracking intent ### Rationale - Simplifies input validation without changing behavior - Uses the `Deque` API to make the backtracking step clearer and less error-prone ### Impact - No change in algorithm logic or time/space complexity - Output remains identical Co-authored-by: Swati Vusurumarthi --- .../com/thealgorithms/backtracking/ArrayCombination.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java index f8cd0c40c20e..d05e33a4242f 100644 --- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java +++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java @@ -20,8 +20,8 @@ private ArrayCombination() { * @throws IllegalArgumentException if n or k are negative, or if k is greater than n. */ public static List> combination(int n, int k) { - if (n < 0 || k < 0 || k > n) { - throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n."); + if (k < 0 || k > n) { + throw new IllegalArgumentException("Invalid input: 0 ≀ k ≀ n is required."); } List> combinations = new ArrayList<>(); @@ -48,7 +48,7 @@ private static void combine(List> combinations, List curr for (int i = start; i < n; i++) { current.add(i); combine(combinations, current, i + 1, n, k); - current.remove(current.size() - 1); // Backtrack + current.removeLast(); // Backtrack } } } From 8403b8feb198ce250a1e2aef4416216154300961 Mon Sep 17 00:00:00 2001 From: Adarsh-Melath Date: Mon, 9 Feb 2026 23:02:56 +0530 Subject: [PATCH 09/68] fix:shape's dimension constraints added correctyl (issue id: #7260) (#7261) * fix:shape's dimension constraints added correctyl (issue id: #7260) * fix: base changed to baseLength for better understanding * fix: base changed to baseLength for better understanding * base changed to baseLength for better understanding * fix:cleared some format issues * fix:cleared some format issues --- .../java/com/thealgorithms/maths/Area.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java index 1eba6666dde3..08807580cb03 100644 --- a/src/main/java/com/thealgorithms/maths/Area.java +++ b/src/main/java/com/thealgorithms/maths/Area.java @@ -10,17 +10,17 @@ private Area() { /** * String of IllegalArgumentException for radius */ - private static final String POSITIVE_RADIUS = "Must be a positive radius"; + private static final String POSITIVE_RADIUS = "Radius must be greater than 0"; /** * String of IllegalArgumentException for height */ - private static final String POSITIVE_HEIGHT = "Must be a positive height"; + private static final String POSITIVE_HEIGHT = "Height must be greater than 0"; /** * String of IllegalArgumentException for base */ - private static final String POSITIVE_BASE = "Must be a positive base"; + private static final String POSITIVE_BASE = "Base must be greater than 0"; /** * Calculate the surface area of a cube. @@ -30,7 +30,7 @@ private Area() { */ public static double surfaceAreaCube(final double sideLength) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException("Side length must be greater than 0"); } return 6 * sideLength * sideLength; } @@ -57,10 +57,10 @@ public static double surfaceAreaSphere(final double radius) { */ public static double surfaceAreaPyramid(final double sideLength, final double slantHeight) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException(""); } if (slantHeight <= 0) { - throw new IllegalArgumentException("Must be a positive slantHeight"); + throw new IllegalArgumentException("slant height must be greater than 0"); } double baseArea = sideLength * sideLength; double lateralSurfaceArea = 2 * sideLength * slantHeight; @@ -76,10 +76,10 @@ public static double surfaceAreaPyramid(final double sideLength, final double sl */ public static double surfaceAreaRectangle(final double length, final double width) { if (length <= 0) { - throw new IllegalArgumentException("Must be a positive length"); + throw new IllegalArgumentException("Length must be greater than 0"); } if (width <= 0) { - throw new IllegalArgumentException("Must be a positive width"); + throw new IllegalArgumentException("Width must be greater than 0"); } return length * width; } @@ -109,7 +109,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh */ public static double surfaceAreaSquare(final double sideLength) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException("Side Length must be greater than 0"); } return sideLength * sideLength; } @@ -121,14 +121,14 @@ public static double surfaceAreaSquare(final double sideLength) { * @param height height of triangle * @return area of given triangle */ - public static double surfaceAreaTriangle(final double base, final double height) { - if (base <= 0) { + public static double surfaceAreaTriangle(final double baseLength, final double height) { + if (baseLength <= 0) { throw new IllegalArgumentException(POSITIVE_BASE); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return base * height / 2; + return baseLength * height / 2; } /** @@ -138,14 +138,14 @@ public static double surfaceAreaTriangle(final double base, final double height) * @param height height of a parallelogram * @return area of given parallelogram */ - public static double surfaceAreaParallelogram(final double base, final double height) { - if (base <= 0) { + public static double surfaceAreaParallelogram(final double baseLength, final double height) { + if (baseLength <= 0) { throw new IllegalArgumentException(POSITIVE_BASE); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return base * height; + return baseLength * height; } /** @@ -156,17 +156,17 @@ public static double surfaceAreaParallelogram(final double base, final double he * @param height height of trapezium * @return area of given trapezium */ - public static double surfaceAreaTrapezium(final double base1, final double base2, final double height) { - if (base1 <= 0) { + public static double surfaceAreaTrapezium(final double baseLength1, final double baseLength2, final double height) { + if (baseLength1 <= 0) { throw new IllegalArgumentException(POSITIVE_BASE + 1); } - if (base2 <= 0) { + if (baseLength2 <= 0) { throw new IllegalArgumentException(POSITIVE_BASE + 2); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return (base1 + base2) * height / 2; + return (baseLength1 + baseLength2) * height / 2; } /** From 0c79d33eb591842c8cc809f02a9303ab843a1c37 Mon Sep 17 00:00:00 2001 From: Mohammed Vijahath <116938255+vizahat36@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:30:15 +0530 Subject: [PATCH 10/68] Add Tower of Hanoi recursive algorithm (#7235) * Add Tower of Hanoi recursive algorithm with tests * Fix SpotBugs issues and format TowerOfHanoi tests * Enhance existing TowerOfHanoi and remove duplicate recursion version * Fix clang-format issue --------- Co-authored-by: Deniz Altunkapan --- .../puzzlesandgames/TowerOfHanoi.java | 51 +++++++++++-------- .../puzzlesandgames/TowerOfHanoiTest.java | 32 ++++++++++++ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java index 72e9a14ac070..d94bef69cd3a 100644 --- a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java +++ b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java @@ -3,27 +3,32 @@ import java.util.List; /** - * The {@code TowerOfHanoi} class provides a recursive solution to the Tower of Hanoi puzzle. - * This puzzle involves moving a set of discs from one pole to another, following specific rules: + * Recursive solution to the Tower of Hanoi puzzle. + * + *

+ * The puzzle rules are: * 1. Only one disc can be moved at a time. * 2. A disc can only be placed on top of a larger disc. * 3. All discs must start on one pole and end on another. + *

* - * This implementation recursively calculates the steps required to solve the puzzle and stores them - * in a provided list. + *

+ * The recursion follows three steps: + * 1. Move {@code n-1} discs from start to intermediate. + * 2. Move the largest disc from start to end. + * 3. Move {@code n-1} discs from intermediate to end. + *

* *

- * For more information about the Tower of Hanoi, see - * Tower of Hanoi on Wikipedia. + * Time Complexity: O(2^n) - exponential due to recursive expansion. + * Space Complexity: O(n) - recursion stack depth. *

* - * The {@code shift} method takes the number of discs and the names of the poles, - * and appends the steps required to solve the puzzle to the provided list. - * Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem. - * Space Complexity: O(n) - Linear space complexity due to the recursion stack. - * Wikipedia: https://en.wikipedia.org/wiki/Tower_of_Hanoi + *

+ * See Tower of Hanoi on Wikipedia. + *

*/ -final class TowerOfHanoi { +public final class TowerOfHanoi { private TowerOfHanoi() { } @@ -36,6 +41,7 @@ private TowerOfHanoi() { * @param intermediatePole The name of the intermediate pole used as a temporary holding area. * @param endPole The name of the end pole to which discs are moved. * @param result A list to store the steps required to solve the puzzle. + * @throws IllegalArgumentException if {@code n} is negative. * *

* This method is called recursively to move n-1 discs @@ -51,15 +57,20 @@ private TowerOfHanoi() { *

*/ public static void shift(int n, String startPole, String intermediatePole, String endPole, List result) { - if (n != 0) { - // Move n-1 discs from startPole to intermediatePole - shift(n - 1, startPole, endPole, intermediatePole, result); + if (n < 0) { + throw new IllegalArgumentException("Number of discs must be non-negative"); + } + if (n == 0) { + return; + } - // Add the move of the nth disc from startPole to endPole - result.add(String.format("Move %d from %s to %s", n, startPole, endPole)); + // Move n-1 discs from startPole to intermediatePole + shift(n - 1, startPole, endPole, intermediatePole, result); - // Move the n-1 discs from intermediatePole to endPole - shift(n - 1, intermediatePole, startPole, endPole, result); - } + // Add the move of the nth disc from startPole to endPole + result.add(String.format("Move %d from %s to %s", n, startPole, endPole)); + + // Move the n-1 discs from intermediatePole to endPole + shift(n - 1, intermediatePole, startPole, endPole, result); } } diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java index 42669eb03bb4..f0a2686d3e4b 100644 --- a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java +++ b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java @@ -1,14 +1,31 @@ package com.thealgorithms.puzzlesandgames; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class TowerOfHanoiTest { + @ParameterizedTest + @MethodSource("diskCountAndMoveCount") + void testMoveCountMatchesFormula(int disks, int expectedMoves) { + List result = new ArrayList<>(); + TowerOfHanoi.shift(disks, "A", "B", "C", result); + assertEquals(expectedMoves, result.size()); + } + + private static Stream diskCountAndMoveCount() { + return Stream.of(Arguments.of(1, 1), Arguments.of(2, 3), Arguments.of(3, 7), Arguments.of(4, 15), Arguments.of(5, 31), Arguments.of(10, 1023)); + } + @Test public void testHanoiWithOneDisc() { List result = new ArrayList<>(); @@ -39,6 +56,15 @@ public void testHanoiWithThreeDiscs() { assertEquals(expected, result); } + @Test + public void testHanoiWithDifferentPoles() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(2, "X", "Y", "Z", result); + + List expected = List.of("Move 1 from X to Y", "Move 2 from X to Z", "Move 1 from Y to Z"); + assertEquals(expected, result); + } + @Test public void testHanoiWithZeroDiscs() { List result = new ArrayList<>(); @@ -47,4 +73,10 @@ public void testHanoiWithZeroDiscs() { // There should be no moves if there are 0 discs assertTrue(result.isEmpty()); } + + @Test + public void testHanoiWithNegativeDiscsThrows() { + List result = new ArrayList<>(); + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.shift(-1, "Pole1", "Pole2", "Pole3", result)); + } } From 504b5283eb75f9416693740eba48c5cea9eb28bd Mon Sep 17 00:00:00 2001 From: Muhammad Muneeb Mubashar Date: Fri, 13 Feb 2026 01:43:27 -0800 Subject: [PATCH 11/68] Refactor getAbsValue method to use Math.abs (#7266) --- src/main/java/com/thealgorithms/maths/AbsoluteValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java index b9279d5a244a..114eb71b1015 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java @@ -11,6 +11,6 @@ private AbsoluteValue() { * @return The absolute value of the {@code number} */ public static int getAbsValue(int number) { - return number < 0 ? -number : number; + return Math.abs(number); } } From c8d029107ce397c6d427a23a5127003d87fb0792 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:57:18 +0100 Subject: [PATCH 12/68] chore(deps): bump org.junit:junit-bom from 6.0.2 to 6.0.3 (#7271) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.2...r6.0.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-version: 6.0.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 3716323bd115..2445a1e920a8 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.junit junit-bom - 6.0.2 + 6.0.3 pom import From dfa6bf06910b2c989a0e4acb82155ebf51d869f9 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:20:34 +0100 Subject: [PATCH 13/68] style: remove redundant PMD exclusions (#7272) --- pmd-exclude.properties | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pmd-exclude.properties b/pmd-exclude.properties index a3c95b12fa4b..64562c524728 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -53,14 +53,11 @@ com.thealgorithms.maths.Gaussian=UselessParentheses com.thealgorithms.maths.GcdSolutionWrapper=UselessParentheses com.thealgorithms.maths.HeronsFormula=UselessParentheses com.thealgorithms.maths.JugglerSequence=UselessMainMethod -com.thealgorithms.maths.KaprekarNumbers=UselessParentheses com.thealgorithms.maths.KeithNumber=UselessMainMethod,UselessParentheses -com.thealgorithms.maths.LeonardoNumber=UselessParentheses com.thealgorithms.maths.LinearDiophantineEquationsSolver=UselessMainMethod,UselessParentheses com.thealgorithms.maths.MagicSquare=UselessMainMethod com.thealgorithms.maths.PiNilakantha=UselessMainMethod com.thealgorithms.maths.Prime.PrimeCheck=UselessMainMethod -com.thealgorithms.maths.PythagoreanTriple=UselessMainMethod com.thealgorithms.maths.RomanNumeralUtil=UselessParentheses com.thealgorithms.maths.SecondMinMax=UselessParentheses com.thealgorithms.maths.SecondMinMaxTest=UnnecessaryFullyQualifiedName @@ -71,7 +68,6 @@ com.thealgorithms.maths.TrinomialTriangle=UselessMainMethod,UselessParentheses com.thealgorithms.maths.VectorCrossProduct=UselessMainMethod com.thealgorithms.maths.Volume=UselessParentheses com.thealgorithms.matrix.RotateMatrixBy90Degrees=UselessMainMethod -com.thealgorithms.misc.Sparsity=UselessParentheses com.thealgorithms.others.BankersAlgorithm=UselessMainMethod com.thealgorithms.others.BrianKernighanAlgorithm=UselessMainMethod com.thealgorithms.others.CRC16=UselessMainMethod,UselessParentheses @@ -79,11 +75,9 @@ com.thealgorithms.others.CRC32=UselessMainMethod com.thealgorithms.others.Damm=UnnecessaryFullyQualifiedName,UselessMainMethod com.thealgorithms.others.Dijkstra=UselessMainMethod com.thealgorithms.others.GaussLegendre=UselessMainMethod -com.thealgorithms.others.HappyNumbersSeq=UselessMainMethod com.thealgorithms.others.Huffman=UselessMainMethod com.thealgorithms.others.InsertDeleteInArray=UselessMainMethod com.thealgorithms.others.KochSnowflake=UselessMainMethod -com.thealgorithms.others.Krishnamurthy=UselessMainMethod com.thealgorithms.others.LinearCongruentialGenerator=UselessMainMethod com.thealgorithms.others.Luhn=UnnecessaryFullyQualifiedName,UselessMainMethod com.thealgorithms.others.Mandelbrot=UselessMainMethod,UselessParentheses @@ -94,7 +88,6 @@ com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod -com.thealgorithms.puzzlesandgames.Sudoku=UselessMainMethod com.thealgorithms.recursion.DiceThrower=UselessMainMethod com.thealgorithms.searches.HowManyTimesRotated=UselessMainMethod com.thealgorithms.searches.InterpolationSearch=UselessParentheses @@ -108,15 +101,11 @@ com.thealgorithms.sorts.MergeSortNoExtraSpace=UselessParentheses com.thealgorithms.sorts.RadixSort=UselessParentheses com.thealgorithms.sorts.TreeSort=UselessMainMethod com.thealgorithms.sorts.WiggleSort=UselessParentheses -com.thealgorithms.stacks.LargestRectangle=UselessMainMethod com.thealgorithms.stacks.MaximumMinimumWindow=UselessMainMethod com.thealgorithms.stacks.PostfixToInfix=UselessParentheses -com.thealgorithms.strings.Alphabetical=UselessMainMethod com.thealgorithms.strings.HorspoolSearch=UnnecessaryFullyQualifiedName,UselessParentheses -com.thealgorithms.strings.KMP=UselessMainMethod com.thealgorithms.strings.Lower=UselessMainMethod com.thealgorithms.strings.Palindrome=UselessParentheses com.thealgorithms.strings.Pangram=UselessMainMethod -com.thealgorithms.strings.RabinKarp=UselessMainMethod com.thealgorithms.strings.Rotation=UselessMainMethod com.thealgorithms.strings.Upper=UselessMainMethod From 1646edaeb96a27add8d83788782550ee2048b263 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:06:08 +0100 Subject: [PATCH 14/68] style: remove redundant exclusions (#7276) --- spotbugs-exclude.xml | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 1390387bacdf..7483d37daa57 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -8,18 +8,12 @@ - - - - - - @@ -32,15 +26,9 @@ - - - - - - @@ -50,15 +38,9 @@ - - - - - - @@ -71,9 +53,6 @@ - - - @@ -83,9 +62,6 @@ - - - @@ -117,9 +93,6 @@ - - - @@ -150,9 +123,6 @@ - - - @@ -189,24 +159,15 @@ - - - - - - - - - From c9bda3dad761c5b27edc36e32b8b9c746ebda345 Mon Sep 17 00:00:00 2001 From: Syed Mohammad Saad <134770714+SYEDMDSAAD@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:54:34 +0530 Subject: [PATCH 15/68] Add string algorithms: RemoveStars and ComplexNumberMultiply (#7275) * Add RemoveStars and ComplexNumberMultiply string algorithms * Add RemoveStars and ComplexNumberMultiply string algorithms * Add unit tests for RemoveStars and ComplexNumber Multiply * Fix checkstyle * Remove redundant main method * Move ComplexNumberMultiply to maths package and add input validation with tests * Apply spotless formatting --------- Co-authored-by: Deniz Altunkapan --- .../maths/ComplexNumberMultiply.java | 32 +++++++++++++++++ .../thealgorithms/strings/RemoveStars.java | 31 +++++++++++++++++ .../maths/ComplexNumberMultiplyTest.java | 34 +++++++++++++++++++ .../strings/RemoveStarsTest.java | 28 +++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java create mode 100644 src/main/java/com/thealgorithms/strings/RemoveStars.java create mode 100644 src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java create mode 100644 src/test/java/com/thealgorithms/strings/RemoveStarsTest.java diff --git a/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java new file mode 100644 index 000000000000..4b68b7824574 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +/** + * Multiplies two complex numbers represented as strings in the form "a+bi". + * Supports negative values and validates input format. + */ +public final class ComplexNumberMultiply { + + private ComplexNumberMultiply() { + } + + private static int[] parse(String num) { + if (num == null || !num.matches("-?\\d+\\+-?\\d+i")) { + throw new IllegalArgumentException("Invalid complex number format: " + num); + } + + String[] parts = num.split("\\+"); + int real = Integer.parseInt(parts[0]); + int imaginary = Integer.parseInt(parts[1].replace("i", "")); + return new int[] {real, imaginary}; + } + + public static String multiply(String num1, String num2) { + int[] a = parse(num1); + int[] b = parse(num2); + + int real = a[0] * b[0] - a[1] * b[1]; + int imaginary = a[0] * b[1] + a[1] * b[0]; + + return real + "+" + imaginary + "i"; + } +} diff --git a/src/main/java/com/thealgorithms/strings/RemoveStars.java b/src/main/java/com/thealgorithms/strings/RemoveStars.java new file mode 100644 index 000000000000..816311e9da84 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/RemoveStars.java @@ -0,0 +1,31 @@ +package com.thealgorithms.strings; + +/** + * Removes characters affected by '*' in a string. + * Each '*' deletes the closest non-star character to its left. + * + * Example: + * Input: leet**cod*e + * Output: lecoe + */ + +public final class RemoveStars { + + private RemoveStars() { + } + + public static String removeStars(String s) { + StringBuilder result = new StringBuilder(); + + for (char c : s.toCharArray()) { + if (c == '*') { + if (result.length() > 0) { + result.deleteCharAt(result.length() - 1); + } + } else { + result.append(c); + } + } + return result.toString(); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java new file mode 100644 index 000000000000..02e964b53771 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ComplexNumberMultiplyTest { + + @Test + void testExample() { + assertEquals("0+2i", ComplexNumberMultiply.multiply("1+1i", "1+1i")); + } + + @Test + void testNegative() { + assertEquals("0+-2i", ComplexNumberMultiply.multiply("1+-1i", "1+-1i")); + } + + @Test + void testZero() { + assertEquals("0+0i", ComplexNumberMultiply.multiply("0+0i", "5+3i")); + } + + @Test + void testInvalidFormat() { + assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply("1+1", "1+1i")); + } + + @Test + void testNullInput() { + assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply(null, "1+1i")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java new file mode 100644 index 000000000000..3beb2e83399b --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java @@ -0,0 +1,28 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RemoveStarsTest { + + @Test + void testExampleCase() { + assertEquals("lecoe", RemoveStars.removeStars("leet**cod*e")); + } + + @Test + void testAllStars() { + assertEquals("", RemoveStars.removeStars("abc***")); + } + + @Test + void testNoStars() { + assertEquals("hello", RemoveStars.removeStars("hello")); + } + + @Test + void testSingleCharacter() { + assertEquals("", RemoveStars.removeStars("a*")); + } +} From 2ae1bdfd9a3897d206c0e62751b7ec562dcafb08 Mon Sep 17 00:00:00 2001 From: Neha-2005-VCE Date: Thu, 19 Feb 2026 00:04:41 +0530 Subject: [PATCH 16/68] feat: add contains() method to DynamicArray (#7270) * feat: add contains() method to DynamicArray * style: fix clang formatting * style: fix clang format issues * style: apply remaining clang formatting --------- Co-authored-by: Deniz Altunkapan --- .../dynamicarray/DynamicArray.java | 43 +++++++++++++++---- .../dynamicarray/DynamicArrayTest.java | 19 ++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java index cd5dc580b694..dbdb2d806209 100644 --- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java +++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java @@ -63,7 +63,8 @@ public void add(final E element) { * * @param index the index at which the element is to be placed * @param element the element to be inserted at the specified index - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the number of elements */ public void put(final int index, E element) { if (index < 0) { @@ -82,7 +83,8 @@ public void put(final int index, E element) { * * @param index the index of the element to retrieve * @return the element at the specified index - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the current size */ @SuppressWarnings("unchecked") public E get(final int index) { @@ -97,7 +99,8 @@ public E get(final int index) { * * @param index the index of the element to be removed * @return the element that was removed from the array - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the current size */ public E remove(final int index) { if (index < 0 || index >= size) { @@ -127,6 +130,21 @@ public boolean isEmpty() { return size == 0; } + /** + * Checks whether the array contains the specified element. + * + * @param element the element to check for + * @return true if the array contains the specified element, false otherwise + */ + public boolean contains(final E element) { + for (int i = 0; i < size; i++) { + if (Objects.equals(elements[i], element)) { + return true; + } + } + return false; + } + /** * Returns a sequential stream with this collection as its source. * @@ -137,7 +155,8 @@ public Stream stream() { } /** - * Ensures that the array has enough capacity to hold the specified number of elements. + * Ensures that the array has enough capacity to hold the specified number of + * elements. * * @param minCapacity the minimum capacity required */ @@ -150,7 +169,8 @@ private void ensureCapacity(int minCapacity) { /** * Removes the element at the specified index without resizing the array. - * This method shifts any subsequent elements to the left and clears the last element. + * This method shifts any subsequent elements to the left and clears the last + * element. * * @param index the index of the element to remove */ @@ -163,7 +183,8 @@ private void fastRemove(int index) { } /** - * Returns a string representation of the array, including only the elements that are currently stored. + * Returns a string representation of the array, including only the elements + * that are currently stored. * * @return a string containing the elements in the array */ @@ -227,7 +248,9 @@ public E next() { /** * Removes the last element returned by this iterator. * - * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method + * @throws IllegalStateException if the next method has not yet been called, or + * the remove method has already been called after + * the last call to the next method */ @Override public void remove() { @@ -242,7 +265,8 @@ public void remove() { /** * Checks for concurrent modifications to the array during iteration. * - * @throws ConcurrentModificationException if the array has been modified structurally + * @throws ConcurrentModificationException if the array has been modified + * structurally */ private void checkForComodification() { if (modCount != expectedModCount) { @@ -251,7 +275,8 @@ private void checkForComodification() { } /** - * Performs the given action for each remaining element in the iterator until all elements have been processed. + * Performs the given action for each remaining element in the iterator until + * all elements have been processed. * * @param action the action to be performed for each element * @throws NullPointerException if the specified action is null diff --git a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java index 8fdc93e1ca22..39e3fa0abe77 100644 --- a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java @@ -255,4 +255,23 @@ public void testCapacityDoubling() { assertEquals(3, array.getSize()); assertEquals("Charlie", array.get(2)); } + + @Test + public void testContains() { + DynamicArray array = new DynamicArray<>(); + array.add(1); + array.add(2); + array.add(3); + + assertTrue(array.contains(2)); + assertFalse(array.contains(5)); + } + + @Test + public void testContainsWithNull() { + DynamicArray array = new DynamicArray<>(); + array.add(null); + + assertTrue(array.contains(null)); + } } From 0a2c7f2e3bb539d3aee68673b5d743d178d455b2 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:32:32 +0100 Subject: [PATCH 17/68] style: include `BL_BURYING_LOGIC` (#7277) --- spotbugs-exclude.xml | 3 -- .../searches/RecursiveBinarySearch.java | 33 +++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 7483d37daa57..a8eedcfed402 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -90,9 +90,6 @@ - - - diff --git a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java index daf0c12c0978..1716e78964ae 100644 --- a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java @@ -23,28 +23,27 @@ public int find(T[] arr, T target) { // Recursive binary search function public int binsear(T[] arr, int left, int right, T target) { - if (right >= left) { - int mid = left + (right - left) / 2; - - // Compare the element at the middle with the target - int comparison = arr[mid].compareTo(target); + if (right < left) { + // Element is not present in the array + return -1; + } + final int mid = left + (right - left) / 2; - // If the element is equal to the target, return its index - if (comparison == 0) { - return mid; - } + // Compare the element at the middle with the target + final int comparison = arr[mid].compareTo(target); - // If the element is greater than the target, search in the left subarray - if (comparison > 0) { - return binsear(arr, left, mid - 1, target); - } + // If the element is equal to the target, return its index + if (comparison == 0) { + return mid; + } - // Otherwise, search in the right subarray - return binsear(arr, mid + 1, right, target); + // If the element is greater than the target, search in the left subarray + if (comparison > 0) { + return binsear(arr, left, mid - 1, target); } - // Element is not present in the array - return -1; + // Otherwise, search in the right subarray + return binsear(arr, mid + 1, right, target); } public static void main(String[] args) { From 29ce2ef66607083e922b389650af160b7606903a Mon Sep 17 00:00:00 2001 From: Ruturaj Jadhav Date: Fri, 20 Feb 2026 02:41:56 +0530 Subject: [PATCH 18/68] Add RangeSumQuery algorithm in prefix folder (#7280) * add Subarray Sum Equals K using prefix sum * Add RangeSumQuery algorithm in prefix folder * chore: apply clang-format to RangeSumQueryTest file * Add import statement for JUnit test class --------- Co-authored-by: Deniz Altunkapan --- .../prefixsum/RangeSumQuery.java | 73 +++++++++++++++++++ .../prefixsum/RangeSumQueryTest.java | 73 +++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java create mode 100644 src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java diff --git a/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java new file mode 100644 index 000000000000..14a02a2de4d0 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java @@ -0,0 +1,73 @@ +package com.thealgorithms.prefixsum; + +/** + * Implements an algorithm to efficiently compute the sum of elements + * between any two indices in an integer array using the Prefix Sum technique. + * + *

+ * Given an array nums, this algorithm precomputes the prefix sum array + * to allow O(1) sum queries for any range [left, right]. + *

+ * + *

+ * Let prefixSum[i] be the sum of elements from index 0 to i-1. + * The sum of elements from left to right is: + * + *

+ * prefixSum[right + 1] - prefixSum[left]
+ * 
+ *

+ * + *

+ * Time Complexity: O(N) for preprocessing, O(1) per query
+ * Space Complexity: O(N) + *

+ * + * @author Ruturaj Jadhav, ruturajjadhav07 + */ +public final class RangeSumQuery { + + private RangeSumQuery() { + // Utility class; prevent instantiation + } + + /** + * Computes the prefix sum array for efficient range queries. + * + * @param nums The input integer array. + * @return Prefix sum array where prefixSum[i+1] = sum of nums[0..i]. + * @throws IllegalArgumentException if nums is null. + */ + public static int[] buildPrefixSum(int[] nums) { + if (nums == null) { + throw new IllegalArgumentException("Input array cannot be null"); + } + + int n = nums.length; + int[] prefixSum = new int[n + 1]; + for (int i = 0; i < n; i++) { + prefixSum[i + 1] = prefixSum[i] + nums[i]; + } + return prefixSum; + } + + /** + * Returns the sum of elements from index left to right (inclusive) + * using the provided prefix sum array. + * + * @param prefixSum The prefix sum array computed using buildPrefixSum. + * @param left The start index (inclusive). + * @param right The end index (inclusive). + * @return The sum of elements in the range [left, right]. + * @throws IllegalArgumentException if indices are invalid. + */ + public static int sumRange(int[] prefixSum, int left, int right) { + if (prefixSum == null) { + throw new IllegalArgumentException("Prefix sum array cannot be null"); + } + if (left < 0 || right >= prefixSum.length - 1 || left > right) { + throw new IllegalArgumentException("Invalid range indices"); + } + return prefixSum[right + 1] - prefixSum[left]; + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java new file mode 100644 index 000000000000..12072318ac74 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.prefixsum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link RangeSumQuery}. + */ +class RangeSumQueryTest { + + @Test + void testBasicExample() { + int[] nums = {1, 2, 3, 4, 5}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(6, RangeSumQuery.sumRange(prefixSum, 0, 2)); // 1+2+3 + assertEquals(9, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2+3+4 + assertEquals(15, RangeSumQuery.sumRange(prefixSum, 0, 4)); // 1+2+3+4+5 + assertEquals(12, RangeSumQuery.sumRange(prefixSum, 2, 4)); // 3+4+5 + } + + @Test + void testSingleElement() { + int[] nums = {7}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(7, RangeSumQuery.sumRange(prefixSum, 0, 0)); + } + + @Test + void testAllZeros() { + int[] nums = {0, 0, 0, 0}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(0, RangeSumQuery.sumRange(prefixSum, 0, 3)); + assertEquals(0, RangeSumQuery.sumRange(prefixSum, 1, 2)); + } + + @Test + void testNegativeNumbers() { + int[] nums = {-1, 2, -3, 4}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(-2, RangeSumQuery.sumRange(prefixSum, 0, 2)); // -1+2-3 + assertEquals(3, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2-3+4 + } + + @Test + void testEmptyArrayThrowsException() { + int[] nums = {}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 0, 0)); + } + + @Test + void testNullArrayThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.buildPrefixSum(null)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(null, 0, 0)); + } + + @Test + void testInvalidIndicesThrowsException() { + int[] nums = {1, 2, 3}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 1, 5)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 2, 1)); + } +} From 7c38e5acd29a22e29c6954ae7088889a4afde689 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:48:12 +0100 Subject: [PATCH 19/68] style: include `DM_NEXTINT_VIA_NEXTDOUBLE` (#7282) --- spotbugs-exclude.xml | 3 --- .../com/thealgorithms/searches/LinearSearchThreadTest.java | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index a8eedcfed402..13d72334e594 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -11,9 +11,6 @@ - - - diff --git a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java index 534c2a4487b2..c0d82489209f 100644 --- a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java +++ b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Random; import org.junit.jupiter.api.Test; class LinearSearchThreadTest { @@ -62,10 +63,11 @@ void testSearcherEmptySegment() throws InterruptedException { void testSearcherRandomNumbers() throws InterruptedException { int size = 200; int[] array = new int[size]; + Random random = new Random(); for (int i = 0; i < size; i++) { - array[i] = (int) (Math.random() * 100); + array[i] = random.nextInt(100); } - int target = array[(int) (Math.random() * size)]; // Randomly select a target that is present + final int target = array[random.nextInt(size)]; // Randomly select a target that is present Searcher searcher = new Searcher(array, 0, size, target); searcher.start(); searcher.join(); From 2d443a9991f7a80f1ab6bca437d1b953f7d05420 Mon Sep 17 00:00:00 2001 From: Mohan E <777emohan@gmail.com> Date: Sat, 21 Feb 2026 18:40:37 +0530 Subject: [PATCH 20/68] Add time and space complexity documentation to LongestNonRepetitiveSubstring (#7284) --- .../strings/LongestNonRepetitiveSubstring.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java index 6808cd50602f..51e8dc6b02c3 100644 --- a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java +++ b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java @@ -13,6 +13,12 @@ private LongestNonRepetitiveSubstring() { /** * Finds the length of the longest substring without repeating characters. * + * Uses the sliding window technique with a HashMap to track + * the last seen index of each character. + * + * Time Complexity: O(n), where n is the length of the input string. + * Space Complexity: O(min(n, m)), where m is the size of the character set. + * * @param s the input string * @return the length of the longest non-repetitive substring */ From 109df1f39837d0102646920dcfa353ae3c09e7cc Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:27:13 +0100 Subject: [PATCH 21/68] style: include `SUA_SUSPICIOUS_UNINITIALIZED_ARRAY` (#7285) --- spotbugs-exclude.xml | 3 --- .../com/thealgorithms/divideandconquer/ClosestPair.java | 4 ---- .../thealgorithms/divideandconquer/ClosestPairTest.java | 8 -------- 3 files changed, 15 deletions(-) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 13d72334e594..8c42802520e3 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -96,9 +96,6 @@ - - - diff --git a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java index 4c9c40c83174..323098a99887 100644 --- a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java +++ b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java @@ -66,10 +66,6 @@ public static class Location { } } - public Location[] createLocation(int numberValues) { - return new Location[numberValues]; - } - public Location buildLocation(double x, double y) { return new Location(x, y); } diff --git a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java index 38784228d68e..b25fd796b112 100644 --- a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java +++ b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java @@ -16,14 +16,6 @@ public void testBuildLocation() { assertEquals(4.0, point.y); } - @Test - public void testCreateLocation() { - ClosestPair cp = new ClosestPair(5); - ClosestPair.Location[] locations = cp.createLocation(5); - assertNotNull(locations); - assertEquals(5, locations.length); - } - @Test public void testXPartition() { ClosestPair cp = new ClosestPair(5); From 12935c291def0a4079c961a0aa7652177b1b05f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Flar=20Eker?= Date: Sun, 22 Feb 2026 21:15:22 +0300 Subject: [PATCH 22/68] Add Longest Repeated Substring algorithm (#7286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Longest Repeated Substring algorithm Implement LongestRepeatedSubstring in the strings package using the existing SuffixArray and Kasai's algorithm for LCP array construction. Includes parameterized unit tests covering typical and edge cases. * style: reformat test file per clang-format and add coverage test --------- Co-authored-by: Çağlar Eker --- .../strings/LongestRepeatedSubstring.java | 83 +++++++++++++++++++ .../strings/LongestRepeatedSubstringTest.java | 33 ++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java create mode 100644 src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java diff --git a/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java new file mode 100644 index 000000000000..87c9278fd4bf --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java @@ -0,0 +1,83 @@ +package com.thealgorithms.strings; + +/** + * Finds the longest substring that occurs at least twice in a given string. + * + *

Uses the suffix array (via {@link SuffixArray}) and Kasai's algorithm + * to build the LCP (Longest Common Prefix) array, then returns the substring + * corresponding to the maximum LCP value.

+ * + *

Time complexity: O(n logΒ² n) for suffix array construction + O(n) for LCP.

+ * + * @see Longest repeated substring problem + * @see SuffixArray + */ +public final class LongestRepeatedSubstring { + + private LongestRepeatedSubstring() { + } + + /** + * Returns the longest substring that appears at least twice in the given text. + * + * @param text the input string + * @return the longest repeated substring, or an empty string if none exists + */ + public static String longestRepeatedSubstring(String text) { + if (text == null || text.length() <= 1) { + return ""; + } + + final int[] suffixArray = SuffixArray.buildSuffixArray(text); + final int[] lcp = buildLcpArray(text, suffixArray); + + int maxLen = 0; + int maxIdx = 0; + for (int i = 0; i < lcp.length; i++) { + if (lcp[i] > maxLen) { + maxLen = lcp[i]; + maxIdx = suffixArray[i + 1]; + } + } + + return text.substring(maxIdx, maxIdx + maxLen); + } + + /** + * Builds the LCP (Longest Common Prefix) array using Kasai's algorithm. + * + *

LCP[i] is the length of the longest common prefix between the suffixes + * at positions suffixArray[i] and suffixArray[i+1] in sorted order.

+ * + * @param text the original string + * @param suffixArray the suffix array of the string + * @return the LCP array of length n-1 + */ + static int[] buildLcpArray(String text, int[] suffixArray) { + final int n = text.length(); + final int[] rank = new int[n]; + final int[] lcp = new int[n - 1]; + + for (int i = 0; i < n; i++) { + rank[suffixArray[i]] = i; + } + + int k = 0; + for (int i = 0; i < n; i++) { + if (rank[i] == n - 1) { + k = 0; + continue; + } + final int j = suffixArray[rank[i] + 1]; + while (i + k < n && j + k < n && text.charAt(i + k) == text.charAt(j + k)) { + k++; + } + lcp[rank[i]] = k; + if (k > 0) { + k--; + } + } + + return lcp; + } +} diff --git a/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java new file mode 100644 index 000000000000..366f6863340d --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class LongestRepeatedSubstringTest { + + @ParameterizedTest(name = "\"{0}\" -> \"{1}\"") + @MethodSource("provideTestCases") + void testLongestRepeatedSubstring(String input, String expected) { + assertEquals(expected, LongestRepeatedSubstring.longestRepeatedSubstring(input)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of("banana", "ana"), Arguments.of("abcabc", "abc"), Arguments.of("aaaa", "aaa"), Arguments.of("abcd", ""), Arguments.of("a", ""), Arguments.of("", ""), Arguments.of(null, ""), Arguments.of("aab", "a"), Arguments.of("aa", "a"), Arguments.of("mississippi", "issi")); + } + + @ParameterizedTest(name = "\"{0}\" -> LCP={1}") + @MethodSource("provideLcpTestCases") + void testBuildLcpArray(String input, int[] expectedLcp) { + int[] suffixArray = SuffixArray.buildSuffixArray(input); + assertArrayEquals(expectedLcp, LongestRepeatedSubstring.buildLcpArray(input, suffixArray)); + } + + private static Stream provideLcpTestCases() { + return Stream.of(Arguments.of("banana", new int[] {1, 3, 0, 0, 2}), Arguments.of("ab", new int[] {0})); + } +} From 10114b79ae8869e9351c8fd38eca798978119204 Mon Sep 17 00:00:00 2001 From: Vasundhara117 Date: Tue, 24 Feb 2026 00:36:51 +0530 Subject: [PATCH 23/68] Add ReverseQueueRecursion.java: Reverse a Queue using recursion (#7281) * Create ReverseQueueRecursion Add ReverseQueueRecursion.java: - Reverses a Queue using recursion (generic ) - Includes unit tests in ReverseQueueRecursionTest.java - Follows repo style (final class, private constructor, Javadoc) * Create ReverseQueueRecursionTest.java Add ReverseQueueRecursionTest as required by CONTRIBUTING.md * Rename ReverseQueueRecursion to ReverseQueueRecursion.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursion.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursionTest.java * Update ReverseQueueRecursion.java --- .../queues/ReverseQueueRecursion.java | 28 ++++++++++ .../queues/ReverseQueueRecursionTest.java | 54 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java create mode 100644 src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java new file mode 100644 index 000000000000..79275dcefe20 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java @@ -0,0 +1,28 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.Queue; + +/** + * Reverse a queue using recursion. + */ +public final class ReverseQueueRecursion { + private ReverseQueueRecursion() { + // private constructor to prevent instantiation + } + + /** + * Reverses the given queue recursively. + * + * @param queue the queue to reverse + * @param the type of elements in the queue + */ + public static void reverseQueue(final Queue queue) { + if (queue == null || queue.isEmpty()) { + return; + } + + final T front = queue.poll(); + reverseQueue(queue); + queue.add(front); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java new file mode 100644 index 000000000000..e3abe15b6a46 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.datastructures.queues; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedList; +import java.util.Queue; +import org.junit.jupiter.api.Test; + +class ReverseQueueRecursionTest { + @Test + void testReverseMultipleElements() { + Queue queue = new LinkedList<>(); + queue.add(1); + queue.add(2); + queue.add(3); + queue.add(4); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals(4, queue.poll()); + assertEquals(3, queue.poll()); + assertEquals(2, queue.poll()); + assertEquals(1, queue.poll()); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseSingleElement() { + Queue queue = new LinkedList<>(); + queue.add(42); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals(42, queue.poll()); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseEmptyQueue() { + Queue queue = new LinkedList<>(); + ReverseQueueRecursion.reverseQueue(queue); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseStringQueue() { + Queue queue = new LinkedList<>(); + queue.add("A"); + queue.add("B"); + queue.add("C"); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals("C", queue.poll()); + assertEquals("B", queue.poll()); + assertEquals("A", queue.poll()); + assertTrue(queue.isEmpty()); + } +} From 023f856a9bca320e5ab4324e3efb4766ae2f4f54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:55:11 +0100 Subject: [PATCH 24/68] chore(deps-dev): bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.4 to 3.5.5 (#7288) chore(deps-dev): bump org.apache.maven.plugins:maven-surefire-plugin Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.4 to 3.5.5. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.4...surefire-3.5.5) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-version: 3.5.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2445a1e920a8..96deca50fea8 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ maven-surefire-plugin - 3.5.4 + 3.5.5 From 7e5d9d469d8d2deb7309a1003dafdeb884c7dd84 Mon Sep 17 00:00:00 2001 From: Chahat Sandhu Date: Thu, 26 Feb 2026 05:27:11 -0600 Subject: [PATCH 25/68] feat: add HuffmanCoding with fail-fast validation and immutable design (#7289) --- .../compression/HuffmanCoding.java | 253 ++++++++++++++++++ .../compression/HuffmanCodingTest.java | 110 ++++++++ 2 files changed, 363 insertions(+) create mode 100644 src/main/java/com/thealgorithms/compression/HuffmanCoding.java create mode 100644 src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java diff --git a/src/main/java/com/thealgorithms/compression/HuffmanCoding.java b/src/main/java/com/thealgorithms/compression/HuffmanCoding.java new file mode 100644 index 000000000000..d7f9d58d2429 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/HuffmanCoding.java @@ -0,0 +1,253 @@ +package com.thealgorithms.compression; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * Huffman Coding Compression Algorithm Implementation. + *

+ * Huffman Coding is a popular greedy algorithm used for lossless data compression. + * It reduces the overall size of data by assigning variable-length, prefix-free + * binary codes to input characters, ensuring that more frequent characters receive + * the shortest possible codes. + *

+ *

+ * Key Features: + *

    + *
  • Uses a PriorityQueue (min-heap) to efficiently construct the optimal prefix tree.
  • + *
  • Fail-fast design throws exceptions for unsupported characters and malformed binary payloads.
  • + *
  • Immutable internal dictionary state prevents external tampering with generated codes.
  • + *
  • Robust handling of edge cases, including single-character strings and incomplete sequences.
  • + *
+ *

+ * @author Chahat Sandhu, singhc7 + * @see Huffman Coding (Wikipedia) + */ +public class HuffmanCoding { + + private Node root; + private final Map huffmanCodes; + + /** + * Represents a node within the Huffman Tree. + * Implements {@link Comparable} to allow sorting by frequency in a PriorityQueue. + */ + private static class Node implements Comparable { + final char ch; + final int freq; + final Node left; + final Node right; + + /** + * Constructs a leaf node containing a specific character and its frequency. + * + * @param ch The character stored in this leaf. + * @param freq The frequency of occurrence of the character. + */ + Node(char ch, int freq) { + this.ch = ch; + this.freq = freq; + this.left = null; + this.right = null; + } + + /** + * Constructs an internal node that merges two child nodes. + * The character is defaulted to the null character ('\0'). + * + * @param freq The combined frequency of the left and right child nodes. + * @param left The left child node. + * @param right The right child node. + */ + Node(int freq, Node left, Node right) { + this.ch = '\0'; + this.freq = freq; + this.left = left; + this.right = right; + } + + /** + * Determines if the current node is a leaf (contains no children). + * + * @return {@code true} if both left and right children are null, {@code false} otherwise. + */ + boolean isLeaf() { + return left == null && right == null; + } + + /** + * Compares this node with another node based on their frequencies. + * Used by the PriorityQueue to maintain the min-heap property. + * + * @param other The other Node to compare against. + * @return A negative integer, zero, or a positive integer as this node's frequency + * is less than, equal to, or greater than the specified node's frequency. + */ + @Override + public int compareTo(Node other) { + return Integer.compare(this.freq, other.freq); + } + } + + /** + * Initializes the Huffman Tree and generates immutable prefix-free codes + * based on the character frequencies in the provided text. + * + * @param text The input string used to calculate frequencies and build the optimal tree. + * If null or empty, an empty tree and dictionary are created. + */ + public HuffmanCoding(String text) { + if (text == null || text.isEmpty()) { + this.huffmanCodes = Collections.emptyMap(); + return; + } + + Map tempCodes = new HashMap<>(); + buildTree(text); + generateCodes(root, "", tempCodes); + + if (tempCodes.size() == 1) { + tempCodes.put(root.ch, "0"); + } + + this.huffmanCodes = Collections.unmodifiableMap(tempCodes); + } + + /** + * Computes character frequencies and constructs the Huffman Tree using a min-heap. + * The optimal tree is built by repeatedly extracting the two lowest-frequency nodes + * and merging them until a single root node remains. + * + * @param text The input text to analyze. + */ + private void buildTree(String text) { + Map freqMap = new HashMap<>(); + for (char c : text.toCharArray()) { + freqMap.put(c, freqMap.getOrDefault(c, 0) + 1); + } + + PriorityQueue pq = new PriorityQueue<>(); + for (Map.Entry entry : freqMap.entrySet()) { + pq.add(new Node(entry.getKey(), entry.getValue())); + } + + while (pq.size() > 1) { + Node left = pq.poll(); + Node right = pq.poll(); + pq.add(new Node(left.freq + right.freq, left, right)); + } + + root = pq.poll(); + } + + /** + * Recursively traverses the Huffman Tree to generate prefix-free binary codes. + * Left traversals append a '0' to the code, while right traversals append a '1'. + * + * @param node The current node in the traversal. + * @param code The accumulated binary string for the current path. + * @param map The temporary dictionary to populate with the final character-to-code mappings. + */ + private void generateCodes(Node node, String code, Map map) { + if (node == null) { + return; + } + if (node.isLeaf()) { + map.put(node.ch, code); + return; + } + generateCodes(node.left, code + "0", map); + generateCodes(node.right, code + "1", map); + } + + /** + * Encodes the given plaintext string into a binary string using the generated Huffman dictionary. + * + * @param text The plaintext string to compress. + * @return A string of '0's and '1's representing the compressed data. + * Returns an empty string if the input is null or empty. + * @throws IllegalStateException If attempting to encode when the Huffman tree is empty. + * @throws IllegalArgumentException If the input text contains a character not present + * in the original text used to build the tree. + */ + public String encode(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + if (root == null) { + throw new IllegalStateException("Huffman tree is empty."); + } + + StringBuilder sb = new StringBuilder(); + for (char c : text.toCharArray()) { + if (!huffmanCodes.containsKey(c)) { + throw new IllegalArgumentException(String.format("Character '%c' (U+%04X) not found in Huffman dictionary.", c, (int) c)); + } + sb.append(huffmanCodes.get(c)); + } + return sb.toString(); + } + + /** + * Decodes the given binary string back into the original plaintext using the Huffman Tree. + * Validates the integrity of the binary payload during traversal. + * + * @param encodedText The binary string of '0's and '1's to decompress. + * @return The reconstructed plaintext string. Returns an empty string if the input is null or empty. + * @throws IllegalStateException If attempting to decode when the Huffman tree is empty. + * @throws IllegalArgumentException If the binary string contains characters other than '0' or '1', + * or if the sequence ends abruptly without reaching a leaf node. + */ + public String decode(String encodedText) { + if (encodedText == null || encodedText.isEmpty()) { + return ""; + } + if (root == null) { + throw new IllegalStateException("Huffman tree is empty."); + } + + StringBuilder sb = new StringBuilder(); + + if (root.isLeaf()) { + for (char bit : encodedText.toCharArray()) { + if (bit != '0') { + throw new IllegalArgumentException("Invalid binary sequence for single-character tree."); + } + sb.append(root.ch); + } + return sb.toString(); + } + + Node current = root; + for (char bit : encodedText.toCharArray()) { + if (bit != '0' && bit != '1') { + throw new IllegalArgumentException("Encoded text contains invalid characters: " + bit); + } + + current = (bit == '0') ? current.left : current.right; + + if (current.isLeaf()) { + sb.append(current.ch); + current = root; + } + } + + if (current != root) { + throw new IllegalArgumentException("Malformed encoded string: incomplete sequence ending."); + } + + return sb.toString(); + } + + /** + * Retrieves the generated Huffman dictionary mapping characters to their binary codes. + * + * @return An unmodifiable map containing the character-to-binary-code mappings to prevent + * external mutation of the algorithm's state. + */ + public Map getHuffmanCodes() { + return huffmanCodes; + } +} diff --git a/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java new file mode 100644 index 000000000000..f919417899db --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java @@ -0,0 +1,110 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class HuffmanCodingTest { + + @Test + void testStandardLifecycle() { + String input = "efficiency is key"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertNotNull(encoded); + assertTrue(encoded.matches("[01]+")); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testNullAndEmptyHandling() { + HuffmanCoding huffman = new HuffmanCoding(""); + assertEquals("", huffman.encode("")); + assertEquals("", huffman.decode("")); + + HuffmanCoding huffmanNull = new HuffmanCoding(null); + assertEquals("", huffmanNull.encode(null)); + assertEquals("", huffmanNull.decode(null)); + } + + @Test + void testSingleCharacterEdgeCase() { + String input = "aaaaa"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertEquals("00000", encoded); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testUnicodeAndSpecialCharacters() { + // Tests spacing, symbols, non-latin alphabets, and surrogate pairs (emojis) + String input = "Hello, World! πŸš€\nLine 2: こんにけは"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testFailFastOnUnseenCharacter() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> huffman.encode("abcd") // 'd' was not in the original tree + ); + assertTrue(exception.getMessage().contains("not found in Huffman dictionary")); + } + + @Test + void testFailFastOnInvalidBinaryCharacter() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + String encoded = huffman.encode("abc"); + + // Inject a '2' into the binary stream + String corruptedEncoded = encoded + "2"; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(corruptedEncoded)); + assertTrue(exception.getMessage().contains("contains invalid characters")); + } + + @Test + void testFailFastOnIncompleteSequence() { + HuffmanCoding huffman = new HuffmanCoding("abcd"); + String encoded = huffman.encode("abc"); + + // Truncate the last bit to simulate an incomplete byte/sequence transfer + String truncatedEncoded = encoded.substring(0, encoded.length() - 1); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(truncatedEncoded)); + assertTrue(exception.getMessage().contains("incomplete sequence")); + } + + @Test + void testImmutabilityOfDictionary() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + var codes = huffman.getHuffmanCodes(); + + assertThrows(UnsupportedOperationException.class, () -> codes.put('z', "0101")); + } + + @Test + void testStressVolume() { + StringBuilder sb = new StringBuilder(); + // Generate a 100,000 character string + for (int i = 0; i < 100000; i++) { + sb.append((char) ('a' + (i % 26))); + } + String largeInput = sb.toString(); + + HuffmanCoding huffman = new HuffmanCoding(largeInput); + String encoded = huffman.encode(largeInput); + + assertEquals(largeInput, huffman.decode(encoded)); + } +} From 705eb52833f50e88b1f92d309ac40f770fa56b10 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:35:42 +0200 Subject: [PATCH 26/68] Added volume of a pyramid frustum (#7291) * Added volume of a pyramid frustum Added a function calculating volume of a pyramid frustum, V=(S1+S2+sqrt(S1*S2))*h/3 * compiler error fixed * Added pyramid frustum test case * extra space removed --- src/main/java/com/thealgorithms/maths/Volume.java | 12 ++++++++++++ .../java/com/thealgorithms/maths/VolumeTest.java | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/com/thealgorithms/maths/Volume.java b/src/main/java/com/thealgorithms/maths/Volume.java index 0f282b2abae2..89b0595912b9 100644 --- a/src/main/java/com/thealgorithms/maths/Volume.java +++ b/src/main/java/com/thealgorithms/maths/Volume.java @@ -102,4 +102,16 @@ public static double volumePyramid(double baseArea, double height) { public static double volumeFrustumOfCone(double r1, double r2, double height) { return (Math.PI * height / 3) * (r1 * r1 + r2 * r2 + r1 * r2); } + + /** + * Calculate the volume of a frustum of a pyramid. + * + * @param upperBaseArea area of the upper base + * @param lowerBaseArea area of the lower base + * @param height height of the frustum + * @return volume of the frustum + */ + public static double volumeFrustumOfPyramid(double upperBaseArea, double lowerBaseArea, double height) { + return (upperBaseArea + lowerBaseArea + Math.sqrt(upperBaseArea * lowerBaseArea)) * height / 3; + } } diff --git a/src/test/java/com/thealgorithms/maths/VolumeTest.java b/src/test/java/com/thealgorithms/maths/VolumeTest.java index af882eef7563..cf72d7084e75 100644 --- a/src/test/java/com/thealgorithms/maths/VolumeTest.java +++ b/src/test/java/com/thealgorithms/maths/VolumeTest.java @@ -35,5 +35,8 @@ public void volume() { /* test frustum */ assertEquals(359.188760060433, Volume.volumeFrustumOfCone(3, 5, 7)); + + /* test pyramid frustum */ + assertEquals(140.0, Volume.volumeFrustumOfPyramid(6, 24, 10)); } } From ba286b24d66972150547fc6683eaa1925743e9d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:52:50 +0100 Subject: [PATCH 27/68] chore(deps-dev): bump org.mockito:mockito-core from 5.21.0 to 5.22.0 (#7292) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.21.0 to 5.22.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.21.0...v5.22.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-version: 5.22.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96deca50fea8..7c614f31e52e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.mockito mockito-core - 5.21.0 + 5.22.0 test From 0d2a98e9f87dc7bd5dde732a8d34a6bcaef43400 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:08:50 +0200 Subject: [PATCH 28/68] Added volume of a torus (#7294) * Added volume of a torus Added function calculation the volume of a torus according to the formula: V = 2 * pi^2 * R * r^2 where R is the major radius and r is the minor radius of the torus. * Added test for torus volume --- src/main/java/com/thealgorithms/maths/Volume.java | 11 +++++++++++ src/test/java/com/thealgorithms/maths/VolumeTest.java | 3 +++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/thealgorithms/maths/Volume.java b/src/main/java/com/thealgorithms/maths/Volume.java index 89b0595912b9..c0898c5424a0 100644 --- a/src/main/java/com/thealgorithms/maths/Volume.java +++ b/src/main/java/com/thealgorithms/maths/Volume.java @@ -114,4 +114,15 @@ public static double volumeFrustumOfCone(double r1, double r2, double height) { public static double volumeFrustumOfPyramid(double upperBaseArea, double lowerBaseArea, double height) { return (upperBaseArea + lowerBaseArea + Math.sqrt(upperBaseArea * lowerBaseArea)) * height / 3; } + + /** + * Calculate the volume of a torus. + * + * @param majorRadius major radius of a torus + * @param minorRadius minor radius of a torus + * @return volume of the torus + */ + public static double volumeTorus(double majorRadius, double minorRadius) { + return 2 * Math.PI * Math.PI * majorRadius * minorRadius * minorRadius; + } } diff --git a/src/test/java/com/thealgorithms/maths/VolumeTest.java b/src/test/java/com/thealgorithms/maths/VolumeTest.java index cf72d7084e75..c159d7566b46 100644 --- a/src/test/java/com/thealgorithms/maths/VolumeTest.java +++ b/src/test/java/com/thealgorithms/maths/VolumeTest.java @@ -38,5 +38,8 @@ public void volume() { /* test pyramid frustum */ assertEquals(140.0, Volume.volumeFrustumOfPyramid(6, 24, 10)); + + /* test torus */ + assertEquals(39.47841760435743, Volume.volumeTorus(2, 1)); } } From d8672882bfebc8cf85f184da461880c84aa7cb92 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:13:19 +0200 Subject: [PATCH 29/68] Added surface area of a cuboid (#7293) * Added surface area of a cuboid Added surface area of a cuboid according to the formula: S = 2 * (ab + ac + bc) * Removed extra white space * Added test for cuboid surface area * fixed syntax error * Removed extra space * Added tests for cuboid surface area that should fail I have added tests for cuboid surface area where one of the parameters is invalid. These should fail. --- .../java/com/thealgorithms/maths/Area.java | 21 +++++++++++++++++++ .../com/thealgorithms/maths/AreaTest.java | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java index 08807580cb03..84fc67159379 100644 --- a/src/main/java/com/thealgorithms/maths/Area.java +++ b/src/main/java/com/thealgorithms/maths/Area.java @@ -35,6 +35,27 @@ public static double surfaceAreaCube(final double sideLength) { return 6 * sideLength * sideLength; } + /** + * Calculate the surface area of a cuboid. + * + * @param length length of the cuboid + * @param width width of the cuboid + * @param height height of the cuboid + * @return surface area of given cuboid + */ + public static double surfaceAreaCuboid(final double length, double width, double height) { + if (length <= 0) { + throw new IllegalArgumentException("Length must be greater than 0"); + } + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0"); + } + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0"); + } + return 2 * (length * width + length * height + width * height); + } + /** * Calculate the surface area of a sphere. * diff --git a/src/test/java/com/thealgorithms/maths/AreaTest.java b/src/test/java/com/thealgorithms/maths/AreaTest.java index b28afb85fbc3..1c2fe53ff3f3 100644 --- a/src/test/java/com/thealgorithms/maths/AreaTest.java +++ b/src/test/java/com/thealgorithms/maths/AreaTest.java @@ -16,6 +16,11 @@ void testSurfaceAreaCube() { assertEquals(6.0, Area.surfaceAreaCube(1)); } + @Test + void testSurfaceAreaCuboid() { + assertEquals(214.0, Area.surfaceAreaCuboid(5, 6, 7)); + } + @Test void testSurfaceAreaSphere() { assertEquals(12.566370614359172, Area.surfaceAreaSphere(1)); @@ -70,6 +75,12 @@ void surfaceAreaCone() { void testAllIllegalInput() { assertAll(() -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCube(0)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(0, 1, 2)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 0, 2)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 2, 0)), () -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaSphere(0)), () From 4b04ad4a836ad87d6d4adf3bf395c0aade96bb07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:56:39 +0100 Subject: [PATCH 30/68] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.2.0 to 13.3.0 (#7295) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.2.0 to 13.3.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.2.0...checkstyle-13.3.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.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> Co-authored-by: a <19151554+alxkm@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7c614f31e52e..b2192fb9a64a 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.2.0 + 13.3.0
From 9875648a835ac49b9502c0b6c4816780b4f0462b Mon Sep 17 00:00:00 2001 From: Alex Tumanov Date: Thu, 5 Mar 2026 07:34:33 -0600 Subject: [PATCH 31/68] feat: add TopKFrequentWords with deterministic tie-breaking (#7298) (#7297) * feat: add TopKFrequentWords with deterministic tie-breaking * style: format TopKFrequentWords files with clang-format --- .../strings/TopKFrequentWords.java | 56 +++++++++++++++++++ .../strings/TopKFrequentWordsTest.java | 34 +++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/TopKFrequentWords.java create mode 100644 src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java diff --git a/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java new file mode 100644 index 000000000000..106de304cf40 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java @@ -0,0 +1,56 @@ +package com.thealgorithms.strings; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class to find the top-k most frequent words. + * + *

Words are ranked by frequency in descending order. For equal frequencies, + * words are ranked in lexicographical ascending order. + * + *

Reference: + * https://en.wikipedia.org/wiki/Top-k_problem + * + */ +public final class TopKFrequentWords { + private TopKFrequentWords() { + } + + /** + * Finds the k most frequent words. + * + * @param words input array of words + * @param k number of words to return + * @return list of top-k words ordered by frequency then lexicographical order + * @throws IllegalArgumentException if words is null, k is negative, or words contains null + */ + public static List findTopKFrequentWords(String[] words, int k) { + if (words == null) { + throw new IllegalArgumentException("Input words array cannot be null."); + } + if (k < 0) { + throw new IllegalArgumentException("k cannot be negative."); + } + if (k == 0 || words.length == 0) { + return List.of(); + } + + Map frequency = new HashMap<>(); + for (String word : words) { + if (word == null) { + throw new IllegalArgumentException("Input words cannot contain null values."); + } + frequency.put(word, frequency.getOrDefault(word, 0) + 1); + } + + List candidates = new ArrayList<>(frequency.keySet()); + candidates.sort(Comparator.comparingInt(frequency::get).reversed().thenComparing(Comparator.naturalOrder())); + + int limit = Math.min(k, candidates.size()); + return new ArrayList<>(candidates.subList(0, limit)); + } +} diff --git a/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java new file mode 100644 index 000000000000..42b2d04ff265 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class TopKFrequentWordsTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testFindTopKFrequentWords(String[] words, int k, List expected) { + assertEquals(expected, TopKFrequentWords.findTopKFrequentWords(words, k)); + } + + static Stream validTestCases() { + return Stream.of(Arguments.of(new String[] {"i", "love", "leetcode", "i", "love", "coding"}, 2, List.of("i", "love")), Arguments.of(new String[] {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4, List.of("the", "is", "sunny", "day")), + Arguments.of(new String[] {"bbb", "aaa", "bbb", "aaa", "ccc"}, 2, List.of("aaa", "bbb")), Arguments.of(new String[] {"one", "two", "three"}, 10, List.of("one", "three", "two")), Arguments.of(new String[] {}, 3, List.of()), Arguments.of(new String[] {"x", "x", "y"}, 0, List.of())); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testFindTopKFrequentWordsInvalidInput(String[] words, int k) { + assertThrows(IllegalArgumentException.class, () -> TopKFrequentWords.findTopKFrequentWords(words, k)); + } + + static Stream invalidTestCases() { + return Stream.of(Arguments.of((String[]) null, 1), Arguments.of(new String[] {"a", null, "b"}, 2), Arguments.of(new String[] {"a"}, -1)); + } +} From 8b41533d004babdd641ad70f80e50cf6c1746ebe Mon Sep 17 00:00:00 2001 From: Kakkirala Reshma <24wh1a05q3@bvrithyderabad.edu.in> Date: Fri, 6 Mar 2026 19:41:57 +0530 Subject: [PATCH 32/68] Add Javadoc comments for AnyBaseToAnyBase class (#7301) Added Javadoc comments to describe the algorithm and its complexities. --- .../com/thealgorithms/conversions/AnyBaseToAnyBase.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java index 7a9448fd8fe7..7698cc832981 100644 --- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java +++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java @@ -1,3 +1,10 @@ +/** + * [Brief description of what the algorithm does] + *

+ * Time Complexity: O(n) [or appropriate complexity] + * Space Complexity: O(n) + * * @author Reshma Kakkirala + */ package com.thealgorithms.conversions; import java.util.Arrays; From 9aa2544f61566c5e715d56e1a76e51692c26eb37 Mon Sep 17 00:00:00 2001 From: Debojeet Bhattacharya Date: Sun, 8 Mar 2026 09:27:33 -0500 Subject: [PATCH 33/68] Renamed method variables (#7296) * renamed method variables for improved readibility in the sort method of InsertionSort * Renamed method in PancackeSort. * Renamed array "aux" to "tempArray" --------- Co-authored-by: Deniz Altunkapan --- .../thealgorithms/sorts/InsertionSort.java | 30 +++++++++---------- .../com/thealgorithms/sorts/MergeSort.java | 16 +++++----- .../com/thealgorithms/sorts/PancakeSort.java | 4 +-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/thealgorithms/sorts/InsertionSort.java b/src/main/java/com/thealgorithms/sorts/InsertionSort.java index fdbfd9cd1cfa..1e42f2a61271 100644 --- a/src/main/java/com/thealgorithms/sorts/InsertionSort.java +++ b/src/main/java/com/thealgorithms/sorts/InsertionSort.java @@ -33,30 +33,30 @@ public > T[] sort(T[] array) { } /** - * Sorts a subarray of the given array using the standard Insertion Sort algorithm. + * Sorts a subarray of the given items using the standard Insertion Sort algorithm. * - * @param array The array to be sorted - * @param lo The starting index of the subarray - * @param hi The ending index of the subarray (exclusive) - * @param The type of elements in the array, which must be comparable - * @return The sorted array + * @param items The items to be sorted + * @param startIndex The starting index of the subarray + * @param endIndex The ending index of the subarray (exclusive) + * @param The type of elements in the items, which must be comparable + * @return The sorted items */ - public > T[] sort(T[] array, final int lo, final int hi) { - if (array == null || lo >= hi) { - return array; + public > T[] sort(T[] items, final int startIndex, final int endIndex) { + if (items == null || startIndex >= endIndex) { + return items; } - for (int i = lo + 1; i < hi; i++) { - final T key = array[i]; + for (int i = startIndex + 1; i < endIndex; i++) { + final T key = items[i]; int j = i - 1; - while (j >= lo && SortUtils.less(key, array[j])) { - array[j + 1] = array[j]; + while (j >= startIndex && SortUtils.less(key, items[j])) { + items[j + 1] = items[j]; j--; } - array[j + 1] = key; + items[j + 1] = key; } - return array; + return items; } /** diff --git a/src/main/java/com/thealgorithms/sorts/MergeSort.java b/src/main/java/com/thealgorithms/sorts/MergeSort.java index f7a7c8da004d..5db9c48b4f61 100644 --- a/src/main/java/com/thealgorithms/sorts/MergeSort.java +++ b/src/main/java/com/thealgorithms/sorts/MergeSort.java @@ -10,7 +10,7 @@ @SuppressWarnings("rawtypes") class MergeSort implements SortAlgorithm { - private Comparable[] aux; + private Comparable[] tempArray; /** * Generic merge sort algorithm. @@ -26,7 +26,7 @@ class MergeSort implements SortAlgorithm { */ @Override public > T[] sort(T[] unsorted) { - aux = new Comparable[unsorted.length]; + tempArray = new Comparable[unsorted.length]; doSort(unsorted, 0, unsorted.length - 1); return unsorted; } @@ -58,17 +58,17 @@ private > void doSort(T[] arr, int left, int right) { private > void merge(T[] arr, int left, int mid, int right) { int i = left; int j = mid + 1; - System.arraycopy(arr, left, aux, left, right + 1 - left); + System.arraycopy(arr, left, tempArray, left, right + 1 - left); for (int k = left; k <= right; k++) { if (j > right) { - arr[k] = (T) aux[i++]; + arr[k] = (T) tempArray[i++]; } else if (i > mid) { - arr[k] = (T) aux[j++]; - } else if (less(aux[j], aux[i])) { - arr[k] = (T) aux[j++]; + arr[k] = (T) tempArray[j++]; + } else if (less(tempArray[j], tempArray[i])) { + arr[k] = (T) tempArray[j++]; } else { - arr[k] = (T) aux[i++]; + arr[k] = (T) tempArray[i++]; } } } diff --git a/src/main/java/com/thealgorithms/sorts/PancakeSort.java b/src/main/java/com/thealgorithms/sorts/PancakeSort.java index 6079672a1d77..6522aefd7ae3 100644 --- a/src/main/java/com/thealgorithms/sorts/PancakeSort.java +++ b/src/main/java/com/thealgorithms/sorts/PancakeSort.java @@ -15,7 +15,7 @@ public > T[] sort(T[] array) { } for (int currentSize = 0; currentSize < array.length; currentSize++) { - int maxIndex = findMaxIndex(array, currentSize); + int maxIndex = findIndexOfMax(array, currentSize); SortUtils.flip(array, maxIndex, array.length - 1 - currentSize); } @@ -30,7 +30,7 @@ public > T[] sort(T[] array) { * @param the type of elements in the array * @return the index of the maximum element */ - private > int findMaxIndex(T[] array, int currentSize) { + private > int findIndexOfMax(T[] array, int currentSize) { T max = array[0]; int maxIndex = 0; for (int i = 0; i < array.length - currentSize; i++) { From 8e1f12447c23b0ac9179e76e4adef6b75ecc10e7 Mon Sep 17 00:00:00 2001 From: lmj798 <2757400745@qq.com> Date: Wed, 11 Mar 2026 21:31:15 +0800 Subject: [PATCH 34/68] test: enhance GenericHashMapUsingArrayTest with comprehensive edge case coverage (#7300) * test: enhance GenericHashMapUsingArrayTest with additional edge case coverage * Removed unused assertion 'assertNotEquals' from imports. * Simplify import statements in GenericHashMapUsingArrayTest * Refactor assertions to use Assertions class * Refactor null key test to use variable --- .../hashing/GenericHashMapUsingArrayTest.java | 190 +++++++++++++++--- 1 file changed, 164 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java index 5d1733a3e97c..6b6e670a258b 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java @@ -1,11 +1,9 @@ package com.thealgorithms.datastructures.hashmap.hashing; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class GenericHashMapUsingArrayTest { @@ -16,10 +14,10 @@ void testGenericHashmapWhichUsesArrayAndBothKeyAndValueAreStrings() { map.put("Nepal", "Kathmandu"); map.put("India", "New Delhi"); map.put("Australia", "Sydney"); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals("Kathmandu", map.get("Nepal")); - assertEquals("Sydney", map.get("Australia")); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals("Kathmandu", map.get("Nepal")); + Assertions.assertEquals("Sydney", map.get("Australia")); } @Test @@ -29,12 +27,12 @@ void testGenericHashmapWhichUsesArrayAndKeyIsStringValueIsInteger() { map.put("Nepal", 25); map.put("India", 101); map.put("Australia", 99); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals(25, map.get("Nepal")); - assertEquals(99, map.get("Australia")); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals(25, map.get("Nepal")); + Assertions.assertEquals(99, map.get("Australia")); map.remove("Nepal"); - assertFalse(map.containsKey("Nepal")); + Assertions.assertFalse(map.containsKey("Nepal")); } @Test @@ -44,11 +42,11 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() { map.put(34, "Kathmandu"); map.put(46, "New Delhi"); map.put(89, "Sydney"); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals("Sydney", map.get(89)); - assertEquals("Washington DC", map.get(101)); - assertTrue(map.containsKey(46)); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals("Sydney", map.get(89)); + Assertions.assertEquals("Washington DC", map.get(101)); + Assertions.assertTrue(map.containsKey(46)); } @Test @@ -56,7 +54,7 @@ void testRemoveNonExistentKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); map.remove("Nepal"); // Attempting to remove a non-existent key - assertEquals(1, map.size()); // Size should remain the same + Assertions.assertEquals(1, map.size()); // Size should remain the same } @Test @@ -65,8 +63,8 @@ void testRehashing() { for (int i = 0; i < 20; i++) { map.put("Key" + i, "Value" + i); } - assertEquals(20, map.size()); // Ensure all items were added - assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash + Assertions.assertEquals(20, map.size()); // Ensure all items were added + Assertions.assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash } @Test @@ -74,7 +72,7 @@ void testUpdateValueForExistingKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); map.put("USA", "New Washington DC"); // Updating value for existing key - assertEquals("New Washington DC", map.get("USA")); + Assertions.assertEquals("New Washington DC", map.get("USA")); } @Test @@ -83,14 +81,154 @@ void testToStringMethod() { map.put("USA", "Washington DC"); map.put("Nepal", "Kathmandu"); String expected = "{USA : Washington DC, Nepal : Kathmandu}"; - assertEquals(expected, map.toString()); + Assertions.assertEquals(expected, map.toString()); } @Test void testContainsKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); - assertTrue(map.containsKey("USA")); - assertFalse(map.containsKey("Nepal")); + Assertions.assertTrue(map.containsKey("USA")); + Assertions.assertFalse(map.containsKey("Nepal")); + } + + // ======= Added tests from the new version ======= + + @Test + void shouldThrowNullPointerExceptionForNullKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + String nullKey = null; // Use variable to avoid static analysis false positive + Assertions.assertThrows(NullPointerException.class, () -> map.put(nullKey, "value")); + } + + @Test + void shouldStoreNullValueForKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("keyWithNullValue", null); + Assertions.assertEquals(1, map.size()); + Assertions.assertNull(map.get("keyWithNullValue")); + // Note: containsKey returns false for null values due to implementation + Assertions.assertFalse(map.containsKey("keyWithNullValue")); + } + + @Test + void shouldHandleCollisionWhenKeysHashToSameBucket() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + Integer key1 = 1; + Integer key2 = 17; + map.put(key1, 100); + map.put(key2, 200); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(key1)); + Assertions.assertEquals(200, map.get(key2)); + Assertions.assertTrue(map.containsKey(key1)); + Assertions.assertTrue(map.containsKey(key2)); + } + + @Test + void shouldHandleEmptyStringAsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("", "valueForEmptyKey"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("valueForEmptyKey", map.get("")); + Assertions.assertTrue(map.containsKey("")); + } + + @Test + void shouldHandleEmptyStringAsValue() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("keyForEmptyValue", ""); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("", map.get("keyForEmptyValue")); + Assertions.assertTrue(map.containsKey("keyForEmptyValue")); + } + + @Test + void shouldHandleNegativeIntegerKeys() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(-1, 100); + map.put(-100, 200); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(-1)); + Assertions.assertEquals(200, map.get(-100)); + Assertions.assertTrue(map.containsKey(-1)); + Assertions.assertTrue(map.containsKey(-100)); + } + + @Test + void shouldHandleZeroAsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(0, 100); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals(100, map.get(0)); + Assertions.assertTrue(map.containsKey(0)); + } + + @Test + void shouldHandleStringWithSpecialCharacters() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("key!@#$%^&*()", "value<>?/\\|"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("value<>?/\\|", map.get("key!@#$%^&*()")); + Assertions.assertTrue(map.containsKey("key!@#$%^&*()")); + } + + @Test + void shouldHandleLongStrings() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + StringBuilder longKey = new StringBuilder(); + StringBuilder longValue = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + longKey.append("a"); + longValue.append("b"); + } + String key = longKey.toString(); + String value = longValue.toString(); + map.put(key, value); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals(value, map.get(key)); + Assertions.assertTrue(map.containsKey(key)); + } + + @ParameterizedTest + @ValueSource(strings = {"a", "ab", "abc", "test", "longerString"}) + void shouldHandleKeysOfDifferentLengths(String key) { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(key, "value"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("value", map.get(key)); + Assertions.assertTrue(map.containsKey(key)); + } + + @Test + void shouldHandleUpdateOnExistingKeyInCollisionBucket() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + Integer key1 = 1; + Integer key2 = 17; + map.put(key1, 100); + map.put(key2, 200); + Assertions.assertEquals(2, map.size()); + map.put(key2, 999); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(key1)); + Assertions.assertEquals(999, map.get(key2)); + Assertions.assertTrue(map.containsKey(key1)); + Assertions.assertTrue(map.containsKey(key2)); + } + + @Test + void shouldHandleExactlyLoadFactorBoundary() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + // Fill exactly to load factor (12 items with capacity 16 and 0.75 load factor) + for (int i = 0; i < 12; i++) { + map.put(i, i * 10); + } + Assertions.assertEquals(12, map.size()); + // Act - This should trigger rehash on 13th item + map.put(12, 120); + // Assert - Rehash should have happened + Assertions.assertEquals(13, map.size()); + Assertions.assertEquals(120, map.get(12)); + Assertions.assertTrue(map.containsKey(12)); } } From 5e06b1592638ac0826258341398f92537717eac3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:12:37 +0100 Subject: [PATCH 35/68] chore(deps-dev): bump org.mockito:mockito-core from 5.22.0 to 5.23.0 (#7305) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.22.0 to 5.23.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.22.0...v5.23.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-version: 5.23.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b2192fb9a64a..dab7447430e5 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.mockito mockito-core - 5.22.0 + 5.23.0 test From 8bbd090e0cc7cf0dc9b2e3e74f9a33ca8dc297e6 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Sat, 14 Mar 2026 23:09:19 +0200 Subject: [PATCH 36/68] Overlapping condition changed (#7314) Overlapping happens not when centers of circular bodies are in the same point, but when the distance between them is smaller than the sum of their radii. --- src/main/java/com/thealgorithms/physics/ElasticCollision2D.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java index 399c3f1e041f..d096e0a8d7cd 100644 --- a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java +++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java @@ -41,7 +41,7 @@ public static void resolveCollision(Body a, Body b) { double dy = b.y - a.y; double dist = Math.hypot(dx, dy); - if (dist == 0) { + if (dist < a.radius + b.radius) { return; // overlapping } From 24c2beae463b7b1f8c2616110911449589af482b Mon Sep 17 00:00:00 2001 From: Maryam Hazrati <117775713+Maryamh12@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:15:50 +0000 Subject: [PATCH 37/68] Improve space complexity to O(1) in WordSearch. (#7308) * Modifying space complexity to O(1). * Fix formatting using clang-format. * Fix checkstyle violations. * Fix checkstyle violations and code formatting. * Remove unused fields reported by SpotBugs. * Remove unused fields and comments. * Remove unused field reported by SpotBugs. * Fix PMD collapsible if statement. * Fix indentation to satisfy clang-format. --------- Co-authored-by: Deniz Altunkapan --- .../backtracking/WordSearch.java | 66 +++++++------------ 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/WordSearch.java b/src/main/java/com/thealgorithms/backtracking/WordSearch.java index 174ca90ccaab..452f17b6ace6 100644 --- a/src/main/java/com/thealgorithms/backtracking/WordSearch.java +++ b/src/main/java/com/thealgorithms/backtracking/WordSearch.java @@ -35,22 +35,6 @@ * - Stack space for the recursive DFS function, where L is the maximum depth of recursion (length of the word). */ public class WordSearch { - private final int[] dx = {0, 0, 1, -1}; - private final int[] dy = {1, -1, 0, 0}; - private boolean[][] visited; - private char[][] board; - private String word; - - /** - * Checks if the given (x, y) coordinates are valid positions in the board. - * - * @param x The row index. - * @param y The column index. - * @return True if the coordinates are within the bounds of the board; false otherwise. - */ - private boolean isValid(int x, int y) { - return x >= 0 && x < board.length && y >= 0 && y < board[0].length; - } /** * Performs Depth First Search (DFS) from the cell (x, y) @@ -58,28 +42,27 @@ private boolean isValid(int x, int y) { * * @param x The current row index. * @param y The current column index. - * @param nextIdx The index of the next character in the word to be matched. + * @param idx The index of the next character in the word to be matched. * @return True if a valid path is found to match the remaining characters of the word; false otherwise. */ - private boolean doDFS(int x, int y, int nextIdx) { - visited[x][y] = true; - if (nextIdx == word.length()) { + + private boolean dfs(char[][] board, int x, int y, String word, int idx) { + if (idx == word.length()) { return true; } - for (int i = 0; i < 4; ++i) { - int xi = x + dx[i]; - int yi = y + dy[i]; - if (isValid(xi, yi) && board[xi][yi] == word.charAt(nextIdx) && !visited[xi][yi]) { - boolean exists = doDFS(xi, yi, nextIdx + 1); - if (exists) { - return true; - } - } + if (x < 0 || y < 0 || x >= board.length || y >= board[0].length || board[x][y] != word.charAt(idx)) { + return false; } - visited[x][y] = false; // Backtrack - return false; + char temp = board[x][y]; + board[x][y] = '#'; + + boolean found = dfs(board, x + 1, y, word, idx + 1) || dfs(board, x - 1, y, word, idx + 1) || dfs(board, x, y + 1, word, idx + 1) || dfs(board, x, y - 1, word, idx + 1); + + board[x][y] = temp; + + return found; } /** @@ -90,20 +73,21 @@ private boolean doDFS(int x, int y, int nextIdx) { * @param word The target word to search for in the board. * @return True if the word exists in the board; false otherwise. */ + public boolean exist(char[][] board, String word) { - this.board = board; - this.word = word; - for (int i = 0; i < board.length; ++i) { - for (int j = 0; j < board[0].length; ++j) { - if (board[i][j] == word.charAt(0)) { - visited = new boolean[board.length][board[0].length]; - boolean exists = doDFS(i, j, 1); - if (exists) { - return true; - } + + int m = board.length; + int n = board[0].length; + + // DFS search + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == word.charAt(0) && dfs(board, i, j, word, 0)) { + return true; } } } + return false; } } From 7d57c5720670dc0fd6e37d7bdc41ad053d925f5b Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:44:31 +0200 Subject: [PATCH 38/68] Added quadratic mean (#7315) * Added quadratic mean Added a quadratic mean of the given numbers, sqrt ((n1^2+n2^2+...+nk^2)/k). * Added tests for quadratic mean * Corrected quadratic mean * Added comment to quadratic mean * Corrected quadratic mean tests * Replaced sqrt by pow * Error fixed * Extra whitespace removed * Extra whitespace removed * Removed extra white space * Removed extra white space --- .../java/com/thealgorithms/maths/Means.java | 22 ++++++++ .../com/thealgorithms/maths/MeansTest.java | 53 ++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/maths/Means.java b/src/main/java/com/thealgorithms/maths/Means.java index 5445a3caebc7..d77eb1d3f661 100644 --- a/src/main/java/com/thealgorithms/maths/Means.java +++ b/src/main/java/com/thealgorithms/maths/Means.java @@ -107,6 +107,28 @@ public static Double harmonic(final Iterable numbers) { return size / sumOfReciprocals; } + /** + * Computes the quadratic mean (root mean square) of the given numbers. + *

+ * The quadratic mean is calculated as: √[(x₁^2 Γ— xβ‚‚^2 Γ— ... Γ— xβ‚™^2)/n] + *

+ *

+ * Example: For numbers [1, 7], the quadratic mean is √[(1^2+7^2)/2] = √25 = 5.0 + *

+ * + * @param numbers the input numbers (must not be empty) + * @return the quadratic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Quadratic + * Mean + */ + public static Double quadratic(final Iterable numbers) { + checkIfNotEmpty(numbers); + double sumOfSquares = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y * y); + int size = IterableUtils.size(numbers); + return Math.pow(sumOfSquares / size, 0.5); + } + /** * Validates that the input iterable is not empty. * diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java index deee0a931910..853fdbea3963 100644 --- a/src/test/java/com/thealgorithms/maths/MeansTest.java +++ b/src/test/java/com/thealgorithms/maths/MeansTest.java @@ -172,6 +172,53 @@ void testHarmonicMeanWithLinkedList() { assertEquals(expected, Means.harmonic(numbers), EPSILON); } + // ========== Quadratic Mean Tests ========== + + @Test + void testQuadraticMeanThrowsExceptionForEmptyList() { + List numbers = new ArrayList<>(); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.quadratic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testQuadraticMeanSingleNumber() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanTwoNumbers() { + List numbers = Arrays.asList(1.0, 7.0); + assertEquals(5.0, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanMultipleNumbers() { + Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 3.0, 7.5, 10.0)); + double expected = Math.sqrt(34.5); + assertEquals(expected, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanThreeNumbers() { + List numbers = Arrays.asList(3.0, 6.0, 9.0); + double expected = Math.sqrt(42.0); + assertEquals(expected, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanIdenticalNumbers() { + List numbers = Arrays.asList(5.0, 5.0, 5.0); + assertEquals(5.0, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanWithLinkedList() { + LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 5.0, 11.0)); + assertEquals(7.0, Means.quadratic(numbers), EPSILON); + } + // ========== Additional Edge Case Tests ========== @Test @@ -198,21 +245,25 @@ void testAllMeansConsistencyForIdenticalValues() { double arithmetic = Means.arithmetic(numbers); double geometric = Means.geometric(numbers); double harmonic = Means.harmonic(numbers); + double quadratic = Means.quadratic(numbers); assertEquals(7.5, arithmetic, EPSILON); assertEquals(7.5, geometric, EPSILON); assertEquals(7.5, harmonic, EPSILON); + assertEquals(7.5, quadratic, EPSILON); } @Test void testMeansRelationship() { - // For positive numbers, harmonic mean ≀ geometric mean ≀ arithmetic mean + // For positive numbers, harmonic mean ≀ geometric mean ≀ arithmetic mean ≀ quadratic mean List numbers = Arrays.asList(2.0, 4.0, 8.0); double arithmetic = Means.arithmetic(numbers); double geometric = Means.geometric(numbers); double harmonic = Means.harmonic(numbers); + double quadratic = Means.quadratic(numbers); assertTrue(harmonic <= geometric, "Harmonic mean should be ≀ geometric mean"); assertTrue(geometric <= arithmetic, "Geometric mean should be ≀ arithmetic mean"); + assertTrue(arithmetic <= quadratic, "Arithmetic mean should be ≀ quadratic mean"); } } From af1d9d166522e3904ce60f2303d1ad9d8b462d62 Mon Sep 17 00:00:00 2001 From: Keykyrios Date: Thu, 19 Mar 2026 00:55:39 +0530 Subject: [PATCH 39/68] feat(strings): add Kasai's algorithm for LCP array construction (#7324) * feat(strings): add Kasai's algorithm for LCP array Implement Kasai's algorithm to compute the Longest Common Prefix (LCP) array in O(N) time given a string and its suffix array. Add KasaiAlgorithm.java and KasaiAlgorithmTest.java. * style(strings): fix KasaiAlgorithmTest array initialization format for clang-format --- .../thealgorithms/strings/KasaiAlgorithm.java | 79 +++++++++++++++++++ .../strings/KasaiAlgorithmTest.java | 75 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java create mode 100644 src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java diff --git a/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java new file mode 100644 index 000000000000..b8b10dcf4538 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java @@ -0,0 +1,79 @@ +package com.thealgorithms.strings; + +/** + * Kasai's Algorithm for constructing the Longest Common Prefix (LCP) array. + * + *

+ * The LCP array stores the lengths of the longest common prefixes between + * lexicographically adjacent suffixes of a string. Kasai's algorithm computes + * this array in O(N) time given the string and its suffix array. + *

+ * + * @see LCP array - Wikipedia + */ +public final class KasaiAlgorithm { + + private KasaiAlgorithm() { + } + + /** + * Computes the LCP array using Kasai's algorithm. + * + * @param text the original string + * @param suffixArr the suffix array of the string + * @return the LCP array of length N, where LCP[i] is the length of the longest + * common prefix of the suffixes indexed by suffixArr[i] and suffixArr[i+1]. + * The last element LCP[N-1] is always 0. + * @throws IllegalArgumentException if text or suffixArr is null, or their lengths differ + */ + public static int[] kasai(String text, int[] suffixArr) { + if (text == null || suffixArr == null) { + throw new IllegalArgumentException("Text and suffix array must not be null."); + } + int n = text.length(); + if (suffixArr.length != n) { + throw new IllegalArgumentException("Suffix array length must match text length."); + } + if (n == 0) { + return new int[0]; + } + + // Compute the inverse suffix array + // invSuff[i] stores the index of the suffix text.substring(i) in the suffix array + int[] invSuff = new int[n]; + for (int i = 0; i < n; i++) { + if (suffixArr[i] < 0 || suffixArr[i] >= n) { + throw new IllegalArgumentException("Suffix array contains out-of-bounds index."); + } + invSuff[suffixArr[i]] = i; + } + + int[] lcp = new int[n]; + int k = 0; // Length of the longest common prefix + + for (int i = 0; i < n; i++) { + // Suffix at index i has not a next suffix in suffix array + int rank = invSuff[i]; + if (rank == n - 1) { + k = 0; + continue; + } + + int nextSuffixIndex = suffixArr[rank + 1]; + + // Directly match characters to find LCP + while (i + k < n && nextSuffixIndex + k < n && text.charAt(i + k) == text.charAt(nextSuffixIndex + k)) { + k++; + } + + lcp[rank] = k; + + // Delete the starting character from the string + if (k > 0) { + k--; + } + } + + return lcp; + } +} diff --git a/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java new file mode 100644 index 000000000000..c22cc77df18a --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java @@ -0,0 +1,75 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class KasaiAlgorithmTest { + + @Test + public void testKasaiBanana() { + String text = "banana"; + // Suffixes: + // 0: banana + // 1: anana + // 2: nana + // 3: ana + // 4: na + // 5: a + // + // Sorted Suffixes: + // 5: a + // 3: ana + // 1: anana + // 0: banana + // 4: na + // 2: nana + int[] suffixArr = {5, 3, 1, 0, 4, 2}; + + int[] expectedLcp = {1, 3, 0, 0, 2, 0}; + + assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr)); + } + + @Test + public void testKasaiAaaa() { + String text = "aaaa"; + // Sorted Suffixes: + // 3: a + // 2: aa + // 1: aaa + // 0: aaaa + int[] suffixArr = {3, 2, 1, 0}; + int[] expectedLcp = {1, 2, 3, 0}; + + assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr)); + } + + @Test + public void testKasaiEmptyString() { + assertArrayEquals(new int[0], KasaiAlgorithm.kasai("", new int[0])); + } + + @Test + public void testKasaiSingleChar() { + assertArrayEquals(new int[] {0}, KasaiAlgorithm.kasai("A", new int[] {0})); + } + + @Test + public void testKasaiNullTextOrSuffixArray() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai(null, new int[] {0})); + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", null)); + } + + @Test + public void testKasaiInvalidSuffixArrayLength() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {0, 1})); + } + + @Test + public void testKasaiInvalidSuffixArrayIndex() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {1})); // Out of bounds + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {-1})); // Out of bounds + } +} From be0224082a6e41608a760ca44631e758f6564125 Mon Sep 17 00:00:00 2001 From: AbhiramSakha <143825001+AbhiramSakha@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:52:56 +0530 Subject: [PATCH 40/68] Improve InterpolationSearch documentation with example (#7336) docs: improve InterpolationSearch documentation --- .../thealgorithms/searches/InterpolationSearch.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index 3ac6be25bf53..d24cc1c774bc 100644 --- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java +++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java @@ -1,3 +1,14 @@ +/** + * Interpolation Search estimates the position of the target value + * based on the distribution of values. + * + * Example: + * Input: [10, 20, 30, 40], target = 30 + * Output: Index = 2 + * + * Time Complexity: O(log log n) (average case) + * Space Complexity: O(1) + */ package com.thealgorithms.searches; /** From 76c45c1874c7268a1bd0d81579daebc86366530c Mon Sep 17 00:00:00 2001 From: Alex Tumanov Date: Tue, 24 Mar 2026 06:27:23 -0500 Subject: [PATCH 41/68] feat: add StockSpanProblem algorithm (#7312) * feat: add StockSpanProblem algorithm * feat: add OptimalBinarySearchTree algorithm --- .../stacks/StockSpanProblem.java | 67 +++++++++++++++++++ .../stacks/StockSpanProblemTest.java | 34 ++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/main/java/com/thealgorithms/stacks/StockSpanProblem.java create mode 100644 src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java diff --git a/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java new file mode 100644 index 000000000000..2e9f6863c90a --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java @@ -0,0 +1,67 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Calculates the stock span for each day in a series of stock prices. + * + *

The span of a price on a given day is the number of consecutive days ending on that day + * for which the price was less than or equal to the current day's price. + * + *

Idea: keep a stack of indices whose prices are strictly greater than the current price. + * While processing each day, pop smaller or equal prices because they are part of the current + * span. After popping, the nearest greater price left on the stack tells us where the span stops. + * + *

Time complexity is O(n) because each index is pushed onto the stack once and popped at most + * once, so the total number of stack operations grows linearly with the number of prices. This + * makes the stack approach efficient because it avoids rechecking earlier days repeatedly, unlike + * a naive nested-loop solution that can take O(n^2) time. + * + *

Example: for prices [100, 80, 60, 70, 60, 75, 85], the spans are + * [1, 1, 1, 2, 1, 4, 6]. + */ +public final class StockSpanProblem { + private StockSpanProblem() { + } + + /** + * Calculates the stock span for each price in the input array. + * + * @param prices the stock prices + * @return the span for each day + * @throws IllegalArgumentException if the input array is null + */ + public static int[] calculateSpan(int[] prices) { + if (prices == null) { + throw new IllegalArgumentException("Input prices cannot be null"); + } + + int[] spans = new int[prices.length]; + Stack stack = new Stack<>(); + + // Small example: + // prices = [100, 80, 60, 70] + // spans = [ 1, 1, 1, 2] + // When we process 70, we pop 60 because 60 <= 70, so the span becomes 2. + // + // The stack stores indices of days with prices greater than the current day's price. + for (int index = 0; index < prices.length; index++) { + // Remove all previous days whose prices are less than or equal to the current price. + while (!stack.isEmpty() && prices[stack.peek()] <= prices[index]) { + stack.pop(); + } + + // If the stack is empty, there is no earlier day with a greater price, + // so the count will be from day 0 to this day (index + 1). + // + // Otherwise, the span is the number of days between + // the nearest earlier day with a greater price and the current day. + spans[index] = stack.isEmpty() ? index + 1 : index - stack.peek(); + + // Store the current index as a candidate for future span calculations. + stack.push(index); + } + + return spans; + } +} diff --git a/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java new file mode 100644 index 000000000000..2e4ea74691da --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class StockSpanProblemTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testCalculateSpan(int[] prices, int[] expectedSpans) { + assertArrayEquals(expectedSpans, StockSpanProblem.calculateSpan(prices)); + } + + private static Stream validTestCases() { + return Stream.of(Arguments.of(new int[] {10, 4, 5, 90, 120, 80}, new int[] {1, 1, 2, 4, 5, 1}), Arguments.of(new int[] {100, 50, 60, 70, 80, 90}, new int[] {1, 1, 2, 3, 4, 5}), Arguments.of(new int[] {5, 4, 3, 2, 1}, new int[] {1, 1, 1, 1, 1}), + Arguments.of(new int[] {1, 2, 3, 4, 5}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {10, 20, 30, 40, 50}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {100, 80, 60, 70, 60, 75, 85}, new int[] {1, 1, 1, 2, 1, 4, 6}), + Arguments.of(new int[] {7, 7, 7, 7}, new int[] {1, 2, 3, 4}), Arguments.of(new int[] {}, new int[] {}), Arguments.of(new int[] {42}, new int[] {1})); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testCalculateSpanInvalidInput(int[] prices) { + assertThrows(IllegalArgumentException.class, () -> StockSpanProblem.calculateSpan(prices)); + } + + private static Stream invalidTestCases() { + return Stream.of(Arguments.of((int[]) null)); + } +} From 010c9552987e2a8cbb4ff9b5e0203d12a6adf227 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:06:18 +0200 Subject: [PATCH 42/68] Implement volume calculation for ellipsoid (#7338) * Implement volume calculation for ellipsoid Added a method to calculate the volume of an ellipsoid. * Add test for volume of ellipsoid * Fix formatting of volumeEllipsoid method * Update Volume.java * Fix precision in ellipsoid volume test * Fix formatting of ellipsoid volume method documentation --- src/main/java/com/thealgorithms/maths/Volume.java | 12 ++++++++++++ .../java/com/thealgorithms/maths/VolumeTest.java | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/com/thealgorithms/maths/Volume.java b/src/main/java/com/thealgorithms/maths/Volume.java index c0898c5424a0..488b921cae83 100644 --- a/src/main/java/com/thealgorithms/maths/Volume.java +++ b/src/main/java/com/thealgorithms/maths/Volume.java @@ -125,4 +125,16 @@ public static double volumeFrustumOfPyramid(double upperBaseArea, double lowerBa public static double volumeTorus(double majorRadius, double minorRadius) { return 2 * Math.PI * Math.PI * majorRadius * minorRadius * minorRadius; } + + /** + * Calculate the volume of an ellipsoid. + * + * @param a first semi-axis of an ellipsoid + * @param b second semi-axis of an ellipsoid + * @param c third semi-axis of an ellipsoid + * @return volume of the ellipsoid + */ + public static double volumeEllipsoid(double a, double b, double c) { + return (4 * Math.PI * a * b * c) / 3; + } } diff --git a/src/test/java/com/thealgorithms/maths/VolumeTest.java b/src/test/java/com/thealgorithms/maths/VolumeTest.java index c159d7566b46..c0b02d6ba28e 100644 --- a/src/test/java/com/thealgorithms/maths/VolumeTest.java +++ b/src/test/java/com/thealgorithms/maths/VolumeTest.java @@ -41,5 +41,8 @@ public void volume() { /* test torus */ assertEquals(39.47841760435743, Volume.volumeTorus(2, 1)); + + /* test ellipsoid */ + assertEquals(25.1327412287183459, Volume.volumeEllipsoid(3, 2, 1)); } } From 29275951c5c81665fc13d1972c8f6650777a4ff5 Mon Sep 17 00:00:00 2001 From: AbhiramSakha <143825001+AbhiramSakha@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:08:08 +0530 Subject: [PATCH 43/68] Improve LinearSearch documentation with example and explanation (#7335) docs: improve LinearSearch documentation --- .../com/thealgorithms/searches/LinearSearch.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index cb483d8dfedc..6403749a3154 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -1,3 +1,16 @@ +/** + * 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 ebcf5adc598032b5c01a6a76453531142d31397d Mon Sep 17 00:00:00 2001 From: AbhiramSakha <143825001+AbhiramSakha@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:12:44 +0530 Subject: [PATCH 44/68] Fix IterativeBinarySearch implementation and resolve build issues (#7332) fix: resolve formatting and build issues in IterativeBinarySearch Co-authored-by: Deniz Altunkapan --- .../searches/IterativeBinarySearch.java | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java index 05fab0534267..cc0bfb16d26c 100644 --- a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java @@ -3,50 +3,53 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; /** - * Binary search is one of the most popular algorithms This class represents - * iterative version {@link BinarySearch} Iterative binary search is likely to - * have lower constant factors because it doesn't involve the overhead of - * manipulating the call stack. But in java the recursive version can be - * optimized by the compiler to this version. + * Binary search is one of the most popular algorithms. + * This class represents the iterative version of {@link BinarySearch}. * - *

- * Worst-case performance O(log n) Best-case performance O(1) Average - * performance O(log n) Worst-case space complexity O(1) + *

Iterative binary search avoids recursion overhead and uses constant space. * - * @author Gabriele La Greca : https://github.com/thegabriele97 - * @author Podshivalov Nikita (https://github.com/nikitap492) + *

Performance: + *

    + *
  • Best-case: O(1)
  • + *
  • Average-case: O(log n)
  • + *
  • Worst-case: O(log n)
  • + *
  • Space complexity: O(1)
  • + *
+ * + * @author Gabriele La Greca + * @author Podshivalov Nikita * @see SearchAlgorithm * @see BinarySearch */ public final class IterativeBinarySearch implements SearchAlgorithm { /** - * This method implements an iterative version of binary search algorithm + * Performs iterative binary search on a sorted array. * - * @param array a sorted array - * @param key the key to search in array - * @return the index of key in the array or -1 if not found + * @param array the sorted array + * @param key the element to search + * @param type of elements (must be Comparable) + * @return index of the key if found, otherwise -1 */ @Override public > int find(T[] array, T key) { - int l; - int r; - int k; - int cmp; + if (array == null || array.length == 0) { + return -1; + } - l = 0; - r = array.length - 1; + int left = 0; + int right = array.length - 1; - while (l <= r) { - k = (l + r) >>> 1; - cmp = key.compareTo(array[k]); + while (left <= right) { + int mid = (left + right) >>> 1; + int cmp = key.compareTo(array[mid]); if (cmp == 0) { - return k; + return mid; } else if (cmp < 0) { - r = --k; + right = mid - 1; } else { - l = ++k; + left = mid + 1; } } From cc75b5ebae11e4dc70b3cb398700ecc775fb7923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 23:53:34 +0100 Subject: [PATCH 45/68] chore(deps): bump codecov/codecov-action from 5 to 6 in /.github/workflows (#7344) chore(deps): bump codecov/codecov-action in /.github/workflows Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5f200c12836..1c2c1ef828b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) @@ -28,7 +28,7 @@ jobs: github.repository == 'TheAlgorithms/Java' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From 92d47022e72fa00d37c00df35bbc7b123a87e7b5 Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:52:54 +0800 Subject: [PATCH 46/68] fix: handle null and empty array in LinearSearch (issue #7340) (#7347) --- src/main/java/com/thealgorithms/searches/LinearSearch.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 6403749a3154..00fb9c2d0fcf 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -41,10 +41,13 @@ public class LinearSearch implements SearchAlgorithm { * * @param array List to be searched * @param value Key being searched for - * @return Location of the key + * @return Location of the key, -1 if array is null or empty, or key not found */ @Override public > int find(T[] array, T value) { + if (array == null || array.length == 0) { + return -1; + } for (int i = 0; i < array.length; i++) { if (array[i].compareTo(value) == 0) { return i; From 227355e313627fb0394914cd7255c83c0e469beb Mon Sep 17 00:00:00 2001 From: Keykyrios Date: Sat, 28 Mar 2026 15:26:47 +0530 Subject: [PATCH 47/68] feat(graph): implement Tarjan's Bridge-Finding Algorithm (#7346) * feat: implement Tarjan's Bridge-Finding Algorithm Adds a classic graph algorithm to find bridge edges in an undirected graph in O(V + E) time. * style: format 2D arrays in TarjanBridgesTest --------- Co-authored-by: Deniz Altunkapan --- .../thealgorithms/graph/TarjanBridges.java | 122 +++++++++++ .../graph/TarjanBridgesTest.java | 207 ++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/TarjanBridges.java create mode 100644 src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java diff --git a/src/main/java/com/thealgorithms/graph/TarjanBridges.java b/src/main/java/com/thealgorithms/graph/TarjanBridges.java new file mode 100644 index 000000000000..dbe2e710429a --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TarjanBridges.java @@ -0,0 +1,122 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of Tarjan's Bridge-Finding Algorithm for undirected graphs. + * + *

A bridge (also called a cut-edge) is an edge in an undirected graph whose removal + * increases the number of connected components. Bridges represent critical links + * in a network β€” if any bridge is removed, part of the network becomes unreachable.

+ * + *

The algorithm performs a single Depth-First Search (DFS) traversal, tracking two + * values for each vertex:

+ *
    + *
  • discoveryTime β€” the time step at which the vertex was first visited.
  • + *
  • lowLink β€” the smallest discovery time reachable from the subtree rooted + * at that vertex (via back edges).
  • + *
+ * + *

An edge (u, v) is a bridge if and only if {@code lowLink[v] > discoveryTime[u]}, + * meaning there is no back edge from the subtree of v that can reach u or any ancestor of u.

+ * + *

Time Complexity: O(V + E), where V is the number of vertices and E is the number of edges.

+ *

Space Complexity: O(V + E) for the adjacency list, discovery/low arrays, and recursion stack.

+ * + * @see Wikipedia: Bridge (graph theory) + */ +public final class TarjanBridges { + + private TarjanBridges() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Finds all bridge edges in an undirected graph. + * + *

The graph is represented as an adjacency list where each vertex is identified by + * an integer in the range {@code [0, vertexCount)}. For each undirected edge (u, v), + * v must appear in {@code adjacencyList.get(u)} and u must appear in + * {@code adjacencyList.get(v)}.

+ * + * @param vertexCount the total number of vertices in the graph (must be non-negative) + * @param adjacencyList the adjacency list representation of the graph; must contain + * exactly {@code vertexCount} entries (one per vertex) + * @return a list of bridge edges, where each bridge is represented as an {@code int[]} + * of length 2 with {@code edge[0] < edge[1]}; returns an empty list if no bridges exist + * @throws IllegalArgumentException if {@code vertexCount} is negative, or if + * {@code adjacencyList} is null or its size does not match + * {@code vertexCount} + */ + public static List findBridges(int vertexCount, List> adjacencyList) { + if (vertexCount < 0) { + throw new IllegalArgumentException("vertexCount must be non-negative"); + } + if (adjacencyList == null || adjacencyList.size() != vertexCount) { + throw new IllegalArgumentException("adjacencyList size must equal vertexCount"); + } + + List bridges = new ArrayList<>(); + + if (vertexCount == 0) { + return bridges; + } + + BridgeFinder finder = new BridgeFinder(vertexCount, adjacencyList, bridges); + + // Run DFS from every unvisited vertex to handle disconnected graphs + for (int i = 0; i < vertexCount; i++) { + if (!finder.visited[i]) { + finder.dfs(i, -1); + } + } + + return bridges; + } + + private static class BridgeFinder { + private final List> adjacencyList; + private final List bridges; + private final int[] discoveryTime; + private final int[] lowLink; + boolean[] visited; + private int timer; + + BridgeFinder(int vertexCount, List> adjacencyList, List bridges) { + this.adjacencyList = adjacencyList; + this.bridges = bridges; + this.discoveryTime = new int[vertexCount]; + this.lowLink = new int[vertexCount]; + this.visited = new boolean[vertexCount]; + this.timer = 0; + } + + /** + * Performs DFS from the given vertex, computing discovery times and low-link values, + * and collects any bridge edges found. + * + * @param u the current vertex being explored + * @param parent the parent of u in the DFS tree (-1 if u is a root) + */ + void dfs(int u, int parent) { + visited[u] = true; + discoveryTime[u] = timer; + lowLink[u] = timer; + timer++; + + for (int v : adjacencyList.get(u)) { + if (!visited[v]) { + dfs(v, u); + lowLink[u] = Math.min(lowLink[u], lowLink[v]); + + if (lowLink[v] > discoveryTime[u]) { + bridges.add(new int[] {Math.min(u, v), Math.max(u, v)}); + } + } else if (v != parent) { + lowLink[u] = Math.min(lowLink[u], discoveryTime[v]); + } + } + } + } +} diff --git a/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java new file mode 100644 index 000000000000..8608bfb2dfc9 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java @@ -0,0 +1,207 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link TarjanBridges}. + * + *

Tests cover a wide range of graph configurations including simple graphs, + * cycles, trees, disconnected components, multigraph-like structures, and + * various edge cases to ensure correct bridge detection.

+ */ +class TarjanBridgesTest { + + /** + * Helper to build a symmetric adjacency list for an undirected graph. + */ + private static List> buildGraph(int vertexCount, int[][] edges) { + List> adj = new ArrayList<>(); + for (int i = 0; i < vertexCount; i++) { + adj.add(new ArrayList<>()); + } + for (int[] edge : edges) { + adj.get(edge[0]).add(edge[1]); + adj.get(edge[1]).add(edge[0]); + } + return adj; + } + + /** + * Sorts bridges for deterministic comparison. + */ + private static void sortBridges(List bridges) { + bridges.sort(Comparator.comparingInt((int[] a) -> a[0]).thenComparingInt(a -> a[1])); + } + + @Test + void testSimpleGraphWithOneBridge() { + // Graph: 0-1-2-3 where 1-2 is the only bridge + // 0---1---2---3 + // | | + // +-------+ (via 0-2 would make cycle, but not here) + // Actually: 0-1 in a cycle with 0-1, and 2-3 in a cycle with 2-3 + // Let's use: 0--1--2 (linear chain). All edges are bridges. + List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}}); + List bridges = TarjanBridges.findBridges(3, adj); + sortBridges(bridges); + assertEquals(2, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + assertEquals(1, bridges.get(1)[0]); + assertEquals(2, bridges.get(1)[1]); + } + + @Test + void testCycleGraphHasNoBridges() { + // Graph: 0-1-2-0 (triangle). No bridges. + List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}, {2, 0}}); + List bridges = TarjanBridges.findBridges(3, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testTreeGraphAllEdgesAreBridges() { + // Tree: 0 + // / \ + // 1 2 + // / \ + // 3 4 + List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {1, 3}, {1, 4}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(4, bridges.size()); + } + + @Test + void testGraphWithMixedBridgesAndCycles() { + // Graph: + // 0---1 + // | | + // 3---2---4---5 + // | + // 6 + // Cycle: 0-1-2-3-0 (no bridges within) + // Bridges: 2-4, 4-5, 5-6 + List> adj = buildGraph(7, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {2, 4}, {4, 5}, {5, 6}}); + List bridges = TarjanBridges.findBridges(7, adj); + sortBridges(bridges); + assertEquals(3, bridges.size()); + assertEquals(2, bridges.get(0)[0]); + assertEquals(4, bridges.get(0)[1]); + assertEquals(4, bridges.get(1)[0]); + assertEquals(5, bridges.get(1)[1]); + assertEquals(5, bridges.get(2)[0]); + assertEquals(6, bridges.get(2)[1]); + } + + @Test + void testDisconnectedGraphWithBridges() { + // Component 1: 0-1 (bridge) + // Component 2: 2-3-4-2 (cycle, no bridges) + List> adj = buildGraph(5, new int[][] {{0, 1}, {2, 3}, {3, 4}, {4, 2}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(1, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + } + + @Test + void testSingleVertex() { + List> adj = buildGraph(1, new int[][] {}); + List bridges = TarjanBridges.findBridges(1, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testTwoVerticesWithOneEdge() { + List> adj = buildGraph(2, new int[][] {{0, 1}}); + List bridges = TarjanBridges.findBridges(2, adj); + assertEquals(1, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + } + + @Test + void testEmptyGraph() { + List> adj = buildGraph(0, new int[][] {}); + List bridges = TarjanBridges.findBridges(0, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testIsolatedVertices() { + // 5 vertices, no edges β€” all isolated + List> adj = buildGraph(5, new int[][] {}); + List bridges = TarjanBridges.findBridges(5, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testLargeCycleNoBridges() { + // Cycle: 0-1-2-3-4-5-6-7-0 + int n = 8; + int[][] edges = new int[n][2]; + for (int i = 0; i < n; i++) { + edges[i] = new int[] {i, (i + 1) % n}; + } + List> adj = buildGraph(n, edges); + List bridges = TarjanBridges.findBridges(n, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testComplexGraphWithMultipleCyclesAndBridges() { + // Two cycles connected by a single bridge edge: + // Cycle A: 0-1-2-0 + // Cycle B: 3-4-5-3 + // Bridge: 2-3 + List> adj = buildGraph(6, new int[][] {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {2, 3}}); + List bridges = TarjanBridges.findBridges(6, adj); + assertEquals(1, bridges.size()); + assertEquals(2, bridges.get(0)[0]); + assertEquals(3, bridges.get(0)[1]); + } + + @Test + void testNegativeVertexCountThrowsException() { + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(-1, new ArrayList<>())); + } + + @Test + void testNullAdjacencyListThrowsException() { + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(3, null)); + } + + @Test + void testMismatchedAdjacencyListSizeThrowsException() { + List> adj = buildGraph(2, new int[][] {{0, 1}}); + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(5, adj)); + } + + @Test + void testStarGraphAllEdgesAreBridges() { + // Star graph: center vertex 0 connected to 1, 2, 3, 4 + List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {0, 3}, {0, 4}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(4, bridges.size()); + } + + @Test + void testBridgeBetweenTwoCycles() { + // Two squares connected by one bridge: + // Square 1: 0-1-2-3-0 + // Square 2: 4-5-6-7-4 + // Bridge: 3-4 + List> adj = buildGraph(8, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {3, 4}}); + List bridges = TarjanBridges.findBridges(8, adj); + assertEquals(1, bridges.size()); + assertEquals(3, bridges.get(0)[0]); + assertEquals(4, bridges.get(0)[1]); + } +} From 8be75436ed863ac7e198dc889a71669d11c30354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:37:03 +0200 Subject: [PATCH 48/68] chore(deps): bump com.puppycrawl.tools:checkstyle from 13.3.0 to 13.4.0 (#7352) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.3.0 to 13.4.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-13.3.0...checkstyle-13.4.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-version: 13.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> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dab7447430e5..c3629f165361 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ com.puppycrawl.tools checkstyle - 13.3.0 + 13.4.0 From 5753f46f538e460b1246da699bfa700a091cddc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:40:59 +0000 Subject: [PATCH 49/68] chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin from 4.9.8.2 to 4.9.8.3 (#7353) chore(deps-dev): bump com.github.spotbugs:spotbugs-maven-plugin Bumps [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) from 4.9.8.2 to 4.9.8.3. - [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases) - [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.8.2...spotbugs-maven-plugin-4.9.8.3) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-maven-plugin dependency-version: 4.9.8.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3629f165361..74b6cd9bd485 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ com.github.spotbugs spotbugs-maven-plugin - 4.9.8.2 + 4.9.8.3 spotbugs-exclude.xml true From bdbbecedfc2b5263a4ddd749be3bfe34670e8b7d Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:03:01 +0800 Subject: [PATCH 50/68] Improve JumpSearch documentation with detailed explanation and example (#7354) Issue: #7349 Added: - Step-by-step algorithm explanation - Worked example with input/output - Time and space complexity analysis - Notes on when to use Jump Search vs Binary/Linear Search - @see references to related search algorithms --- .../thealgorithms/searches/JumpSearch.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java index 8dcec3a819a4..cbc494c8c16a 100644 --- a/src/main/java/com/thealgorithms/searches/JumpSearch.java +++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java @@ -12,26 +12,50 @@ * Once the range is found, a linear search is performed within that block. * *

- * The Jump Search algorithm is particularly effective for large sorted arrays where the cost of - * performing a linear search on the entire array would be prohibitive. + * How it works: + *

    + *
  1. Calculate the optimal block size as √n (square root of array length)
  2. + *
  3. Jump ahead by the block size until the current element is greater than the target
  4. + *
  5. Perform a linear search backwards within the identified block
  6. + *
* *

- * Worst-case performance: O(√N)
- * Best-case performance: O(1)
- * Average performance: O(√N)
- * Worst-case space complexity: O(1) + * Example:
+ * Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], Target: 9
+ * Step 1: Jump from index 0 β†’ 3 β†’ 6 (9 < 13, so we found the block)
+ * Step 2: Linear search from index 3 to 6: found 9 at index 4
+ * Result: Index = 4 + * + *

+ * Time Complexity:
+ * - Best-case: O(1) - element found at first position
+ * - Average: O(√n) - optimal block size reduces jumps
+ * - Worst-case: O(√n) - element at end of array or not present
+ * + *

+ * Space Complexity: O(1) - only uses a constant amount of extra space + * + *

+ * Note: Jump Search requires a sorted array. For unsorted arrays, use Linear Search. + * Compared to Linear Search (O(n)), Jump Search is faster for large arrays. + * Compared to Binary Search (O(log n)), Jump Search is less efficient but may be + * preferable when jumping through a linked list or when backward scanning is costly. * *

* This class implements the {@link SearchAlgorithm} interface, providing a generic search method * for any comparable type. + * + * @see SearchAlgorithm + * @see BinarySearch + * @see LinearSearch */ public class JumpSearch implements SearchAlgorithm { /** * Jump Search algorithm implementation. * - * @param array the sorted array containing elements - * @param key the element to be searched + * @param array the sorted array containing elements (must be sorted in ascending order) + * @param key the element to be searched for * @return the index of {@code key} if found, otherwise -1 */ @Override From d2744b56173ca4936765d55457190098501e4617 Mon Sep 17 00:00:00 2001 From: Suraj Devatha <42767118+surajdm123@users.noreply.github.com> Date: Mon, 30 Mar 2026 10:56:16 -0700 Subject: [PATCH 51/68] Fixes #7350: Binary Search Reliability Improvement (#7351) --- .../searches/BinarySearchTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java index bd4620a7fa7d..00bed165734e 100644 --- a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java +++ b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java @@ -105,4 +105,90 @@ void testBinarySearchLargeArray() { int expectedIndex = 9999; // Index of the last element assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the last element should be 9999."); } + + /** + * Test for binary search with null array. + */ + @Test + void testBinarySearchNullArray() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = null; + int key = 5; // Key to search + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in a null array."); + } + + /** + * Test for binary search with duplicate elements. + */ + @Test + void testBinarySearchWithDuplicates() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 2, 2, 3}; + int key = 2; // Element present multiple times + + int result = binarySearch.find(array, key); + assertEquals(2, array[result], "The returned index should contain the searched element."); + } + + /** + * Test for binary search where all elements are the same. + */ + @Test + void testBinarySearchAllElementsSame() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {5, 5, 5, 5, 5}; + int key = 5; // All elements match + + int result = binarySearch.find(array, key); + assertEquals(5, array[result], "The returned index should contain the searched element."); + } + + /** + * Test for binary search with negative numbers. + */ + @Test + void testBinarySearchNegativeNumbers() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {-10, -5, 0, 5, 10}; + int key = -5; // Element present + int expectedIndex = 1; // Index of the element + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 1."); + } + + /** + * Test for binary search when key is smaller than all elements. + */ + @Test + void testBinarySearchKeySmallerThanAll() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {10, 20, 30}; + int key = 5; // Smaller than all elements + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search when key is larger than all elements. + */ + @Test + void testBinarySearchKeyLargerThanAll() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {10, 20, 30}; + int key = 40; // Larger than all elements + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search with String array. + */ + @Test + void testBinarySearchStrings() { + BinarySearch binarySearch = new BinarySearch(); + String[] array = {"apple", "banana", "cherry", "date"}; + String key = "cherry"; // Element present + int expectedIndex = 2; // Index of the element + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 2."); + } } From ee343363c6158d91d7a180ac88e3749482bed291 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:34:30 +0300 Subject: [PATCH 52/68] Correlation function for discrete variable correlation (#7326) * Correlation function for discrete variable correlation * Add unit tests for Correlation class Added unit tests for the Correlation class to validate correlation calculations under various scenarios, including linear dependence and constant values. * Added missing bracket * Refactor variable initialization in correlation method * Remove unused imports and clean up CorrelationTest * Fix formatting of variable declarations in correlation method * Update Correlation.java * Fix formatting in CorrelationTest.java * Enhance comments in correlation function Added detailed comments to the correlation function for better understanding. * Add correlation tests for various scenarios * Format comments for clarity in correlation method * Fix formatting and comments in correlation method --- .../com/thealgorithms/maths/Correlation.java | 51 +++++++++++++++++++ .../thealgorithms/maths/CorrelationTest.java | 51 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/Correlation.java create mode 100644 src/test/java/com/thealgorithms/maths/CorrelationTest.java diff --git a/src/main/java/com/thealgorithms/maths/Correlation.java b/src/main/java/com/thealgorithms/maths/Correlation.java new file mode 100644 index 000000000000..a46445fb23b7 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/Correlation.java @@ -0,0 +1,51 @@ +package com.thealgorithms.maths; + +/** + * Class for correlation of two discrete variables + */ + +public final class Correlation { + private Correlation() { + } + + public static final double DELTA = 1e-9; + + /** + * Discrete correlation function. + * Correlation between two discrete variables is calculated + * according to the formula: Cor(x, y)=Cov(x, y)/sqrt(Var(x)*Var(y)). + * Correlation with a constant variable is taken to be zero. + * + * @param x The first discrete variable + * @param y The second discrete variable + * @param n The number of values for each variable + * @return The result of the correlation of variables x,y. + */ + public static double correlation(double[] x, double[] y, int n) { + double exy = 0; // E(XY) + double ex = 0; // E(X) + double exx = 0; // E(X^2) + double ey = 0; // E(Y) + double eyy = 0; // E(Y^2) + for (int i = 0; i < n; i++) { + exy += x[i] * y[i]; + ex += x[i]; + exx += x[i] * x[i]; + ey += y[i]; + eyy += y[i] * y[i]; + } + exy /= n; + ex /= n; + exx /= n; + ey /= n; + eyy /= n; + double cov = exy - ex * ey; // Cov(X, Y) = E(XY)-E(X)E(Y) + double varx = Math.sqrt(exx - ex * ex); // Var(X) = sqrt(E(X^2)-E(X)^2) + double vary = Math.sqrt(eyy - ey * ey); // Var(Y) = sqrt(E(Y^2)-E(Y)^2) + if (varx * vary < DELTA) { // Var(X) = 0 means X = const, the same about Y + return 0; + } else { + return cov / Math.sqrt(varx * vary); + } + } +} diff --git a/src/test/java/com/thealgorithms/maths/CorrelationTest.java b/src/test/java/com/thealgorithms/maths/CorrelationTest.java new file mode 100644 index 000000000000..96867d56ad5e --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/CorrelationTest.java @@ -0,0 +1,51 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test class for Correlation class + */ +public class CorrelationTest { + + public static final double DELTA = 1e-9; + + // Regular correlation test + public void testCorrelationFirst() { + double[] x = {1, 2, 3, 4}; + double[] y = {7, 1, 4, 9}; + int n = 4; + assertEquals(0.3319700011, Correlation.correlation(x, y, n), DELTA); + } + + // Regular correlation test (zero correlation) + public void testCorrelationSecond() { + double[] x = {1, 2, 3, 4}; + double[] y = {5, 0, 9, 2}; + int n = 4; + assertEquals(0, Correlation.correlation(x, y, n), DELTA); + } + + // Correlation with a constant variable is taken to be zero + public void testCorrelationConstant() { + double[] x = {1, 2, 3}; + double[] y = {4, 4, 4}; + int n = 3; + assertEquals(0, Correlation.correlation(x, y, n), DELTA); + } + + // Linear dependence gives correlation 1 + public void testCorrelationLinearDependence() { + double[] x = {1, 2, 3, 4}; + double[] y = {6, 8, 10, 12}; + int n = 4; + assertEquals(1, Correlation.correlation(x, y, n), DELTA); + } + + // Inverse linear dependence gives correlation -1 + public void testCorrelationInverseLinearDependence() { + double[] x = {1, 2, 3, 4, 5}; + double[] y = {18, 15, 12, 9, 6}; + int n = 5; + assertEquals(-1, Correlation.correlation(x, y, n), DELTA); + } +} From 635d54a9de573a51786b18b151579206b58ae947 Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:03:57 +0800 Subject: [PATCH 53/68] fix(BinarySearch): add null key check to prevent NPE (fix #7356) (#7357) fix(BinarySearch): add null key check to prevent NPE Issue #7356: Add null check for the search value to prevent potential NullPointerException. --- .../java/com/thealgorithms/searches/BinarySearch.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java index 7a5361b280ea..ca873fc6eafa 100644 --- a/src/main/java/com/thealgorithms/searches/BinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java @@ -58,11 +58,18 @@ class BinarySearch implements SearchAlgorithm { */ @Override public > int find(T[] array, T key) { - // Handle edge case: empty array + // Handle edge case: null or empty array if (array == null || array.length == 0) { return -1; } + // Handle edge case: null key + // Searching for null in an array of Comparables is undefined behavior + // Return -1 to indicate not found rather than throwing NPE + if (key == null) { + return -1; + } + // Delegate to the core search implementation return search(array, key, 0, array.length - 1); } From 9729e56fc7b54197460ee449d94d33284c68d7aa Mon Sep 17 00:00:00 2001 From: Vansh Sharma Date: Wed, 1 Apr 2026 17:25:29 +0530 Subject: [PATCH 54/68] Use BigInteger to prevent overflow in factorial calculation (#7358) * Use BigInteger to prevent overflow in factorial calculation * chore: remove unnecessary comment * update * Fix: improve factorial implementation and formatting * test: final fix for FactorialTest logic and formatting * chore: final formatting and test fix for BigInteger * chore: final formatting and test fix for BigInteger --- .../com/thealgorithms/maths/Factorial.java | 18 +++++++----------- .../com/thealgorithms/maths/FactorialTest.java | 9 +++++---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/Factorial.java b/src/main/java/com/thealgorithms/maths/Factorial.java index 511cc1f84f05..8ad219a3066c 100644 --- a/src/main/java/com/thealgorithms/maths/Factorial.java +++ b/src/main/java/com/thealgorithms/maths/Factorial.java @@ -1,23 +1,19 @@ package com.thealgorithms.maths; +import java.math.BigInteger; + public final class Factorial { private Factorial() { } - /** - * Calculate factorial N using iteration - * - * @param n the number - * @return the factorial of {@code n} - */ - public static long factorial(int n) { + public static BigInteger factorial(int n) { if (n < 0) { throw new IllegalArgumentException("Input number cannot be negative"); } - long factorial = 1; - for (int i = 1; i <= n; ++i) { - factorial *= i; + BigInteger result = BigInteger.ONE; + for (int i = 1; i <= n; i++) { + result = result.multiply(BigInteger.valueOf(i)); } - return factorial; + return result; } } diff --git a/src/test/java/com/thealgorithms/maths/FactorialTest.java b/src/test/java/com/thealgorithms/maths/FactorialTest.java index 3ff7097b8113..a393136696e1 100644 --- a/src/test/java/com/thealgorithms/maths/FactorialTest.java +++ b/src/test/java/com/thealgorithms/maths/FactorialTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.math.BigInteger; import org.junit.jupiter.api.Test; public class FactorialTest { @@ -16,9 +17,9 @@ public void testWhenInvalidInoutProvidedShouldThrowException() { @Test public void testCorrectFactorialCalculation() { - assertEquals(1, Factorial.factorial(0)); - assertEquals(1, Factorial.factorial(1)); - assertEquals(120, Factorial.factorial(5)); - assertEquals(3628800, Factorial.factorial(10)); + assertEquals(BigInteger.ONE, Factorial.factorial(0)); + assertEquals(BigInteger.ONE, Factorial.factorial(1)); + assertEquals(BigInteger.valueOf(120), Factorial.factorial(5)); + assertEquals(BigInteger.valueOf(3628800), Factorial.factorial(10)); } } From f38d5cdb3c33589c0e171b17f1daad6634fba963 Mon Sep 17 00:00:00 2001 From: Alex Tumanov Date: Fri, 3 Apr 2026 06:50:11 -0500 Subject: [PATCH 55/68] feat: add OptimalBinarySearchTree algorithm (#7310) --- .../OptimalBinarySearchTree.java | 130 ++++++++++++++++++ .../OptimalBinarySearchTreeTest.java | 73 ++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java new file mode 100644 index 000000000000..428176ea6c40 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java @@ -0,0 +1,130 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Computes the minimum search cost of an optimal binary search tree. + * + *

The algorithm sorts the keys, preserves the corresponding search frequencies, and uses + * dynamic programming with Knuth's optimization to compute the minimum weighted search cost. + * + *

Example: if keys = [10, 12] and frequencies = [34, 50], the best tree puts 12 at the root + * and 10 as its left child. The total cost is 50 * 1 + 34 * 2 = 118. + * + *

Reference: + * https://en.wikipedia.org/wiki/Optimal_binary_search_tree + */ +public final class OptimalBinarySearchTree { + private OptimalBinarySearchTree() { + } + + /** + * Computes the minimum weighted search cost for the given keys and search frequencies. + * + * @param keys the BST keys + * @param frequencies the search frequencies associated with the keys + * @return the minimum search cost + * @throws IllegalArgumentException if the input is invalid + */ + public static long findOptimalCost(int[] keys, int[] frequencies) { + validateInput(keys, frequencies); + if (keys.length == 0) { + return 0L; + } + + int[][] sortedNodes = sortNodes(keys, frequencies); + int nodeCount = sortedNodes.length; + long[] prefixSums = buildPrefixSums(sortedNodes); + long[][] optimalCost = new long[nodeCount][nodeCount]; + int[][] root = new int[nodeCount][nodeCount]; + + // Small example: + // keys = [10, 12] + // frequencies = [34, 50] + // Choosing 12 as the root gives cost 50 * 1 + 34 * 2 = 118, + // which is better than choosing 10 as the root. + + // Base case: a subtree containing one key has cost equal to its frequency, + // because that key becomes the root of the subtree and is searched at depth 1. + for (int index = 0; index < nodeCount; index++) { + optimalCost[index][index] = sortedNodes[index][1]; + root[index][index] = index; + } + + // Build solutions for longer and longer key ranges. + // optimalCost[start][end] stores the minimum search cost for keys in that range. + for (int length = 2; length <= nodeCount; length++) { + for (int start = 0; start <= nodeCount - length; start++) { + int end = start + length - 1; + + // Every key in this range moves one level deeper when we choose a root, + // so the sum of frequencies is added once to the subtree cost. + long frequencySum = prefixSums[end + 1] - prefixSums[start]; + optimalCost[start][end] = Long.MAX_VALUE; + + // Knuth's optimization: + // the best root for [start, end] lies between the best roots of + // [start, end - 1] and [start + 1, end], so we search only this interval. + int leftBoundary = root[start][end - 1]; + int rightBoundary = root[start + 1][end]; + for (int currentRoot = leftBoundary; currentRoot <= rightBoundary; currentRoot++) { + long leftCost = currentRoot > start ? optimalCost[start][currentRoot - 1] : 0L; + long rightCost = currentRoot < end ? optimalCost[currentRoot + 1][end] : 0L; + long currentCost = frequencySum + leftCost + rightCost; + + if (currentCost < optimalCost[start][end]) { + optimalCost[start][end] = currentCost; + root[start][end] = currentRoot; + } + } + } + } + + return optimalCost[0][nodeCount - 1]; + } + + private static void validateInput(int[] keys, int[] frequencies) { + if (keys == null || frequencies == null) { + throw new IllegalArgumentException("Keys and frequencies cannot be null"); + } + if (keys.length != frequencies.length) { + throw new IllegalArgumentException("Keys and frequencies must have the same length"); + } + + for (int frequency : frequencies) { + if (frequency < 0) { + throw new IllegalArgumentException("Frequencies cannot be negative"); + } + } + } + + private static int[][] sortNodes(int[] keys, int[] frequencies) { + int[][] sortedNodes = new int[keys.length][2]; + for (int index = 0; index < keys.length; index++) { + sortedNodes[index][0] = keys[index]; + sortedNodes[index][1] = frequencies[index]; + } + + // Sort by key so the nodes can be treated as an in-order BST sequence. + Arrays.sort(sortedNodes, Comparator.comparingInt(node -> node[0])); + + for (int index = 1; index < sortedNodes.length; index++) { + if (sortedNodes[index - 1][0] == sortedNodes[index][0]) { + throw new IllegalArgumentException("Keys must be distinct"); + } + } + + return sortedNodes; + } + + private static long[] buildPrefixSums(int[][] sortedNodes) { + long[] prefixSums = new long[sortedNodes.length + 1]; + for (int index = 0; index < sortedNodes.length; index++) { + // prefixSums[i] holds the total frequency of the first i sorted keys. + // This lets us get the frequency sum of any range in O(1) time. + prefixSums[index + 1] = prefixSums[index] + sortedNodes[index][1]; + } + return prefixSums; + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java new file mode 100644 index 000000000000..17ff3ec728dc --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class OptimalBinarySearchTreeTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testFindOptimalCost(int[] keys, int[] frequencies, long expectedCost) { + assertEquals(expectedCost, OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream validTestCases() { + return Stream.of(Arguments.of(new int[] {}, new int[] {}, 0L), Arguments.of(new int[] {15}, new int[] {9}, 9L), Arguments.of(new int[] {10, 12}, new int[] {34, 50}, 118L), Arguments.of(new int[] {20, 10, 30}, new int[] {50, 34, 8}, 134L), + Arguments.of(new int[] {12, 10, 20, 42, 25, 37}, new int[] {8, 34, 50, 3, 40, 30}, 324L), Arguments.of(new int[] {1, 2, 3}, new int[] {0, 0, 0}, 0L)); + } + + @ParameterizedTest + @MethodSource("crossCheckTestCases") + void testFindOptimalCostAgainstBruteForce(int[] keys, int[] frequencies) { + assertEquals(bruteForceOptimalCost(keys, frequencies), OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream crossCheckTestCases() { + return Stream.of(Arguments.of(new int[] {3, 1, 2}, new int[] {4, 2, 6}), Arguments.of(new int[] {5, 2, 8, 6}, new int[] {3, 7, 1, 4}), Arguments.of(new int[] {9, 4, 11, 2}, new int[] {1, 8, 2, 5})); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testFindOptimalCostInvalidInput(int[] keys, int[] frequencies) { + assertThrows(IllegalArgumentException.class, () -> OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream invalidTestCases() { + return Stream.of(Arguments.of(null, new int[] {}), Arguments.of(new int[] {}, null), Arguments.of(new int[] {1, 2}, new int[] {3}), Arguments.of(new int[] {1, 1}, new int[] {2, 3}), Arguments.of(new int[] {1, 2}, new int[] {3, -1})); + } + + private static long bruteForceOptimalCost(int[] keys, int[] frequencies) { + int[][] sortedNodes = new int[keys.length][2]; + for (int index = 0; index < keys.length; index++) { + sortedNodes[index][0] = keys[index]; + sortedNodes[index][1] = frequencies[index]; + } + Arrays.sort(sortedNodes, java.util.Comparator.comparingInt(node -> node[0])); + + int[] sortedFrequencies = new int[sortedNodes.length]; + for (int index = 0; index < sortedNodes.length; index++) { + sortedFrequencies[index] = sortedNodes[index][1]; + } + + return bruteForceOptimalCost(sortedFrequencies, 0, sortedFrequencies.length - 1, 1); + } + + private static long bruteForceOptimalCost(int[] frequencies, int start, int end, int depth) { + if (start > end) { + return 0L; + } + + long minimumCost = Long.MAX_VALUE; + for (int root = start; root <= end; root++) { + long currentCost = (long) depth * frequencies[root] + bruteForceOptimalCost(frequencies, start, root - 1, depth + 1) + bruteForceOptimalCost(frequencies, root + 1, end, depth + 1); + minimumCost = Math.min(minimumCost, currentCost); + } + return minimumCost; + } +} From b7e9c85c43a23515cadc34e9b71484b198c7af2b Mon Sep 17 00:00:00 2001 From: AbhiramSakha <143825001+AbhiramSakha@users.noreply.github.com> Date: Sun, 5 Apr 2026 03:13:50 +0530 Subject: [PATCH 56/68] Enhance LinearSearch with null check for value (#7355) Added null check for the search value in the find method. --- .../java/com/thealgorithms/searches/LinearSearch.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 00fb9c2d0fcf..77ecece3aaef 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -14,6 +14,7 @@ package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; + /** * Linear Search is a simple searching algorithm that checks * each element of the array sequentially until the target @@ -33,7 +34,6 @@ * @see BinarySearch * @see SearchAlgorithm */ - public class LinearSearch implements SearchAlgorithm { /** @@ -45,14 +45,17 @@ public class LinearSearch implements SearchAlgorithm { */ @Override public > int find(T[] array, T value) { - if (array == null || array.length == 0) { + + if (array == null || array.length == 0 || value == null) { return -1; } + for (int i = 0; i < array.length; i++) { - if (array[i].compareTo(value) == 0) { + if (array[i] != null && array[i].compareTo(value) == 0) { return i; } } + return -1; } } From abd1c4732f4369a500cd164489e68f3e5fd4aba8 Mon Sep 17 00:00:00 2001 From: kvadrik <41710943+kvadrik@users.noreply.github.com> Date: Sun, 5 Apr 2026 00:58:45 +0300 Subject: [PATCH 57/68] Create Relativity.java (#7323) * Create Relativity.java Created the file that implements relativity formulae. * Create RelativityTest.java Test file created for Relativity.java. * Update RelativityTest.java Function name ambiguity resolved * Update RelativityTest.java DELTA increased * Update Relativity.java Extra white space removed * Update Relativity.java Extra spaces added * Update RelativityTest.java Spaces added and removed * Update Relativity.java White spaces added and removed * Update RelativityTest.java Added and removed spaces * Update Relativity.java * Update Relativity.java Space removed * Update Relativity.java Replaced tabs by spaces * Update RelativityTest.java Tabs replaced by spaces --- .../com/thealgorithms/physics/Relativity.java | 81 +++++++++++++++++++ .../thealgorithms/physics/RelativityTest.java | 73 +++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/main/java/com/thealgorithms/physics/Relativity.java create mode 100644 src/test/java/com/thealgorithms/physics/RelativityTest.java diff --git a/src/main/java/com/thealgorithms/physics/Relativity.java b/src/main/java/com/thealgorithms/physics/Relativity.java new file mode 100644 index 000000000000..ed823c2cc879 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/Relativity.java @@ -0,0 +1,81 @@ +package com.thealgorithms.physics; + +/** + * Implements relativity theory formulae. + * Provides simple static methods to calculate length contraction and time dilation + * in the laboratory frame with respect to the object's own frame, and velocity + * with respect to the moving frame. + * + * @see Wikipedia + */ +public final class Relativity { + + /* Speed of light in m s^-1 */ + public static final double SPEED_OF_LIGHT = 299792458.0; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private Relativity() { + } + + /** + * Calculates the gamma parameter that is of paramount importance in relativity + * theory. It is a dimensionless parameter that is equal to 1 for zero velocity + * but tends to infinity when velocity approaches the speed of light. + * + * @param v The velocity (m/s). + * @return The value of gamma parameter. + */ + public static double gamma(double v) { + if (Math.abs(v) >= SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Speed must be lower than the speed of light"); + } + return 1.0 / Math.sqrt(1 - v * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT)); + } + + /** + * Calculates the length of an object in the moving frame. + * + * @param length The length of an object in its own frame (m). + * @param v The velocity of the object (m/s). + * @return The length of an object in the laboratory frame (m). + */ + public static double lengthContraction(double length, double v) { + if (length < 0) { + throw new IllegalArgumentException("Length must be non-negative"); + } + return length / gamma(v); + } + + /** + * Calculates the time that has passed in the moving frame. + * + * @param length The time that has passed in the object's own frame (s). + * @param v The velocity of the object (m/s). + * @return The time that has passed in the laboratory frame (s). + */ + public static double timeDilation(double time, double v) { + if (time < 0) { + throw new IllegalArgumentException("Time must be non-negative"); + } + return time * gamma(v); + } + + /** + * Calculates the velocity with respect to the moving frame. + * + * @param v1 The velocity of the object with respect to laboratory frame (m/s). + * @param v The velocity of the moving frame (m/s). + * @return The velocity with respect to the moving frame (m/s). + */ + public static double velocityAddition(double v1, double v) { + if (Math.abs(v1) > SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Speed must not exceed the speed of light"); + } + if (Math.abs(v) >= SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Frame speed must be lower than the speed of light"); + } + return (v1 - v) / (1 - v1 * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT)); + } +} diff --git a/src/test/java/com/thealgorithms/physics/RelativityTest.java b/src/test/java/com/thealgorithms/physics/RelativityTest.java new file mode 100644 index 000000000000..44c17bdbd40f --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/RelativityTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Relativity utility class. + */ +final class RelativityTest { + + // A small tolerance (delta) for comparing floating-point numbers + private static final double DELTA = 1e-6; + private static final double C = Relativity.SPEED_OF_LIGHT; + + @Test + @DisplayName("Test the gamma parameter") + void testGamma() { + double myGamma = Relativity.gamma(0.6 * C); + assertEquals(1.25, myGamma, DELTA); + } + + @Test + @DisplayName("Test the length contraction") + void testLengthContraction() { + double myLength = Relativity.lengthContraction(5.0, 0.8 * C); + assertEquals(3.0, myLength, DELTA); + } + + @Test + @DisplayName("Test the time dilation") + void testTimeDilation() { + double myTime = Relativity.timeDilation(4.0, 0.6 * C); + assertEquals(5.0, myTime, DELTA); + } + + @Test + @DisplayName("Test the velocity addition in the same direction") + void testVelocityAdditionSameDirection() { + double myVelocity = Relativity.velocityAddition(0.8 * C, 0.75 * C); + assertEquals(0.125 * C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test the velocity addition in different directions") + void testVelocityAdditionDifferentDirections() { + double myVelocity = Relativity.velocityAddition(0.8 * C, -0.75 * C); + assertEquals(0.96875 * C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test the velocity addition with the speed of light") + void testVelocityAdditionWithSpeedOfLight() { + double myVelocity = Relativity.velocityAddition(C, 0.7 * C); + assertEquals(C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test invalid inputs throw exception") + void testInvalidOrbitalVelocityInputs() { + assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(1.2 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(-C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(-1.0, 0.6 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(1.0, 1.5 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(-5.0, -0.8 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(5.0, C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(0.3 * C, -C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(1.4 * C, 0.2 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(-0.4 * C, 1.2 * C)); + } +} From 13aaad21135611953d1e310e5535e4f181a1a4dc Mon Sep 17 00:00:00 2001 From: Shyam Chavda <163722988+ShyamChavda005@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:36:07 +0530 Subject: [PATCH 58/68] feat(strings): add MoveHashToEnd algorithm (#7313) * feat(strings): add MoveHashToEnd algorithm with tests - Add MoveHashToEnd utility class that moves all '#' characters to the end of a string while preserving the order of other characters - Algorithm runs in O(n) time and O(n) space using a two-pass approach: first collects non-'#' chars, then fills remaining positions with '#' - Add null and empty string guards - Add MoveHashToEndTest with 9 unit tests covering normal, edge, and boundary cases (null, empty, all-hash, no-hash, single char) * docs(strings): add reference URL to MoveHashToEnd Javadoc * docs: add MoveHashToEnd to DIRECTORY index * style(strings): add missing newline at EOF in MoveHashToEnd * test(strings): fix MoveHashToEnd expected output for sample input --- DIRECTORY.md | 1 + .../thealgorithms/strings/MoveHashToEnd.java | 56 +++++++++++++++++++ .../strings/MoveHashToEndTest.java | 54 ++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 src/main/java/com/thealgorithms/strings/MoveHashToEnd.java create mode 100644 src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 585c634c3429..37d9b3c295e2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -816,6 +816,7 @@ - πŸ“„ [Lower](src/main/java/com/thealgorithms/strings/Lower.java) - πŸ“„ [Manacher](src/main/java/com/thealgorithms/strings/Manacher.java) - πŸ“„ [MyAtoi](src/main/java/com/thealgorithms/strings/MyAtoi.java) + - πŸ“„ [MoveHashToEnd](src/main/java/com/thealgorithms/strings/MoveHashToEnd.java) - πŸ“„ [Palindrome](src/main/java/com/thealgorithms/strings/Palindrome.java) - πŸ“„ [Pangram](src/main/java/com/thealgorithms/strings/Pangram.java) - πŸ“„ [PermuteString](src/main/java/com/thealgorithms/strings/PermuteString.java) diff --git a/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java new file mode 100644 index 000000000000..bd686a0c5ba0 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java @@ -0,0 +1,56 @@ +package com.thealgorithms.strings; + +/** + * Moves all '#' characters to the end of the given string while preserving + * the order of the other characters. + * + * Example: + * Input : "h#e#l#llo" + * Output : "helllo###" + * + * The algorithm works by iterating through the string and collecting + * all non-# characters first, then filling the remaining positions + * with '#'. + * + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * @see Move all special characters to end - GeeksForGeeks + */ +public final class MoveHashToEnd { + + /** + * Private constructor to prevent instantiation of utility class. + */ + private MoveHashToEnd() { + } + + /** + * Moves all '#' characters in the input string to the end. + * + * @param str the input string containing characters and '#' + * @return a new string with all '#' characters moved to the end + */ + public static String moveHashToEnd(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + char[] arr = str.toCharArray(); + int insertPos = 0; + + // Place all non-# characters at the beginning + for (char ch : arr) { + if (ch != '#') { + arr[insertPos++] = ch; + } + } + + // Fill remaining positions with '#' + while (insertPos < arr.length) { + arr[insertPos++] = '#'; + } + + return new String(arr); + } +} diff --git a/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java new file mode 100644 index 000000000000..016f72ff9d7f --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class MoveHashToEndTest { + + @Test + void testBasicCase() { + assertEquals("helllo###", MoveHashToEnd.moveHashToEnd("h#e#l#llo")); + } + + @Test + void testNoHash() { + assertEquals("hello", MoveHashToEnd.moveHashToEnd("hello")); + } + + @Test + void testAllHashes() { + assertEquals("###", MoveHashToEnd.moveHashToEnd("###")); + } + + @Test + void testHashAtEnd() { + assertEquals("hello#", MoveHashToEnd.moveHashToEnd("hello#")); + } + + @Test + void testHashAtStart() { + assertEquals("hello#", MoveHashToEnd.moveHashToEnd("#hello")); + } + + @Test + void testEmptyString() { + assertEquals("", MoveHashToEnd.moveHashToEnd("")); + } + + @Test + void testNullInput() { + assertNull(MoveHashToEnd.moveHashToEnd(null)); + } + + @Test + void testSingleHash() { + assertEquals("#", MoveHashToEnd.moveHashToEnd("#")); + } + + @Test + void testSingleNonHashChar() { + assertEquals("a", MoveHashToEnd.moveHashToEnd("a")); + } +} From 741b2d10944fe85f0cea81b6e6f2d01eb8d29182 Mon Sep 17 00:00:00 2001 From: Yassa Talaat <145781920+YassaTalaat80@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:42:14 +0200 Subject: [PATCH 59/68] Feat/segment tree 2d (#7363) * feat: add 2D segment tree implementation * test: add comprehensive unit tests for 2D segment tree * style: format code using clang-format --- .../datastructures/trees/SegmentTree2D.java | 201 ++++++++++++++++++ .../trees/SegmentTree2DTest.java | 71 +++++++ 2 files changed, 272 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java new file mode 100644 index 000000000000..40b9e8a73533 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java @@ -0,0 +1,201 @@ +package com.thealgorithms.datastructures.trees; + +/** + * 2D Segment Tree (Tree of Trees) implementation. + * This data structure supports point updates and submatrix sum queries + * in a 2D grid. It achieves this by nesting 1D Segment Trees within a 1D Segment Tree. + * + * Time Complexity: + * - Build/Initialization: O(N * M) + * - Point Update: O(log N * log M) + * - Submatrix Query: O(log N * log M) + * + * @see 2D Segment Tree + */ +public class SegmentTree2D { + + /** + * Represents a 1D Segment Tree. + * This is equivalent to your 'Sagara' struct. It manages the columns (X-axis). + */ + public static class SegmentTree1D { + private int n; + private final int[] tree; + + /** + * Initializes the 1D Segment Tree with the nearest power of 2. + * + * @param size The expected number of elements (columns). + */ + public SegmentTree1D(int size) { + n = 1; + while (n < size) { + n *= 2; + } + tree = new int[n * 2]; + } + + /** + * Recursively updates a point in the 1D tree. + */ + private void update(int index, int val, int node, int lx, int rx) { + if (rx - lx == 1) { + tree[node] = val; + return; + } + + int mid = lx + (rx - lx) / 2; + int leftChild = node * 2 + 1; + int rightChild = node * 2 + 2; + + if (index < mid) { + update(index, val, leftChild, lx, mid); + } else { + update(index, val, rightChild, mid, rx); + } + + tree[node] = tree[leftChild] + tree[rightChild]; + } + + /** + * Public wrapper to update a specific index. + * + * @param index The column index to update. + * @param val The new value. + */ + public void update(int index, int val) { + update(index, val, 0, 0, n); + } + + /** + * Retrieves the exact value at a specific leaf node. + * + * @param index The column index. + * @return The value at the given index. + */ + public int get(int index) { + return query(index, index + 1, 0, 0, n); + } + + /** + * Recursively queries the sum in a 1D range. + */ + private int query(int l, int r, int node, int lx, int rx) { + if (lx >= r || rx <= l) { + return 0; // Out of bounds + } + if (lx >= l && rx <= r) { + return tree[node]; // Fully inside + } + + int mid = lx + (rx - lx) / 2; + int leftSum = query(l, r, node * 2 + 1, lx, mid); + int rightSum = query(l, r, node * 2 + 2, mid, rx); + + return leftSum + rightSum; + } + + /** + * Public wrapper to query the sum in the range [l, r). + * + * @param l Left boundary (inclusive). + * @param r Right boundary (exclusive). + * @return The sum of the range. + */ + public int query(int l, int r) { + return query(l, r, 0, 0, n); + } + } + + // --- Start of 2D Segment Tree (equivalent to 'Sagara2D') --- + + private int n; + private final SegmentTree1D[] tree; + + /** + * Initializes the 2D Segment Tree. + * + * @param rows The number of rows in the matrix. + * @param cols The number of columns in the matrix. + */ + public SegmentTree2D(int rows, int cols) { + n = 1; + while (n < rows) { + n *= 2; + } + tree = new SegmentTree1D[n * 2]; + for (int i = 0; i < n * 2; i++) { + // Every node in the outer tree is a full 1D tree! + tree[i] = new SegmentTree1D(cols); + } + } + + /** + * Recursively updates a point in the 2D grid. + */ + private void update(int row, int col, int val, int node, int lx, int rx) { + if (rx - lx == 1) { + tree[node].update(col, val); + return; + } + + int mid = lx + (rx - lx) / 2; + int leftChild = node * 2 + 1; + int rightChild = node * 2 + 2; + + if (row < mid) { + update(row, col, val, leftChild, lx, mid); + } else { + update(row, col, val, rightChild, mid, rx); + } + + // The value of the current node's column is the sum of its children's column values + int leftVal = tree[leftChild].get(col); + int rightVal = tree[rightChild].get(col); + tree[node].update(col, leftVal + rightVal); + } + + /** + * Public wrapper to update a specific point (row, col). + * + * @param row The row index. + * @param col The column index. + * @param val The new value. + */ + public void update(int row, int col, int val) { + update(row, col, val, 0, 0, n); + } + + /** + * Recursively queries the sum in a submatrix. + */ + private int query(int top, int bottom, int left, int right, int node, int lx, int rx) { + if (lx >= bottom || rx <= top) { + return 0; // Out of bounds + } + if (lx >= top && rx <= bottom) { + // Fully inside the row range, so delegate the column query to the 1D tree + return tree[node].query(left, right); + } + + int mid = lx + (rx - lx) / 2; + int leftSum = query(top, bottom, left, right, node * 2 + 1, lx, mid); + int rightSum = query(top, bottom, left, right, node * 2 + 2, mid, rx); + + return leftSum + rightSum; + } + + /** + * Public wrapper to query the sum of a submatrix. + * Note: boundaries are [top, bottom) and [left, right). + * + * @param top Top row index (inclusive). + * @param bottom Bottom row index (exclusive). + * @param left Left column index (inclusive). + * @param right Right column index (exclusive). + * @return The sum of the submatrix. + */ + public int query(int top, int bottom, int left, int right) { + return query(top, bottom, left, right, 0, 0, n); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java new file mode 100644 index 000000000000..db081da2550a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java @@ -0,0 +1,71 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class SegmentTree2DTest { + + @Test + void testInitialEmptyQueries() { + SegmentTree2D segmentTree = new SegmentTree2D(4, 4); + + // Initial tree should return 0 for any query + assertEquals(0, segmentTree.query(0, 4, 0, 4)); + assertEquals(0, segmentTree.query(1, 3, 1, 3)); + } + + @Test + void testUpdateAndPointQuery() { + SegmentTree2D segmentTree = new SegmentTree2D(5, 5); + + segmentTree.update(2, 3, 10); + segmentTree.update(0, 0, 5); + + // Querying single points [row, row+1) x [col, col+1) + assertEquals(10, segmentTree.query(2, 3, 3, 4)); + assertEquals(5, segmentTree.query(0, 1, 0, 1)); + + // Empty point should be 0 + assertEquals(0, segmentTree.query(1, 2, 1, 2)); + } + + @Test + void testSubmatrixQuery() { + SegmentTree2D segmentTree = new SegmentTree2D(4, 4); + + // Matrix simulation: + // [1, 2, 0, 0] + // [3, 4, 0, 0] + // [0, 0, 0, 0] + // [0, 0, 0, 0] + segmentTree.update(0, 0, 1); + segmentTree.update(0, 1, 2); + segmentTree.update(1, 0, 3); + segmentTree.update(1, 1, 4); + + // Top-left 2x2 sum: 1+2+3+4 = 10 + assertEquals(10, segmentTree.query(0, 2, 0, 2)); + + // First row sum: 1+2 = 3 + assertEquals(3, segmentTree.query(0, 1, 0, 4)); + + // Second column sum: 2+4 = 6 + assertEquals(6, segmentTree.query(0, 4, 1, 2)); + } + + @Test + void testUpdateOverwriting() { + SegmentTree2D segmentTree = new SegmentTree2D(3, 3); + + segmentTree.update(1, 1, 5); + assertEquals(5, segmentTree.query(1, 2, 1, 2)); + + // Overwrite the same point + segmentTree.update(1, 1, 20); + assertEquals(20, segmentTree.query(1, 2, 1, 2)); + + // Full matrix sum should just be this point + assertEquals(20, segmentTree.query(0, 3, 0, 3)); + } +} From 7eea9a5101a2347e4bb69e9ae2bc96b1fbd5cef4 Mon Sep 17 00:00:00 2001 From: kadambari25 Date: Mon, 6 Apr 2026 21:40:34 +0200 Subject: [PATCH 60/68] Fix: handle null key in IterativeBinarySearch (#7365) * Fix: handle null key in IterativeBinarySearch * style: fix indentation in IterativeBinarySearch * style: fix indentation in IterativeBinarySearch --------- Co-authored-by: Kadambari --- .../java/com/thealgorithms/searches/IterativeBinarySearch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java index cc0bfb16d26c..d051dbc7b823 100644 --- a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java @@ -33,7 +33,7 @@ public final class IterativeBinarySearch implements SearchAlgorithm { */ @Override public > int find(T[] array, T key) { - if (array == null || array.length == 0) { + if (array == null || array.length == 0 || key == null) { return -1; } From 49f9e1a5a7c041a6b40db52b089b480e897b89b5 Mon Sep 17 00:00:00 2001 From: kadambari25 Date: Wed, 8 Apr 2026 09:10:19 +0200 Subject: [PATCH 61/68] Improve readability in LinearSearch by using local variable (#7367) Co-authored-by: Kadambari --- src/main/java/com/thealgorithms/searches/LinearSearch.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index 77ecece3aaef..3f273e167f0a 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -51,7 +51,8 @@ public > int find(T[] array, T value) { } for (int i = 0; i < array.length; i++) { - if (array[i] != null && array[i].compareTo(value) == 0) { + T currentElement = array[i]; + if (currentElement != null && currentElement.compareTo(value) == 0) { return i; } } From 5b9db677b3fc3f081ad164a2657f018aa578b47f Mon Sep 17 00:00:00 2001 From: Feliphe Jesus Date: Fri, 10 Apr 2026 17:21:48 -0300 Subject: [PATCH 62/68] Refactor Alphabetical implementation and tests (#7370) * Improve Alphabetical implementation, tests and documentation * Workaround SpotBugs false positive in parameterized tests Replaced boolean literals with Boolean.TRUE/FALSE in Arguments.of(...) to avoid SpotBugs warning (NAB_NEEDLESS_BOOLEAN_CONSTANT_CONVERSION). This is a false positive caused by JUnit's Object... varargs requiring auto-boxing. --- .../thealgorithms/strings/Alphabetical.java | 52 ++++++++++++++----- .../strings/AlphabeticalTest.java | 42 ++++++++++++--- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/thealgorithms/strings/Alphabetical.java b/src/main/java/com/thealgorithms/strings/Alphabetical.java index ef2974eb427d..37b1fb068b44 100644 --- a/src/main/java/com/thealgorithms/strings/Alphabetical.java +++ b/src/main/java/com/thealgorithms/strings/Alphabetical.java @@ -1,32 +1,58 @@ package com.thealgorithms.strings; +import java.util.Locale; + /** - * Utility class for checking if a string's characters are in alphabetical order. + * Utility class for checking whether a string's characters are in non-decreasing + * lexicographical order based on Unicode code points (case-insensitive). + *

+ * This does NOT implement language-aware alphabetical ordering (collation rules). + * It simply compares lowercase Unicode character values. *

- * Alphabetical order is a system whereby character strings are placed in order - * based on the position of the characters in the conventional ordering of an - * alphabet. + * Non-letter characters are not allowed and will cause the check to fail. *

- * Reference: Wikipedia: Alphabetical Order + * Reference: + * Wikipedia: Alphabetical order */ public final class Alphabetical { + private Alphabetical() { } /** - * Checks whether the characters in the given string are in alphabetical order. - * Non-letter characters will cause the check to fail. + * Checks whether the characters in the given string are in non-decreasing + * lexicographical order (case-insensitive). + *

+ * Rules: + *

    + *
  • String must not be null or blank
  • + *
  • All characters must be letters
  • + *
  • Comparison is based on lowercase Unicode values
  • + *
  • Order must be non-decreasing (equal or increasing allowed)
  • + *
* - * @param s the input string - * @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false} + * @param s input string + * @return {@code true} if characters are in non-decreasing order, otherwise {@code false} */ public static boolean isAlphabetical(String s) { - s = s.toLowerCase(); - for (int i = 0; i < s.length() - 1; ++i) { - if (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) { + if (s == null || s.isBlank()) { + return false; + } + + String normalized = s.toLowerCase(Locale.ROOT); + + if (!Character.isLetter(normalized.charAt(0))) { + return false; + } + + for (int i = 1; i < normalized.length(); i++) { + char prev = normalized.charAt(i - 1); + char curr = normalized.charAt(i); + + if (!Character.isLetter(curr) || prev > curr) { return false; } } - return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1)); + return true; } } diff --git a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java index 7b41e11ef22f..0c7d7e4701cf 100644 --- a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java +++ b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java @@ -1,15 +1,45 @@ package com.thealgorithms.strings; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -public class AlphabeticalTest { +@DisplayName("Alphabetical.isAlphabetical()") +class AlphabeticalTest { - @ParameterizedTest(name = "\"{0}\" β†’ Expected: {1}") - @CsvSource({"'abcdefghijklmno', true", "'abcdxxxyzzzz', true", "'123a', false", "'abcABC', false", "'abcdefghikjlmno', false", "'aBC', true", "'abc', true", "'xyzabc', false", "'abcxyz', true", "'', false", "'1', false"}) - void testIsAlphabetical(String input, boolean expected) { - assertEquals(expected, Alphabetical.isAlphabetical(input)); + static Stream testCases() { + // Workaround for SpotBugs false positive (NAB_NEEDLESS_BOOLEAN_CONSTANT_CONVERSION) + // due to JUnit Arguments.of(Object...) auto-boxing + return Stream.of(arguments("", Boolean.FALSE, "Should return false for empty string"), arguments(" ", Boolean.FALSE, "Should return false for blank string"), arguments("a1b2", Boolean.FALSE, "Should return false when string contains numbers"), + arguments("abc!DEF", Boolean.FALSE, "Should return false when string contains symbols"), arguments("#abc", Boolean.FALSE, "Should return false when first character is not a letter"), arguments("abc", Boolean.TRUE, "Should return true for non-decreasing order"), + arguments("aBcD", Boolean.TRUE, "Should return true for mixed case increasing sequence"), arguments("a", Boolean.TRUE, "Should return true for single letter"), arguments("'", Boolean.FALSE, "Should return false for single symbol"), + arguments("aabbcc", Boolean.TRUE, "Should return true for repeated letters"), arguments("cba", Boolean.FALSE, "Should return false when order decreases"), arguments("abzba", Boolean.FALSE, "Should return false when middle breaks order")); + } + + private void assertAlphabetical(String input, boolean expected, String message) { + // Arrange & Act + boolean result = Alphabetical.isAlphabetical(input); + + // Assert + assertEquals(expected, result, message); + } + + @Test + @DisplayName("Should return false for null input") + void nullInputTest() { + assertAlphabetical(null, false, "Should return false for null input"); + } + + @ParameterizedTest(name = "{2}") + @MethodSource("testCases") + @DisplayName("Alphabetical cases") + void isAlphabeticalTest(String input, boolean expected, String message) { + assertAlphabetical(input, expected, message); } } From e0a7223ab40f66a87ce4e02a7b4aeca4302902ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 09:34:54 +0200 Subject: [PATCH 63/68] chore(deps): bump actions/github-script from 8 to 9 in /.github/workflows (#7371) chore(deps): bump actions/github-script in /.github/workflows Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' 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/close-failed-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-failed-prs.yml b/.github/workflows/close-failed-prs.yml index 6deea88f0daf..4013e87b6569 100644 --- a/.github/workflows/close-failed-prs.yml +++ b/.github/workflows/close-failed-prs.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close stale PRs - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 1edf319cb3da42c75ac7c5cfeaf1928d409d9153 Mon Sep 17 00:00:00 2001 From: Papichardog Date: Sat, 11 Apr 2026 05:04:33 -0600 Subject: [PATCH 64/68] feat(maths): enhance Average with stream method and improved JavaDoc (#7369) * docs(maths): improve JavaDoc for Average utility class * feat(maths): add stream-based averageStream method and validations * style: fix formatting * style: final formatting fix for CI checks * style: apply official clang-format to Average.java --- .../java/com/thealgorithms/maths/Average.java | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java index a550a7f6504d..cf55af509ccc 100644 --- a/src/main/java/com/thealgorithms/maths/Average.java +++ b/src/main/java/com/thealgorithms/maths/Average.java @@ -1,9 +1,16 @@ package com.thealgorithms.maths; +import java.util.Arrays; +import java.util.OptionalDouble; + /** * A utility class for computing the average of numeric arrays. - * This class provides static methods to calculate the average of arrays - * of both {@code double} and {@code int} values. + * + *

This class provides static methods to calculate the arithmetic mean + * of arrays of both {@code double} and {@code int} values. It also offers + * a Stream-based alternative for modern, declarative usage. + * + *

All methods guard against {@code null} or empty inputs. */ public final class Average { @@ -13,11 +20,14 @@ private Average() { } /** - * Computes the average of a {@code double} array. + * Computes the arithmetic mean of a {@code double} array. + * + *

The average is calculated as the sum of all elements divided + * by the number of elements: {@code avg = Ξ£(numbers[i]) / n}. * - * @param numbers an array of {@code double} values - * @return the average of the given numbers - * @throws IllegalArgumentException if the input array is {@code null} or empty + * @param numbers a non-null, non-empty array of {@code double} values + * @return the arithmetic mean of the given numbers + * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty */ public static double average(double[] numbers) { if (numbers == null || numbers.length == 0) { @@ -31,11 +41,14 @@ public static double average(double[] numbers) { } /** - * Computes the average of an {@code int} array. + * Computes the arithmetic mean of an {@code int} array. + * + *

The sum is accumulated in a {@code long} to prevent integer overflow + * when processing large arrays or large values. * - * @param numbers an array of {@code int} values - * @return the average of the given numbers - * @throws IllegalArgumentException if the input array is {@code null} or empty + * @param numbers a non-null, non-empty array of {@code int} values + * @return the arithmetic mean as a {@code long} (truncated toward zero) + * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty */ public static long average(int[] numbers) { if (numbers == null || numbers.length == 0) { @@ -47,4 +60,21 @@ public static long average(int[] numbers) { } return sum / numbers.length; } + + /** + * Computes the arithmetic mean of a {@code double} array using Java Streams. + * + *

This method is a declarative alternative to {@link #average(double[])}. + * Instead of throwing on empty input, it returns an empty {@link OptionalDouble}, + * following the convention of the Stream API. + * + * @param numbers an array of {@code double} values, may be {@code null} or empty + * @return an {@link OptionalDouble} with the mean, or empty if input is null/empty + */ + public static OptionalDouble averageStream(double[] numbers) { + if (numbers == null || numbers.length == 0) { + return OptionalDouble.empty(); + } + return Arrays.stream(numbers).average(); + } } 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 65/68] 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 66/68] 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 67/68] 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 68/68] 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); + } +}