diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 2e5622f7b51d..1b91763b2d53 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,14 +5,20 @@ updates:
directory: "/"
schedule:
interval: "weekly"
+ cooldown:
+ default-days: 7
- package-ecosystem: "github-actions"
directory: "/.github/workflows/"
schedule:
interval: "daily"
+ cooldown:
+ default-days: 7
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
+ cooldown:
+ default-days: 7
...
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d9cc4c3c35c5..30b46bbf45b5 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -13,4 +13,5 @@ In order to reduce the number of notifications sent to the maintainers, please:
- [ ] All filenames are in PascalCase.
- [ ] All functions and variable names follow Java naming conventions.
- [ ] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
-- [ ] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`
\ No newline at end of file
+- [ ] All new algorithms include a corresponding test class that validates their functionality.
+- [ ] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 39bde758d68e..38d1471dcf13 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,9 +8,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6.1.0
with:
java-version: 21
distribution: 'temurin'
@@ -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@v7
with:
fail_ci_if_error: true
- name: Upload coverage to codecov (with token)
@@ -28,7 +28,7 @@ jobs:
github.repository == 'TheAlgorithms/Java' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
diff --git a/.github/workflows/clang-format-lint.yml b/.github/workflows/clang-format-lint.yml
index ca014e115282..622679f73842 100644
--- a/.github/workflows/clang-format-lint.yml
+++ b/.github/workflows/clang-format-lint.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v7
- uses: DoozyX/clang-format-lint-action@v0.20
with:
source: './src'
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: |
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 3a4cfd829b6b..d9d9ed1e54e9 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -21,16 +21,16 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6.1.0
with:
java-version: 21
distribution: 'temurin'
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4
+ uses: github/codeql-action/init@v4.39.1
with:
languages: 'java-kotlin'
@@ -38,7 +38,7 @@ jobs:
run: mvn --batch-mode --update-snapshots verify
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4
+ uses: github/codeql-action/analyze@v4.39.1
with:
category: "/language:java-kotlin"
@@ -52,15 +52,15 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v7
- name: Initialize CodeQL
- uses: github/codeql-action/init@v4
+ uses: github/codeql-action/init@v4.39.1
with:
languages: 'actions'
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4
+ uses: github/codeql-action/analyze@v4.39.1
with:
category: "/language:actions"
...
diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml
index 3df7c4b1fc9e..962c090ee699 100644
--- a/.github/workflows/infer.yml
+++ b/.github/workflows/infer.yml
@@ -15,42 +15,16 @@ jobs:
run_infer:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v7
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6.1.0
with:
java-version: 21
distribution: 'temurin'
- - name: Set up OCaml
- uses: ocaml/setup-ocaml@v3
- with:
- ocaml-compiler: 5
-
- - name: Get current year/weak
- run: echo "year_week=$(date +'%Y_%U')" >> $GITHUB_ENV
-
- - name: Cache infer build
- id: cache-infer
- uses: actions/cache@v4
- with:
- path: infer
- key: ${{ runner.os }}-infer-${{ env.year_week }}
-
- - name: Build infer
- if: steps.cache-infer.outputs.cache-hit != 'true'
- run: |
- cd ..
- git clone https://github.com/facebook/infer.git
- cd infer
- git checkout 01aaa268f9d38723ba69c139e10f9e2a04b40b1c
- ./build-infer.sh java
- cp -r infer ../Java
-
- - name: Add infer to PATH
- run: |
- echo "infer/bin" >> $GITHUB_PATH
+ - name: Set up inferAdd commentMore actions
+ uses: srz-zumix/setup-infer@v1
- name: Display infer version
run: |
@@ -60,5 +34,5 @@ jobs:
- name: Run infer
run: |
mvn clean
- infer --fail-on-issue --print-logs --no-progress-bar -- mvn test
+ infer --java-version 21 --fail-on-issue --print-logs --no-progress-bar -- mvn test
...
diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml
index f9fb82a2781c..764832b7b6a6 100644
--- a/.github/workflows/project_structure.yml
+++ b/.github/workflows/project_structure.yml
@@ -15,8 +15,8 @@ jobs:
check_structure:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
- - uses: actions/setup-python@v6
+ - uses: actions/checkout@v7
+ - uses: actions/setup-python@v7.1.0
with:
python-version: '3.13'
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index bb613daf8f1d..b961d15c240e 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -11,7 +11,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v10
+ - uses: actions/stale@v11.0.0
with:
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution!'
close-issue-message: 'Please reopen this issue once you have made the required changes. If you need help, feel free to ask in our [Discord](https://the-algorithms.com/discord) server or ping one of the maintainers here. Thank you for your contribution!'
diff --git a/.github/workflows/update-directorymd.yml b/.github/workflows/update-directorymd.yml
index 6692a884a867..3977bfda86bf 100644
--- a/.github/workflows/update-directorymd.yml
+++ b/.github/workflows/update-directorymd.yml
@@ -14,7 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
- uses: actions/checkout@v5
+ uses: actions/checkout@v7
+ with:
+ persist-credentials: false
- name: Run Directory Tree Generator
uses: DenizAltunkapan/directory-tree-generator@v2
@@ -31,7 +33,7 @@ jobs:
git diff --cached --quiet || git commit -m "Update DIRECTORY.md"
- name: Create Pull Request
- uses: peter-evans/create-pull-request@v7
+ uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
branch: update-directory
diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile
index 4195f928d1bc..a8951da7de26 100644
--- a/.gitpod.dockerfile
+++ b/.gitpod.dockerfile
@@ -1,4 +1,4 @@
-FROM gitpod/workspace-java-21:2025-10-06-13-14-25
+FROM gitpod/workspace-java-21:2025-11-14-10-05-32
ENV LLVM_SCRIPT="tmp_llvm.sh"
diff --git a/.inferconfig b/.inferconfig
index 6af4f9e2e818..cf26212feac5 100644
--- a/.inferconfig
+++ b/.inferconfig
@@ -1,6 +1,8 @@
{
"report-block-list-path-regex": [
"src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java",
+ "src/main/java/com/thealgorithms/compression/ArithmeticCoding.java",
+ "src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java",
"src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java",
"src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java",
"src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java",
@@ -8,15 +10,19 @@
"src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java",
"src/main/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorder.java",
"src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java",
+ "src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java",
"src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java",
"src/main/java/com/thealgorithms/maths/SimpsonIntegration.java",
"src/main/java/com/thealgorithms/others/Dijkstra.java",
"src/main/java/com/thealgorithms/sorts/TopologicalSort.java",
"src/main/java/com/thealgorithms/strings/AhoCorasick.java",
+ "src/test/java/com/thealgorithms/compression/ShannonFanoTest.java",
"src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java",
"src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java",
"src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java",
"src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java",
+ "src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java",
+ "src/test/java/com/thealgorithms/others/HuffmanTest.java",
"src/test/java/com/thealgorithms/searches/QuickSelectTest.java",
"src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java",
"src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java"
diff --git a/DIRECTORY.md b/DIRECTORY.md
index eca2453fad0f..37d9b3c295e2 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -13,6 +13,7 @@
- π [AllPathsFromSourceToTarget](src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java)
- π [ArrayCombination](src/main/java/com/thealgorithms/backtracking/ArrayCombination.java)
- π [Combination](src/main/java/com/thealgorithms/backtracking/Combination.java)
+ - π [CombinationSum](src/main/java/com/thealgorithms/backtracking/CombinationSum.java)
- π [CrosswordSolver](src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java)
- π [FloodFill](src/main/java/com/thealgorithms/backtracking/FloodFill.java)
- π [KnightsTour](src/main/java/com/thealgorithms/backtracking/KnightsTour.java)
@@ -23,11 +24,14 @@
- π [Permutation](src/main/java/com/thealgorithms/backtracking/Permutation.java)
- π [PowerSum](src/main/java/com/thealgorithms/backtracking/PowerSum.java)
- π [SubsequenceFinder](src/main/java/com/thealgorithms/backtracking/SubsequenceFinder.java)
+ - π [SudokuSolver](src/main/java/com/thealgorithms/backtracking/SudokuSolver.java)
+ - π [UniquePermutation](src/main/java/com/thealgorithms/backtracking/UniquePermutation.java)
- π [WordPatternMatcher](src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java)
- π [WordSearch](src/main/java/com/thealgorithms/backtracking/WordSearch.java)
- π **bitmanipulation**
- π [BcdConversion](src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java)
- π [BinaryPalindromeCheck](src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java)
+ - π [BitRotate](src/main/java/com/thealgorithms/bitmanipulation/BitRotate.java)
- π [BitSwap](src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java)
- π [BitwiseGCD](src/main/java/com/thealgorithms/bitmanipulation/BitwiseGCD.java)
- π [BooleanAlgebraGates](src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java)
@@ -76,6 +80,7 @@
- π [ECC](src/main/java/com/thealgorithms/ciphers/ECC.java)
- π [HillCipher](src/main/java/com/thealgorithms/ciphers/HillCipher.java)
- π [MonoAlphabetic](src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java)
+ - π [OneTimePadCipher](src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java)
- π [PermutationCipher](src/main/java/com/thealgorithms/ciphers/PermutationCipher.java)
- π [PlayfairCipher](src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java)
- π [Polybius](src/main/java/com/thealgorithms/ciphers/Polybius.java)
@@ -93,6 +98,12 @@
- π [LFSR](src/main/java/com/thealgorithms/ciphers/a5/LFSR.java)
- π [Utils](src/main/java/com/thealgorithms/ciphers/a5/Utils.java)
- π **compression**
+ - π [ArithmeticCoding](src/main/java/com/thealgorithms/compression/ArithmeticCoding.java)
+ - π [BurrowsWheelerTransform](src/main/java/com/thealgorithms/compression/BurrowsWheelerTransform.java)
+ - π [LZ77](src/main/java/com/thealgorithms/compression/LZ77.java)
+ - π [LZ78](src/main/java/com/thealgorithms/compression/LZ78.java)
+ - π [LZW](src/main/java/com/thealgorithms/compression/LZW.java)
+ - π [MoveToFront](src/main/java/com/thealgorithms/compression/MoveToFront.java)
- π [RunLengthEncoding](src/main/java/com/thealgorithms/compression/RunLengthEncoding.java)
- π [ShannonFano](src/main/java/com/thealgorithms/compression/ShannonFano.java)
- π **conversions**
@@ -125,6 +136,7 @@
- π [PhoneticAlphabetConverter](src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java)
- π [RgbHsvConversion](src/main/java/com/thealgorithms/conversions/RgbHsvConversion.java)
- π [RomanToInteger](src/main/java/com/thealgorithms/conversions/RomanToInteger.java)
+ - π [TemperatureConverter](src/main/java/com/thealgorithms/conversions/TemperatureConverter.java)
- π [TimeConverter](src/main/java/com/thealgorithms/conversions/TimeConverter.java)
- π [TurkishToLatinConversion](src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java)
- π [UnitConversions](src/main/java/com/thealgorithms/conversions/UnitConversions.java)
@@ -173,7 +185,6 @@
- π [FordFulkerson](src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java)
- π [Graphs](src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java)
- π [HamiltonianCycle](src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java)
- - π [HierholzerAlgorithm](src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java)
- π [JohnsonsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java)
- π [KahnsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java)
- π [Kosaraju](src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java)
@@ -190,6 +201,7 @@
- π [GenericHashMapUsingArrayList](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java)
- π [HashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java)
- π [HashMapCuckooHashing](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java)
+ - π [ImmutableHashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java)
- π [Intersection](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java)
- π [LinearProbingHashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java)
- π [MainCuckooHashing](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MainCuckooHashing.java)
@@ -201,6 +213,7 @@
- π [GenericHeap](src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java)
- π [Heap](src/main/java/com/thealgorithms/datastructures/heaps/Heap.java)
- π [HeapElement](src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java)
+ - π [IndexedPriorityQueue](src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java)
- π [KthElementFinder](src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java)
- π [LeftistHeap](src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java)
- π [MaxHeap](src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java)
@@ -255,8 +268,10 @@
- π [BSTRecursiveGeneric](src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java)
- π [BTree](src/main/java/com/thealgorithms/datastructures/trees/BTree.java)
- π [BinaryTree](src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java)
+ - π [BinaryTreeToString](src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java)
- π [BoundaryTraversal](src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java)
- π [CeilInBinarySearchTree](src/main/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTree.java)
+ - π [CentroidDecomposition](src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java)
- π [CheckBinaryTreeIsValidBST](src/main/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBST.java)
- π [CheckIfBinaryTreeBalanced](src/main/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalanced.java)
- π [CheckTreeIsSymmetric](src/main/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetric.java)
@@ -276,6 +291,7 @@
- π [SameTreesCheck](src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java)
- π [SegmentTree](src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java)
- π [SplayTree](src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java)
+ - π [ThreadedBinaryTree](src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java)
- π [Treap](src/main/java/com/thealgorithms/datastructures/trees/Treap.java)
- π [TreeRandomNode](src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java)
- π [Trie](src/main/java/com/thealgorithms/datastructures/trees/Trie.java)
@@ -358,8 +374,10 @@
- π [WildcardMatching](src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java)
- π [WineProblem](src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java)
- π **geometry**
+ - π [BentleyOttmann](src/main/java/com/thealgorithms/geometry/BentleyOttmann.java)
- π [BresenhamLine](src/main/java/com/thealgorithms/geometry/BresenhamLine.java)
- π [ConvexHull](src/main/java/com/thealgorithms/geometry/ConvexHull.java)
+ - π [DDALine](src/main/java/com/thealgorithms/geometry/DDALine.java)
- π [GrahamScan](src/main/java/com/thealgorithms/geometry/GrahamScan.java)
- π [Haversine](src/main/java/com/thealgorithms/geometry/Haversine.java)
- π [MidpointCircle](src/main/java/com/thealgorithms/geometry/MidpointCircle.java)
@@ -372,10 +390,14 @@
- π [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
- π [Edmonds](src/main/java/com/thealgorithms/graph/Edmonds.java)
- π [EdmondsKarp](src/main/java/com/thealgorithms/graph/EdmondsKarp.java)
+ - π [GomoryHuTree](src/main/java/com/thealgorithms/graph/GomoryHuTree.java)
+ - π [HierholzerAlgorithm](src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java)
+ - π [HierholzerEulerianPath](src/main/java/com/thealgorithms/graph/HierholzerEulerianPath.java)
- π [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
- π [HungarianAlgorithm](src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java)
- π [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
- π [PushRelabel](src/main/java/com/thealgorithms/graph/PushRelabel.java)
+ - π [StoerWagner](src/main/java/com/thealgorithms/graph/StoerWagner.java)
- π [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
- π [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
- π [YensKShortestPaths](src/main/java/com/thealgorithms/graph/YensKShortestPaths.java)
@@ -409,6 +431,7 @@
- π [AbsoluteMax](src/main/java/com/thealgorithms/maths/AbsoluteMax.java)
- π [AbsoluteMin](src/main/java/com/thealgorithms/maths/AbsoluteMin.java)
- π [AbsoluteValue](src/main/java/com/thealgorithms/maths/AbsoluteValue.java)
+ - π [AbundantNumber](src/main/java/com/thealgorithms/maths/AbundantNumber.java)
- π [AliquotSum](src/main/java/com/thealgorithms/maths/AliquotSum.java)
- π [AmicableNumber](src/main/java/com/thealgorithms/maths/AmicableNumber.java)
- π [Area](src/main/java/com/thealgorithms/maths/Area.java)
@@ -420,6 +443,7 @@
- π [BinomialCoefficient](src/main/java/com/thealgorithms/maths/BinomialCoefficient.java)
- π [CatalanNumbers](src/main/java/com/thealgorithms/maths/CatalanNumbers.java)
- π [Ceil](src/main/java/com/thealgorithms/maths/Ceil.java)
+ - π [ChebyshevIteration](src/main/java/com/thealgorithms/maths/ChebyshevIteration.java)
- π [ChineseRemainderTheorem](src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java)
- π [CircularConvolutionFFT](src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java)
- π [CollatzConjecture](src/main/java/com/thealgorithms/maths/CollatzConjecture.java)
@@ -434,10 +458,11 @@
- π [EulerMethod](src/main/java/com/thealgorithms/maths/EulerMethod.java)
- π [EulerPseudoprime](src/main/java/com/thealgorithms/maths/EulerPseudoprime.java)
- π [EulersFunction](src/main/java/com/thealgorithms/maths/EulersFunction.java)
+ - π [EvilNumber](src/main/java/com/thealgorithms/maths/EvilNumber.java)
+ - π [ExtendedEuclideanAlgorithm](src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java)
- π [FFT](src/main/java/com/thealgorithms/maths/FFT.java)
- π [FFTBluestein](src/main/java/com/thealgorithms/maths/FFTBluestein.java)
- π [Factorial](src/main/java/com/thealgorithms/maths/Factorial.java)
- - π [FactorialRecursion](src/main/java/com/thealgorithms/maths/FactorialRecursion.java)
- π [FastExponentiation](src/main/java/com/thealgorithms/maths/FastExponentiation.java)
- π [FastInverseSqrt](src/main/java/com/thealgorithms/maths/FastInverseSqrt.java)
- π [FibonacciJavaStreams](src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java)
@@ -471,6 +496,7 @@
- π [LinearDiophantineEquationsSolver](src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java)
- π [LongDivision](src/main/java/com/thealgorithms/maths/LongDivision.java)
- π [LucasSeries](src/main/java/com/thealgorithms/maths/LucasSeries.java)
+ - π [LuckyNumber](src/main/java/com/thealgorithms/maths/LuckyNumber.java)
- π [MagicSquare](src/main/java/com/thealgorithms/maths/MagicSquare.java)
- π [MathBuilder](src/main/java/com/thealgorithms/maths/MathBuilder.java)
- π [MaxValue](src/main/java/com/thealgorithms/maths/MaxValue.java)
@@ -478,6 +504,7 @@
- π [Median](src/main/java/com/thealgorithms/maths/Median.java)
- π [MinValue](src/main/java/com/thealgorithms/maths/MinValue.java)
- π [Mode](src/main/java/com/thealgorithms/maths/Mode.java)
+ - π [Neville](src/main/java/com/thealgorithms/maths/Neville.java)
- π [NonRepeatingElement](src/main/java/com/thealgorithms/maths/NonRepeatingElement.java)
- π [NthUglyNumber](src/main/java/com/thealgorithms/maths/NthUglyNumber.java)
- π [NumberOfDigits](src/main/java/com/thealgorithms/maths/NumberOfDigits.java)
@@ -493,6 +520,7 @@
- π [PiNilakantha](src/main/java/com/thealgorithms/maths/PiNilakantha.java)
- π [PollardRho](src/main/java/com/thealgorithms/maths/PollardRho.java)
- π [Pow](src/main/java/com/thealgorithms/maths/Pow.java)
+ - π [PowerOfFour](src/main/java/com/thealgorithms/maths/PowerOfFour.java)
- π [PowerOfTwoOrNot](src/main/java/com/thealgorithms/maths/PowerOfTwoOrNot.java)
- π [PowerUsingRecursion](src/main/java/com/thealgorithms/maths/PowerUsingRecursion.java)
- π **Prime**
@@ -511,6 +539,7 @@
- π [SieveOfAtkin](src/main/java/com/thealgorithms/maths/SieveOfAtkin.java)
- π [SieveOfEratosthenes](src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java)
- π [SimpsonIntegration](src/main/java/com/thealgorithms/maths/SimpsonIntegration.java)
+ - π [SmithNumber](src/main/java/com/thealgorithms/maths/SmithNumber.java)
- π [SolovayStrassenPrimalityTest](src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java)
- π [SquareRootWithBabylonianMethod](src/main/java/com/thealgorithms/maths/SquareRootWithBabylonianMethod.java)
- π [SquareRootWithNewtonRaphsonMethod](src/main/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonMethod.java)
@@ -531,6 +560,7 @@
- π [ZellersCongruence](src/main/java/com/thealgorithms/maths/ZellersCongruence.java)
- π **matrix**
- π [InverseOfMatrix](src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java)
+ - π [LUDecomposition](src/main/java/com/thealgorithms/matrix/LUDecomposition.java)
- π [MatrixMultiplication](src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java)
- π [MatrixRank](src/main/java/com/thealgorithms/matrix/MatrixRank.java)
- π [MatrixTranspose](src/main/java/com/thealgorithms/matrix/MatrixTranspose.java)
@@ -539,6 +569,7 @@
- π [PrintAMatrixInSpiralOrder](src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java)
- π [RotateMatrixBy90Degrees](src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java)
- π [SolveSystem](src/main/java/com/thealgorithms/matrix/SolveSystem.java)
+ - π [StochasticMatrix](src/main/java/com/thealgorithms/matrix/StochasticMatrix.java)
- π **matrixexponentiation**
- π [Fibonacci](src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java)
- π **utils**
@@ -591,17 +622,22 @@
- π [PageRank](src/main/java/com/thealgorithms/others/PageRank.java)
- π [PasswordGen](src/main/java/com/thealgorithms/others/PasswordGen.java)
- π [PerlinNoise](src/main/java/com/thealgorithms/others/PerlinNoise.java)
- - π [PrintAMatrixInSpiralOrder](src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java)
- π [QueueUsingTwoStacks](src/main/java/com/thealgorithms/others/QueueUsingTwoStacks.java)
- π [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)
+ - π [ElasticCollision2D](src/main/java/com/thealgorithms/physics/ElasticCollision2D.java)
+ - π [Gravitation](src/main/java/com/thealgorithms/physics/Gravitation.java)
- π [GroundToGroundProjectileMotion](src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java)
+ - π [Kinematics](src/main/java/com/thealgorithms/physics/Kinematics.java)
+ - π [ProjectileMotion](src/main/java/com/thealgorithms/physics/ProjectileMotion.java)
+ - π [SimplePendulumRK4](src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java)
+ - π [SnellLaw](src/main/java/com/thealgorithms/physics/SnellLaw.java)
+ - π [ThinLens](src/main/java/com/thealgorithms/physics/ThinLens.java)
- π **puzzlesandgames**
- - π [Sudoku](src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java)
- π [TowerOfHanoi](src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java)
- π [WordBoggle](src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java)
- π **randomized**
@@ -613,6 +649,7 @@
- π [ReservoirSampling](src/main/java/com/thealgorithms/randomized/ReservoirSampling.java)
- π **recursion**
- π [DiceThrower](src/main/java/com/thealgorithms/recursion/DiceThrower.java)
+ - π [FactorialRecursion](src/main/java/com/thealgorithms/recursion/FactorialRecursion.java)
- π [FibonacciSeries](src/main/java/com/thealgorithms/recursion/FibonacciSeries.java)
- π [GenerateSubsets](src/main/java/com/thealgorithms/recursion/GenerateSubsets.java)
- π [SylvesterSequence](src/main/java/com/thealgorithms/recursion/SylvesterSequence.java)
@@ -649,7 +686,7 @@
- π [BoyerMoore](src/main/java/com/thealgorithms/searches/BoyerMoore.java)
- π [BreadthFirstSearch](src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java)
- π [DepthFirstSearch](src/main/java/com/thealgorithms/searches/DepthFirstSearch.java)
- - π [ExponentalSearch](src/main/java/com/thealgorithms/searches/ExponentalSearch.java)
+ - π [ExponentialSearch](src/main/java/com/thealgorithms/searches/ExponentialSearch.java)
- π [FibonacciSearch](src/main/java/com/thealgorithms/searches/FibonacciSearch.java)
- π [HowManyTimesRotated](src/main/java/com/thealgorithms/searches/HowManyTimesRotated.java)
- π [InterpolationSearch](src/main/java/com/thealgorithms/searches/InterpolationSearch.java)
@@ -662,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)
@@ -671,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)
@@ -758,6 +793,8 @@
- π [SortStack](src/main/java/com/thealgorithms/stacks/SortStack.java)
- π [StackPostfixNotation](src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java)
- π [StackUsingTwoQueues](src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java)
+ - π [TrappingRainwater](src/main/java/com/thealgorithms/stacks/TrappingRainwater.java)
+ - π [ValidParentheses](src/main/java/com/thealgorithms/stacks/ValidParentheses.java)
- π **strings**
- π [AhoCorasick](src/main/java/com/thealgorithms/strings/AhoCorasick.java)
- π [Alphabetical](src/main/java/com/thealgorithms/strings/Alphabetical.java)
@@ -772,13 +809,14 @@
- π [Isogram](src/main/java/com/thealgorithms/strings/Isogram.java)
- π [Isomorphic](src/main/java/com/thealgorithms/strings/Isomorphic.java)
- π [KMP](src/main/java/com/thealgorithms/strings/KMP.java)
+ - π [LengthOfLastWord](src/main/java/com/thealgorithms/strings/LengthOfLastWord.java)
- π [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)
+ - π [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)
@@ -792,8 +830,8 @@
- π [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**
- π [ZigZagPattern](src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java)
- π **tree**
@@ -808,6 +846,7 @@
- π **backtracking**
- π [AllPathsFromSourceToTargetTest](src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java)
- π [ArrayCombinationTest](src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java)
+ - π [CombinationSumTest](src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java)
- π [CombinationTest](src/test/java/com/thealgorithms/backtracking/CombinationTest.java)
- π [CrosswordSolverTest](src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java)
- π [FloodFillTest](src/test/java/com/thealgorithms/backtracking/FloodFillTest.java)
@@ -819,11 +858,14 @@
- π [PermutationTest](src/test/java/com/thealgorithms/backtracking/PermutationTest.java)
- π [PowerSumTest](src/test/java/com/thealgorithms/backtracking/PowerSumTest.java)
- π [SubsequenceFinderTest](src/test/java/com/thealgorithms/backtracking/SubsequenceFinderTest.java)
+ - π [SudokuSolverTest](src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java)
+ - π [UniquePermutationTest](src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java)
- π [WordPatternMatcherTest](src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java)
- π [WordSearchTest](src/test/java/com/thealgorithms/backtracking/WordSearchTest.java)
- π **bitmanipulation**
- π [BcdConversionTest](src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java)
- π [BinaryPalindromeCheckTest](src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java)
+ - π [BitRotateTest](src/test/java/com/thealgorithms/bitmanipulation/BitRotateTest.java)
- π [BitSwapTest](src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java)
- π [BitwiseGCDTest](src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java)
- π [BooleanAlgebraGatesTest](src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java)
@@ -871,6 +913,7 @@
- π [ECCTest](src/test/java/com/thealgorithms/ciphers/ECCTest.java)
- π [HillCipherTest](src/test/java/com/thealgorithms/ciphers/HillCipherTest.java)
- π [MonoAlphabeticTest](src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java)
+ - π [OneTimePadCipherTest](src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java)
- π [PermutationCipherTest](src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java)
- π [PlayfairTest](src/test/java/com/thealgorithms/ciphers/PlayfairTest.java)
- π [PolybiusTest](src/test/java/com/thealgorithms/ciphers/PolybiusTest.java)
@@ -884,6 +927,12 @@
- π [A5KeyStreamGeneratorTest](src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java)
- π [LFSRTest](src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
- π **compression**
+ - π [ArithmeticCodingTest](src/test/java/com/thealgorithms/compression/ArithmeticCodingTest.java)
+ - π [BurrowsWheelerTransformTest](src/test/java/com/thealgorithms/compression/BurrowsWheelerTransformTest.java)
+ - π [LZ77Test](src/test/java/com/thealgorithms/compression/LZ77Test.java)
+ - π [LZ78Test](src/test/java/com/thealgorithms/compression/LZ78Test.java)
+ - π [LZWTest](src/test/java/com/thealgorithms/compression/LZWTest.java)
+ - π [MoveToFrontTest](src/test/java/com/thealgorithms/compression/MoveToFrontTest.java)
- π [RunLengthEncodingTest](src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java)
- π [ShannonFanoTest](src/test/java/com/thealgorithms/compression/ShannonFanoTest.java)
- π **conversions**
@@ -914,6 +963,7 @@
- π [OctalToHexadecimalTest](src/test/java/com/thealgorithms/conversions/OctalToHexadecimalTest.java)
- π [PhoneticAlphabetConverterTest](src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java)
- π [RomanToIntegerTest](src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java)
+ - π [TemperatureConverterTest](src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java)
- π [TimeConverterTest](src/test/java/com/thealgorithms/conversions/TimeConverterTest.java)
- π [TurkishToLatinConversionTest](src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java)
- π [UnitConversionsTest](src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java)
@@ -947,8 +997,10 @@
- π [DynamicArrayTest](src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
- π **graphs**
- π [AStarTest](src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java)
+ - π [BellmanFordTest](src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java)
- π [BipartiteGraphDFSTest](src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java)
- π [BoruvkaAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java)
+ - π [ConnectedComponentTest](src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java)
- π [DialsAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DialsAlgorithmTest.java)
- π [DijkstraAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
- π [DijkstraOptimizedAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java)
@@ -971,6 +1023,7 @@
- π [GenericHashMapUsingArrayTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java)
- π [HashMapCuckooHashingTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java)
- π [HashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java)
+ - π [ImmutableHashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java)
- π [IntersectionTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java)
- π [LinearProbingHashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java)
- π [MajorityElementTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java)
@@ -979,6 +1032,7 @@
- π [FibonacciHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java)
- π [GenericHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java)
- π [HeapElementTest](src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java)
+ - π [IndexedPriorityQueueTest](src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java)
- π [KthElementFinderTest](src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java)
- π [LeftistHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java)
- π [MaxHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java)
@@ -1028,8 +1082,10 @@
- π [BSTRecursiveTest](src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveTest.java)
- π [BTreeTest](src/test/java/com/thealgorithms/datastructures/trees/BTreeTest.java)
- π [BinaryTreeTest](src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java)
+ - π [BinaryTreeToStringTest](src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java)
- π [BoundaryTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java)
- π [CeilInBinarySearchTreeTest](src/test/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTreeTest.java)
+ - π [CentroidDecompositionTest](src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java)
- π [CheckBinaryTreeIsValidBSTTest](src/test/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBSTTest.java)
- π [CheckIfBinaryTreeBalancedTest](src/test/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalancedTest.java)
- π [CheckTreeIsSymmetricTest](src/test/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetricTest.java)
@@ -1043,6 +1099,7 @@
- π [QuadTreeTest](src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java)
- π [SameTreesCheckTest](src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java)
- π [SplayTreeTest](src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java)
+ - π [ThreadedBinaryTreeTest](src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java)
- π [TreapTest](src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java)
- π [TreeTestUtils](src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java)
- π [TrieTest](src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java)
@@ -1114,8 +1171,10 @@
- π [WildcardMatchingTest](src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java)
- π [WineProblemTest](src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java)
- π **geometry**
+ - π [BentleyOttmannTest](src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java)
- π [BresenhamLineTest](src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java)
- π [ConvexHullTest](src/test/java/com/thealgorithms/geometry/ConvexHullTest.java)
+ - π [DDALineTest](src/test/java/com/thealgorithms/geometry/DDALineTest.java)
- π [GrahamScanTest](src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
- π [HaversineTest](src/test/java/com/thealgorithms/geometry/HaversineTest.java)
- π [MidpointCircleTest](src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java)
@@ -1128,9 +1187,14 @@
- π [DinicTest](src/test/java/com/thealgorithms/graph/DinicTest.java)
- π [EdmondsKarpTest](src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java)
- π [EdmondsTest](src/test/java/com/thealgorithms/graph/EdmondsTest.java)
+ - π [GomoryHuTreeTest](src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java)
+ - π [HierholzerAlgorithmTest](src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java)
+ - π [HierholzerEulerianPathTest](src/test/java/com/thealgorithms/graph/HierholzerEulerianPathTest.java)
- π [HopcroftKarpTest](src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java)
+ - π [HungarianAlgorithmTest](src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java)
- π [PredecessorConstrainedDfsTest](src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java)
- π [PushRelabelTest](src/test/java/com/thealgorithms/graph/PushRelabelTest.java)
+ - π [StoerWagnerTest](src/test/java/com/thealgorithms/graph/StoerWagnerTest.java)
- π [StronglyConnectedComponentOptimizedTest](src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java)
- π [TravelingSalesmanTest](src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java)
- π [YensKShortestPathsTest](src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java)
@@ -1161,6 +1225,7 @@
- π [AbsoluteMaxTest](src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java)
- π [AbsoluteMinTest](src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java)
- π [AbsoluteValueTest](src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java)
+ - π [AbundantNumberTest](src/test/java/com/thealgorithms/maths/AbundantNumberTest.java)
- π [AliquotSumTest](src/test/java/com/thealgorithms/maths/AliquotSumTest.java)
- π [AmicableNumberTest](src/test/java/com/thealgorithms/maths/AmicableNumberTest.java)
- π [AreaTest](src/test/java/com/thealgorithms/maths/AreaTest.java)
@@ -1172,6 +1237,7 @@
- π [BinomialCoefficientTest](src/test/java/com/thealgorithms/maths/BinomialCoefficientTest.java)
- π [CatalanNumbersTest](src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java)
- π [CeilTest](src/test/java/com/thealgorithms/maths/CeilTest.java)
+ - π [ChebyshevIterationTest](src/test/java/com/thealgorithms/maths/ChebyshevIterationTest.java)
- π [ChineseRemainderTheoremTest](src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java)
- π [CollatzConjectureTest](src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java)
- π [CombinationsTest](src/test/java/com/thealgorithms/maths/CombinationsTest.java)
@@ -1185,8 +1251,9 @@
- π [EulerMethodTest](src/test/java/com/thealgorithms/maths/EulerMethodTest.java)
- π [EulerPseudoprimeTest](src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java)
- π [EulersFunctionTest](src/test/java/com/thealgorithms/maths/EulersFunctionTest.java)
+ - π [EvilNumberTest](src/test/java/com/thealgorithms/maths/EvilNumberTest.java)
+ - π [ExtendedEuclideanAlgorithmTest](src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java)
- π [FFTTest](src/test/java/com/thealgorithms/maths/FFTTest.java)
- - π [FactorialRecursionTest](src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java)
- π [FactorialTest](src/test/java/com/thealgorithms/maths/FactorialTest.java)
- π [FastExponentiationTest](src/test/java/com/thealgorithms/maths/FastExponentiationTest.java)
- π [FastInverseSqrtTests](src/test/java/com/thealgorithms/maths/FastInverseSqrtTests.java)
@@ -1211,6 +1278,7 @@
- π [HarshadNumberTest](src/test/java/com/thealgorithms/maths/HarshadNumberTest.java)
- π [HeronsFormulaTest](src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java)
- π [JosephusProblemTest](src/test/java/com/thealgorithms/maths/JosephusProblemTest.java)
+ - π [JugglerSequenceTest](src/test/java/com/thealgorithms/maths/JugglerSequenceTest.java)
- π [KaprekarNumbersTest](src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java)
- π [KaratsubaMultiplicationTest](src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java)
- π [KeithNumberTest](src/test/java/com/thealgorithms/maths/KeithNumberTest.java)
@@ -1220,12 +1288,14 @@
- π [LinearDiophantineEquationsSolverTest](src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java)
- π [LongDivisionTest](src/test/java/com/thealgorithms/maths/LongDivisionTest.java)
- π [LucasSeriesTest](src/test/java/com/thealgorithms/maths/LucasSeriesTest.java)
+ - π [LuckyNumberTest](src/test/java/com/thealgorithms/maths/LuckyNumberTest.java)
- π [MathBuilderTest](src/test/java/com/thealgorithms/maths/MathBuilderTest.java)
- π [MaxValueTest](src/test/java/com/thealgorithms/maths/MaxValueTest.java)
- π [MeansTest](src/test/java/com/thealgorithms/maths/MeansTest.java)
- π [MedianTest](src/test/java/com/thealgorithms/maths/MedianTest.java)
- π [MinValueTest](src/test/java/com/thealgorithms/maths/MinValueTest.java)
- π [ModeTest](src/test/java/com/thealgorithms/maths/ModeTest.java)
+ - π [NevilleTest](src/test/java/com/thealgorithms/maths/NevilleTest.java)
- π [NonRepeatingElementTest](src/test/java/com/thealgorithms/maths/NonRepeatingElementTest.java)
- π [NthUglyNumberTest](src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java)
- π [NumberOfDigitsTest](src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java)
@@ -1240,6 +1310,7 @@
- π [PiApproximationTest](src/test/java/com/thealgorithms/maths/PiApproximationTest.java)
- π [PollardRhoTest](src/test/java/com/thealgorithms/maths/PollardRhoTest.java)
- π [PowTest](src/test/java/com/thealgorithms/maths/PowTest.java)
+ - π [PowerOfFourTest](src/test/java/com/thealgorithms/maths/PowerOfFourTest.java)
- π [PowerOfTwoOrNotTest](src/test/java/com/thealgorithms/maths/PowerOfTwoOrNotTest.java)
- π [PowerUsingRecursionTest](src/test/java/com/thealgorithms/maths/PowerUsingRecursionTest.java)
- π [PronicNumberTest](src/test/java/com/thealgorithms/maths/PronicNumberTest.java)
@@ -1249,6 +1320,7 @@
- π [SecondMinMaxTest](src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java)
- π [SieveOfAtkinTest](src/test/java/com/thealgorithms/maths/SieveOfAtkinTest.java)
- π [SieveOfEratosthenesTest](src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java)
+ - π [SmithNumberTest](src/test/java/com/thealgorithms/maths/SmithNumberTest.java)
- π [SolovayStrassenPrimalityTestTest](src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java)
- π [SquareFreeIntegerTest](src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java)
- π [SquareRootWithNewtonRaphsonTestMethod](src/test/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonTestMethod.java)
@@ -1275,14 +1347,16 @@
- π [PrimeFactorizationTest](src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java)
- π **matrix**
- π [InverseOfMatrixTest](src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java)
+ - π [LUDecompositionTest](src/test/java/com/thealgorithms/matrix/LUDecompositionTest.java)
- π [MatrixMultiplicationTest](src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java)
- π [MatrixRankTest](src/test/java/com/thealgorithms/matrix/MatrixRankTest.java)
- π [MatrixTransposeTest](src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java)
- π [MatrixUtilTest](src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java)
- π [MedianOfMatrixTest](src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java)
- π [MirrorOfMatrixTest](src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java)
+ - π [PrintAMatrixInSpiralOrderTest](src/test/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrderTest.java)
- π [SolveSystemTest](src/test/java/com/thealgorithms/matrix/SolveSystemTest.java)
- - π [TestPrintMatrixInSpiralOrder](src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java)
+ - π [StochasticMatrixTest](src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java)
- π **misc**
- π [ColorContrastRatioTest](src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java)
- π [MapReduceTest](src/test/java/com/thealgorithms/misc/MapReduceTest.java)
@@ -1316,22 +1390,26 @@
- π [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)
- π [PerlinNoiseTest](src/test/java/com/thealgorithms/others/PerlinNoiseTest.java)
- π [QueueUsingTwoStacksTest](src/test/java/com/thealgorithms/others/QueueUsingTwoStacksTest.java)
- π [SkylineProblemTest](src/test/java/com/thealgorithms/others/SkylineProblemTest.java)
- - π [TestPrintMatrixInSpiralOrder](src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.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)
+ - π [ElasticCollision2DTest](src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java)
+ - π [GravitationTest](src/test/java/com/thealgorithms/physics/GravitationTest.java)
- π [GroundToGroundProjectileMotionTest](src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java)
+ - π [KinematicsTest](src/test/java/com/thealgorithms/physics/KinematicsTest.java)
+ - π [ProjectileMotionTest](src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java)
+ - π [SimplePendulumRK4Test](src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java)
+ - π [SnellLawTest](src/test/java/com/thealgorithms/physics/SnellLawTest.java)
+ - π [ThinLensTest](src/test/java/com/thealgorithms/physics/ThinLensTest.java)
- π **puzzlesandgames**
- - π [SudokuTest](src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java)
- π [TowerOfHanoiTest](src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java)
- π [WordBoggleTest](src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java)
- π **randomized**
@@ -1343,6 +1421,7 @@
- π [ReservoirSamplingTest](src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java)
- π **recursion**
- π [DiceThrowerTest](src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java)
+ - π [FactorialRecursionTest](src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java)
- π [FibonacciSeriesTest](src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java)
- π [GenerateSubsetsTest](src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java)
- π [SylvesterSequenceTest](src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java)
@@ -1392,7 +1471,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)
@@ -1401,7 +1479,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)
@@ -1487,6 +1564,8 @@
- π [SortStackTest](src/test/java/com/thealgorithms/stacks/SortStackTest.java)
- π [StackPostfixNotationTest](src/test/java/com/thealgorithms/stacks/StackPostfixNotationTest.java)
- π [StackUsingTwoQueuesTest](src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java)
+ - π [TrappingRainwaterTest](src/test/java/com/thealgorithms/stacks/TrappingRainwaterTest.java)
+ - π [ValidParenthesesTest](src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java)
- π **strings**
- π [AhoCorasickTest](src/test/java/com/thealgorithms/strings/AhoCorasickTest.java)
- π [AlphabeticalTest](src/test/java/com/thealgorithms/strings/AlphabeticalTest.java)
@@ -1500,10 +1579,10 @@
- π [HorspoolSearchTest](src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java)
- π [IsogramTest](src/test/java/com/thealgorithms/strings/IsogramTest.java)
- π [IsomorphicTest](src/test/java/com/thealgorithms/strings/IsomorphicTest.java)
+ - π [LengthOfLastWordTest](src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java)
- π [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)
@@ -1519,8 +1598,8 @@
- π [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**
- π [ZigZagPatternTest](src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java)
- π **tree**
diff --git a/README-ko.md b/README-ko.md
deleted file mode 100644
index 4f8cab92fc42..000000000000
--- a/README-ko.md
+++ /dev/null
@@ -1,191 +0,0 @@
-# μκ³ λ¦¬μ¦ - μλ°
-
-## μ΄ [κ°λ°λΈλ°μΉ](https://github.com/TheAlgorithms/Java/tree/Development)λ κΈ°μ‘΄ νλ‘μ νΈλ₯Ό Java νλ‘μ νΈ κ΅¬μ‘°λ‘ μ¬κ°λ°νκΈ° μν΄ μμ±λμλ€. κΈ°μ¬λλ₯Ό μν΄ κ°λ° μ§μ¬λ‘ μ νν μ μλ€. μμΈν λ΄μ©μ μ΄ λ¬Έμ λ₯Ό μ°Έμ‘°νμμμ€. 컨νΈλ¦¬λ·°μ
μ μν΄ [κ°λ°λΈλ°μΉ](https://github.com/TheAlgorithms/Java/tree/Development)λ‘ μ νν μ μλ€. μμΈν λ΄μ©μ [μ΄ μ΄μ](https://github.com/TheAlgorithms/Java/issues/474)λ₯Ό μ°Έκ³ νμμμ€.
-
-### μλ°λ‘ ꡬνλ λͺ¨λ μκ³ λ¦¬μ¦λ€ (κ΅μ‘μ©)
-
-μ΄κ²λ€μ λ¨μ§ μλ²μ μν κ²μ΄λ€. νμ€ μλ° λΌμ΄λΈλ¬λ¦¬μλ μ±λ₯μμ μ΄μ λ‘ λ λμ κ²λ€μ΄ ꡬνλμ΄μλ€
-
-## μ λ ¬ μκ³ λ¦¬μ¦
-
-### Bubble(λ²λΈ μ λ ¬)
-
-![alt text][bubble-image]
-
-From [Wikipedia][bubble-wiki]: λ²λΈ μνΈ(sinking sorλΌκ³ λ λΆλ¦¬μ)λ 리μ€νΈλ₯Ό λ°λ³΅μ μΈ λ¨κ³λ‘ μ κ·Όνμ¬ μ λ ¬νλ€. κ°κ°μ μ§μ λΉκ΅νλ©°, μμκ° μλͺ»λ κ²½μ° κ·Έμ ν μμ΄ν
λ€μ μ€μνλ μκ³ λ¦¬μ¦μ΄λ€. λ μ΄μ μ€μν κ²μ΄ μμ λκΉμ§ λ°λ³΅νλ©°, λ°λ³΅μ΄ λλ¨μ 리μ€νΈκ° μ λ ¬λμμμ μλ―Ένλ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n^2)
-- μ΅κ³ μ μ±λ₯ O(n)
-- νκ· μ±λ₯ O(n^2)
-
-###### View the algorithm in [action][bubble-toptal]
-
-### Insertion(μ½μ
μ λ ¬)
-
-![alt text][insertion-image]
-
-From [Wikipedia][insertion-wiki]: μ½μ
μ λ ¬μ μ΅μ’
μ λ ¬λ λ°°μ΄(λλ 리μ€νΈ)μ νλ²μ νλμ© κ΅¬μΆνλ μκ³ λ¦¬μ¦μ΄λ€. μ΄κ²μ ν° λ¦¬μ€νΈμμ λ λμ μκ³ λ¦¬μ¦μΈ ν΅ μνΈ, ν μνΈ, λλ λ¨Έμ§ μνΈλ³΄λ€ ν¨μ¬ μμ’μ ν¨μ¨μ κ°μ§λ€. κ·Έλ¦Όμμ κ° λ§λλ μ λ ¬ν΄μΌ νλ λ°°μ΄μ μμλ₯Ό λνλΈλ€. μλ¨κ³Ό λ λ²μ§Έ μλ¨ λ§λμ 첫 λ²μ§Έ κ΅μ°¨μ μμ λ°μνλ κ²μ λ λ²μ§Έ μμκ° μ²« λ²μ§Έ μμλ³΄λ€ λ λμ μ°μ μμλ₯Ό κ°μ§κΈ° λλ¬Έμ λ§λλ‘ νμλλ μ΄λ¬ν μμλ₯Ό κ΅νν κ²μ΄λ€. μ΄ λ°©λ²μ λ°λ³΅νλ©΄ μ½μ
μ λ ¬μ΄ μλ£λλ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n^2)
-- μ΅κ³ μ μ±λ₯ O(n)
-- νκ· O(n^2)
-
-###### View the algorithm in [action][insertion-toptal]
-
-### Merge(ν©λ³ μ λ ¬)
-
-![alt text][merge-image]
-
-From [Wikipedia][merge-wiki]: μ»΄ν¨ν° κ³Όνμμ, ν©λ³ μ λ ¬μ ν¨μ¨μ μΈ, λ²μ©μ μΈ, λΉκ΅ κΈ°λ° μ λ ¬ μκ³ λ¦¬μ¦μ΄λ€. λλΆλΆμ ꡬνμ μμ μ μΈ λΆλ₯λ₯Ό μ΄λ£¨λλ°, μ΄κ²μ ꡬνμ΄ μ λ ¬λ μΆλ ₯μ λμΌν μμμ μ
λ ₯ μμλ₯Ό μ μ§νλ€λ κ²μ μλ―Ένλ€. ν©λ³ μ λ ¬μ 1945λ
μ John von Neumannμ΄ λ°λͺ
ν λΆν μ 볡 μκ³ λ¦¬μ¦μ΄λ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n log n) (μΌλ°μ )
-- μ΅κ³ μ μ±λ₯ O(n log n)
-- νκ· O(n log n)
-
-###### View the algorithm in [action][merge-toptal]
-
-### Quick(ν΅ μ λ ¬)
-
-![alt text][quick-image]
-
-From [Wikipedia][quick-wiki]: ν΅ μ λ ¬sometimes called partition-exchange sort)μ ν¨μ¨μ μΈ μ λ ¬ μκ³ λ¦¬μ¦μΌλ‘, λ°°μ΄μ μμλ₯Ό μμλλ‘ μ λ ¬νλ 체κ³μ μΈ λ°©λ² μνμ νλ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n^2)
-- μ΅κ³ μ μ±λ₯ O(n log n) or O(n) with three-way partition
-- νκ· O(n log n)
-
-###### View the algorithm in [action][quick-toptal]
-
-### Selection(μ ν μ λ ¬)
-
-![alt text][selection-image]
-
-From [Wikipedia][selection-wiki]: μκ³ λ¦¬μ¦ μ
λ ₯ 리μ€νΈλ₯Ό λ λΆλΆμΌλ‘ λλλ€ : 첫 λΆλΆμ μμ΄ν
λ€μ΄ μ΄λ―Έ μΌμͺ½μμ μ€λ₯Έμͺ½μΌλ‘ μ λ ¬λμλ€. κ·Έλ¦¬κ³ λ¨μ λΆλΆμ μμ΄ν
λ€μ λλ¨Έμ§ νλͺ©μ μ°¨μ§νλ 리μ€νΈμ΄λ€. μ²μμλ μ λ ¬λ 리μ€νΈλ 곡백μ΄κ³ λλ¨Έμ§κ° μ λΆμ΄λ€. μ€λ₯΄μ°¨μ(λλ λ΄λ¦Όμ°¨μ) μκ³ λ¦¬μ¦μ κ°μ₯ μμ μμλ₯Ό μ λ ¬λμ§ μμ 리μ€νΈμμ μ°Ύκ³ μ λ ¬μ΄ μλ κ°μ₯ μΌμͺ½(μ λ ¬λ 리μ€νΈ) 리μ€νΈμ λ°κΎΌλ€. μ΄λ κ² μ€λ₯Έμͺ½μΌλ‘ λμκ°λ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n^2)
-- μ΅κ³ μ μ±λ₯ O(n^2)
-- νκ· O(n^2)
-
-###### View the algorithm in [action][selection-toptal]
-
-### Shell(μ μ λ ¬)
-
-![alt text][shell-image]
-
-From [Wikipedia][shell-wiki]: μ μ λ ¬μ λ©λ¦¬ λ¨μ΄μ Έ μλ νλͺ©μ κ΅νμ νμ©νλ μ½μ
μ’
λ₯μ μΌλ°νμ΄λ€. κ·Έ μμ΄λμ΄λ λͺ¨λ nλ²μ§Έ μμκ° μ λ ¬λ λͺ©λ‘μ μ 곡νλ€λ κ²μ κ³ λ €νμ¬ μ΄λ κ³³μμλ μ§ μμνλλ‘ μμμ λͺ©λ‘μ λ°°μ΄νλ κ²μ΄λ€. μ΄λ¬ν λͺ©λ‘μ h-sortedλ‘ μλ €μ Έ μλ€. λ§μ°¬κ°μ§λ‘, κ°κ° κ°λ³μ μΌλ‘ μ λ ¬λ h μΈν°λ¦¬λΈ λͺ©λ‘μΌλ‘ κ°μ£Όν μ μλ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(nlog2 2n)
-- μ΅κ³ μ μ±λ₯ O(n log n)
-- Average case performance depends on gap sequence
-
-###### View the algorithm in [action][shell-toptal]
-
-### μκ° λ³΅μ‘μ± κ·Έλν
-
-μ λ ¬ μκ³ λ¦¬μ¦μ 볡μ‘μ± λΉκ΅ (λ²λΈ μ λ ¬, μ½μ
μ λ ¬, μ ν μ λ ¬)
-
-[볡μ‘μ± κ·Έλν](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png)
-
----
-
-## κ²μ μκ³ λ¦¬μ¦
-
-### Linear (μ ν νμ)
-
-![alt text][linear-image]
-
-From [Wikipedia][linear-wiki]: μ ν νμ λλ μμ°¨ νμμ λͺ©λ‘ λ΄μμ λͺ©νκ°μ μ°Ύλ λ°©λ²μ΄λ€. μΌμΉ νλͺ©μ΄ λ°κ²¬λκ±°λ λͺ¨λ μμκ° νμλ λκΉμ§ λͺ©λ‘μ κ° μμμ λν΄ λͺ©νκ°μ μμ°¨μ μΌλ‘ κ²μ¬νλ€.
-μ ν κ²μμ μ΅μ
μ μ ν μκ°μΌλ‘ μ€νλλ©° μ΅λ nκ°μ λΉκ΅μμ μ΄λ£¨μ΄μ§λ€. μ¬κΈ°μ nμ λͺ©λ‘μ κΈΈμ΄λ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(n)
-- μ΅κ³ μ μ±λ₯ O(1)
-- νκ· O(n)
-- μ΅μ
μ κ²½μ° κ³΅κ° λ³΅μ‘μ± O(1) iterative
-
-### Binary (μ΄μ§ νμ)
-
-![alt text][binary-image]
-
-From [Wikipedia][binary-wiki]: μ΄μ§ νμ, (also known as half-interval search or logarithmic search), μ μ λ ¬λ λ°°μ΄ λ΄μμ λͺ©νκ°μ μμΉλ₯Ό μ°Ύλ κ²μ μκ³ λ¦¬μ¦μ΄λ€. λͺ©νκ°μ λ°°μ΄μ μ€κ° μμμ λΉκ΅νλ€; λ§μ½ λͺ©νκ°μ΄ λμΌνμ§ μμΌλ©΄, λͺ©νλ¬Όμ μ λ°μ΄ μ κ±°λκ³ κ²μμ΄ μ±κ³΅ν λκΉμ§ λλ¨Έμ§ μ λ°μμ μλλ€.
-
-**μμ±**
-
-- μ΅μ
μ μ±λ₯ O(log n)
-- μ΅κ³ μ μ±λ₯ O(1)
-- νκ· O(log n)
-- μ΅μ
μ κ²½μ° κ³΅κ° λ³΅μ‘μ± O(1)
-
-[bubble-toptal]: https://www.toptal.com/developers/sorting-algorithms/bubble-sort
-[bubble-wiki]: https://en.wikipedia.org/wiki/Bubble_sort
-[bubble-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Bubblesort-edited-color.svg/220px-Bubblesort-edited-color.svg.png "Bubble Sort"
-[insertion-toptal]: https://www.toptal.com/developers/sorting-algorithms/insertion-sort
-[insertion-wiki]: https://en.wikipedia.org/wiki/Insertion_sort
-[insertion-image]: https://upload.wikimedia.org/wikipedia/commons/7/7e/Insertionsort-edited.png "Insertion Sort"
-[quick-toptal]: https://www.toptal.com/developers/sorting-algorithms/quick-sort
-[quick-wiki]: https://en.wikipedia.org/wiki/Quicksort
-[quick-image]: https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif "Quick Sort"
-[merge-toptal]: https://www.toptal.com/developers/sorting-algorithms/merge-sort
-[merge-wiki]: https://en.wikipedia.org/wiki/Merge_sort
-[merge-image]: https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif "Merge Sort"
-[selection-toptal]: https://www.toptal.com/developers/sorting-algorithms/selection-sort
-[selection-wiki]: https://en.wikipedia.org/wiki/Selection_sort
-[selection-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Selection_sort_animation.gif/250px-Selection_sort_animation.gif "Selection Sort Sort"
-[shell-toptal]: https://www.toptal.com/developers/sorting-algorithms/shell-sort
-[shell-wiki]: https://en.wikipedia.org/wiki/Shellsort
-[shell-image]: https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif "Shell Sort"
-[linear-wiki]: https://en.wikipedia.org/wiki/Linear_search
-[linear-image]: http://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif
-[binary-wiki]: https://en.wikipedia.org/wiki/Binary_search_algorithm
-[binary-image]: https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_search_into_array.png
-
----
-
-## λλ¨Έμ§ μκ³ λ¦¬μ¦μ λν λ§ν¬
-
-| μ ν | λ€μ΄λλ―Ήνλ‘κ·Έλλ°(DP) | μνΈ | κ·Έ μΈ κ²λ€ |
-| --------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------ |
-| [Any Base to Any Base](Conversions/AnyBaseToAnyBase.java) | [Coin Change](DynamicProgramming/CoinChange.java) | [Caesar](Ciphers/Caesar.java) | [Heap Sort](Sorts/HeapSort.java) |
-| [Any Base to Decimal](Conversions/AnyBaseToDecimal.java) | [Egg Dropping](DynamicProgramming/EggDropping.java) | [Columnar Transposition Cipher](Ciphers/ColumnarTranspositionCipher.java) | [Palindromic Prime Checker](Misc/PalindromePrime.java) |
-| [Binary to Decimal](Conversions/BinaryToDecimal.java) | [Fibonacci](DynamicProgramming/Fibonacci.java) | [RSA](Ciphers/RSA.java) | More soon... |
-| [Binary to HexaDecimal](Conversions/BinaryToHexadecimal.java) | [Kadane Algorithm](DynamicProgramming/KadaneAlgorithm.java) | more coming soon... |
-| [Binary to Octal](Conversions/BinaryToOctal.java) | [Knapsack](DynamicProgramming/Knapsack.java) |
-| [Decimal To Any Base](Conversions/DecimalToAnyBase.java) | [Longest Common Subsequence](DynamicProgramming/LongestCommonSubsequence.java) |
-| [Decimal To Binary](Conversions/DecimalToBinary.java) | [Longest Increasing Subsequence](DynamicProgramming/LongestIncreasingSubsequence.java) |
-| [Decimal To Hexadecimal](Conversions/DecimalToHexaDecimal.java) | [Rod Cutting](DynamicProgramming/RodCutting.java) |
-| and much more... | and more... |
-
-### μλ£ κ΅¬μ‘°
-
-| κ·Έλν | ν | 리μ€νΈ | ν |
-| ------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------- |
-| | [λΉ ν μμΈμ²λ¦¬](DataStructures/Heaps/EmptyHeapException.java) | [μν μ°κ²°λ¦¬μ€νΈ](DataStructures/Lists/CircleLinkedList.java) | [μ λλ¦ μ΄λ μ΄ λ¦¬μ€νΈ ν](DataStructures/Queues/GenericArrayListQueue.java) |
-| | [ν](DataStructures/Heaps/Heap.java) | [μ΄μ€ μ°κ²°λ¦¬μ€νΈ](DataStructures/Lists/DoublyLinkedList.java) | [ν](DataStructures/Queues/Queues.java) |
-| [κ·Έλν](DataStructures/Graphs/Graphs.java) | [ν μμ](DataStructures/Heaps/HeapElement.java) | [λ¨μ μ°κ²°λ¦¬μ€νΈ](DataStructures/Lists/SinglyLinkedList.java) |
-| [ν¬λ£¨μ€μΉΌ μκ³ λ¦¬μ¦](DataStructures/Graphs/Kruskal.java) | [μ΅λν](DataStructures/Heaps/MaxHeap.java) |
-| [νλ ¬ κ·Έλν](DataStructures/Graphs/MatrixGraphs.java) | [μ΅μν](DataStructures/Heaps/MinHeap.java) |
-| [νλ¦Ό μ΅μμ μ₯νΈλ¦¬](DataStructures/Graphs/PrimMST.java) |
-
-| μ€ν | νΈλ¦¬ |
-| --------------------------------------------------------------- | ------------------------------------------------- |
-| [λ
Έλ μ€ν](DataStructures/Stacks/NodeStack.java) | [AVL νΈλ¦¬](DataStructures/Trees/AVLTree.java) |
-| [μ°κ²°λ¦¬μ€νΈ μ€ν](DataStructures/Stacks/StackOfLinkedList.java) | [μ΄μ§ νΈλ¦¬](DataStructures/Trees/BinaryTree.java) |
-| [μ€ν](DataStructures/Stacks) | And much more... |
-
-- [Bags](DataStructures/Bags/Bag.java)
-- [Buffer](DataStructures/Buffers/CircularBuffer.java)
-- [HashMap](DataStructures/HashMap/Hashing/HashMap.java)
--
diff --git a/README.md b/README.md
index d60d5104c385..91766e523bca 100644
--- a/README.md
+++ b/README.md
@@ -17,4 +17,4 @@ These implementations are intended for learning purposes. As such, they may be l
Please read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute to this project.
## Algorithms
-Our [directory](DIRECTORY.md) has the full list of applications.
+Our [directory](DIRECTORY.md) contains the full list of algorithms implemented in Java.
diff --git a/pmd-exclude.properties b/pmd-exclude.properties
index a3c95b12fa4b..26653d9dc17b 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
@@ -92,9 +86,8 @@ com.thealgorithms.others.MosAlgorithm=UselessMainMethod
com.thealgorithms.others.PageRank=UselessMainMethod,UselessParentheses
com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses
com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses
-com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses
+com.thealgorithms.datastructures.trees.TrieAutocomplete=UselessMainMethod,UselessParentheses
com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod
-com.thealgorithms.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
diff --git a/pom.xml b/pom.xml
index 7e936967208b..21dd79ad17b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
This filter applies an exponential moving average to a sequence of audio
+ *
+ * This filter applies an exponential moving average to a sequence of audio
* signal values, making it useful for smoothing out rapid fluctuations.
* The smoothing factor (alpha) controls the degree of smoothing.
*
- * Based on the definition from
+ *
+ * Based on the definition from
* Wikipedia link.
*/
public class EMAFilter {
private final double alpha;
private double emaValue;
+
/**
* Constructs an EMA filter with a given smoothing factor.
*
@@ -26,14 +29,17 @@ public EMAFilter(double alpha) {
this.alpha = alpha;
this.emaValue = 0.0;
}
+
/**
* Applies the EMA filter to an audio signal array.
+ * EMA formula:
+ * EMA = alpha * currentSample + (1 - alpha) * previousEMA
*
* @param audioSignal Array of audio samples to process
* @return Array of processed (smoothed) samples
*/
public double[] apply(double[] audioSignal) {
- if (audioSignal.length == 0) {
+ if (audioSignal == null || audioSignal.length == 0) {
return new double[0];
}
double[] emaSignal = new double[audioSignal.length];
diff --git a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
index c35a36d97a57..269880b8ddae 100644
--- a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
+++ b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java
@@ -4,11 +4,36 @@
import java.util.List;
/**
- * Program description - To find all possible paths from source to destination
- * Wikipedia
+ * Finds all possible simple paths from a given source vertex to a destination vertex
+ * in a directed graph using backtracking.
*
- * @author Siddhant Swarup Mallick
+ * This algorithm performs a Depth First Search (DFS) traversal while keeping track
+ * of visited vertices to avoid cycles. Whenever the destination vertex is reached,
+ * the current path is stored as one valid path. Key Characteristics: Time Complexity: Space Complexity: This implementation is intended for educational purposes. Given an {@code n x n} binary maze where {@code 1} represents an open cell
+ * and {@code 0} represents a blocked cell, find all paths for a rat starting at
+ * the top-left cell {@code (0, 0)} to reach the bottom-right cell {@code (n-1, n-1)}.
+ *
+ * The rat can move in four directions: Up (U), Down (D), Left (L), Right (R).
+ * Each cell may be visited at most once per path.
+ *
+ * Time Complexity: O(4^(nΒ²)) in the worst case (four choices per cell).
+ * Space Complexity: O(nΒ²) for the visited matrix and recursion stack.
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * > 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
> getNQueensArrangements(int queens) {
}
public static void placeQueens(final int queens) {
- List
> arrangements = new ArrayList
>();
+ List
> arrangements = new ArrayList<>();
getSolution(queens, arrangements, new int[queens], 0);
if (arrangements.isEmpty()) {
- System.out.println("There is no way to place " + queens + " queens on board of size " + queens + "x" + queens);
+ System.out.println(" no way to place " + queens + " queens on board of size " + queens + "x" + queens);
} else {
System.out.println("Arrangement for placing " + queens + " queens");
}
@@ -59,15 +76,15 @@ public static void placeQueens(final int queens) {
/**
* This is backtracking function which tries to place queen recursively
*
- * @param boardSize: size of chess board
- * @param solutions: this holds all possible arrangements
- * @param columns: columns[i] = rowId where queen is placed in ith column.
+ * @param boardSize: size of chess board
+ * @param solutions: this holds all possible arrangements
+ * @param columns: columns[i] = rowId where queen is placed in ith column.
* @param columnIndex: This is the column in which queen is being placed
*/
private static void getSolution(int boardSize, List
> solutions, int[] columns, int columnIndex) {
if (columnIndex == boardSize) {
// this means that all queens have been placed
- List
> solutions, int
// This loop tries to place queen in a row one by one
for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
columns[columnIndex] = rowIndex;
- if (isPlacedCorrectly(columns, rowIndex, columnIndex)) {
- // If queen is placed successfully at rowIndex in column=columnIndex then try
- // placing queen in next column
- getSolution(boardSize, solutions, columns, columnIndex + 1);
- }
- }
- }
- /**
- * This function checks if queen can be placed at row = rowIndex in column =
- * columnIndex safely
- *
- * @param columns: columns[i] = rowId where queen is placed in ith column.
- * @param rowIndex: row in which queen has to be placed
- * @param columnIndex: column in which queen is being placed
- * @return true: if queen can be placed safely false: otherwise
- */
- private static boolean isPlacedCorrectly(int[] columns, int rowIndex, int columnIndex) {
- for (int i = 0; i < columnIndex; i++) {
- int diff = Math.abs(columns[i] - rowIndex);
- if (diff == 0 || columnIndex - i == diff) {
- return false;
+ // Skip current position if row or diagonal is already occupied
+ boolean isROp = OCROWS.contains(rowIndex);
+
+ boolean isDOp = OCDIAG.contains(rowIndex - columnIndex) || OCANTIDIAG.contains(rowIndex + columnIndex);
+
+ if (isROp || isDOp) {
+ continue;
}
+
+ // Mark current row and diagonal as occupied
+ OCROWS.add(rowIndex);
+ OCDIAG.add(rowIndex - columnIndex);
+ OCANTIDIAG.add(rowIndex + columnIndex);
+
+ // Move to the next column after placing current queen
+ getSolution(boardSize, solutions, columns, columnIndex + 1);
+
+ // Backtrack by removing current queen
+
+ OCROWS.remove(rowIndex);
+ OCDIAG.remove(rowIndex - columnIndex);
+ OCANTIDIAG.remove(rowIndex + columnIndex);
}
- return true;
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java
new file mode 100644
index 000000000000..183b4bbd97f8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java
@@ -0,0 +1,119 @@
+package com.thealgorithms.backtracking;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rat in a Maze Problem using Backtracking.
+ *
+ *
+ * maze = { {1, 0, 0, 0},
+ * {1, 1, 0, 1},
+ * {0, 1, 0, 0},
+ * {0, 1, 1, 1} }
+ * Output: ["DDRDRR", "DRDDRR"] (two valid paths)
+ *
+ *
+ * @see Maze solving algorithm
+ * @author the-Sunny-Sharma (GitHub)
+ */
+public final class RatInAMaze {
+
+ private RatInAMaze() {
+ }
+
+ /**
+ * Finds all paths from the top-left to the bottom-right of the given maze.
+ *
+ * @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked
+ * @return a sorted list of all valid path strings using directions D, L, R, U;
+ * an empty list if no path exists
+ * @throws IllegalArgumentException if the maze is null, empty, or not square
+ */
+ public static List
+ * 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: + *
The private key consists of {@code t = 2^Ο} random 32-byte secrets and the public key contains + * their SHA-256 hashes. To sign, the SHA-256 digest of the message is split into {@code k} chunks of + * {@code Ο} bits (most significant bit first); each chunk is an index into the key, and the secrets + * at those {@code k} indices form the signature. The verifier derives the same indices and checks + * that each revealed secret hashes to the matching public-key value. The same index may appear more + * than once, as in the original scheme. Signing is deterministic. + * + *
HORS is a few-time scheme: each signature reveals {@code k} secrets, so after {@code r} + * signatures an attacker knows at most {@code rΒ·k} of them. If all indices of another message fall + * into that set, its signature can be forged, so security decreases with every signature. + * + *
HORST compresses the large public key with a Merkle tree. FORS, used in SPHINCS+ and SLH-DSA + * (FIPS 205), uses a forest of trees instead, which also removes the repeated-index weakness. This + * implementation is educational and must not be used in production. + * + *
References: Reyzin and Reyzin, Better than BiBa, + * Wikipedia: Hash-based cryptography + * + * @author dilaraacetin + * @see LamportSignature + * @see WinternitzSignature + * @see MerkleSignatureScheme + */ +public final class HorsSignature { + + // SHA-256 output length in bytes + private static final int HASH_BYTES = 32; + private static final int DIGEST_BITS = 256; + private static final int MIN_T = 16; + private static final int MAX_T = 65536; + private static final int DEFAULT_T = 1024; + private static final int DEFAULT_K = 16; + + private final int k; + private final int tau; + private final byte[][] privateKey; + private final byte[][] publicKey; + + /** + * Generates a new key pair with {@code t = 1024} and {@code k = 16}. + */ + public HorsSignature() { + this(DEFAULT_T, DEFAULT_K); + } + + /** + * Generates a new key pair. + * + * @param t the number of secrets; a power of two between 16 and 65536 + * @param k the number of secrets revealed per signature; {@code kΒ·log2(t)} must not exceed 256 + * @throws IllegalArgumentException if {@code t} or {@code k} is not supported + */ + public HorsSignature(int t, int k) { + this.tau = tauOf(t); + validateK(k, tau); + this.k = k; + SecureRandom secureRandom = new SecureRandom(); + privateKey = new byte[t][HASH_BYTES]; + publicKey = new byte[t][]; + for (int i = 0; i < t; i++) { + secureRandom.nextBytes(privateKey[i]); + publicKey[i] = hash(privateKey[i]); + } + } + + /** + * Returns a copy of the public key. + * + * @return {@code t} hash values, each 32 bytes long + */ + public byte[][] getPublicKey() { + return deepCopy(publicKey); + } + + /** + * Returns the number of secrets revealed per signature. Like {@code t}, it is part of the public + * key and is needed for verification. + * + * @return the parameter {@code k} + */ + public int getK() { + return k; + } + + /** + * Signs a message. Every signature reveals {@code k} secrets, so a key pair should only sign a + * few messages. + * + * @param message the message to sign + * @return the signature: {@code k} secrets of 32 bytes each + * @throws IllegalArgumentException if the message is null + */ + public byte[][] sign(byte[] message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + int[] indices = messageIndices(hash(message), k, tau); + byte[][] signature = new byte[k][]; + for (int j = 0; j < k; j++) { + signature[j] = privateKey[indices[j]].clone(); + } + return signature; + } + + /** + * Verifies a signature against a public key. {@code t} is taken from the public key length. + * {@code k} is part of the public key and must be supplied by the verifier: taking it from the + * signature would let an attacker submit a shorter signature that reveals fewer secrets. + * + * @param message the signed message + * @param signature the signature to check + * @param publicKey the public key of the signer + * @param k the number of secrets per signature used by the signer (see {@link #getK()}) + * @return true if the signature is valid for the message and public key, false otherwise + * @throws IllegalArgumentException if an argument is null, the public key length is not a + * supported {@code t}, {@code k} is not supported for this {@code t}, a value is not 32 + * bytes long, or the signature does not contain exactly {@code k} values + */ + public static boolean verify(byte[] message, byte[][] signature, byte[][] publicKey, int k) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + validateValues(publicKey, "publicKey"); + validateValues(signature, "signature"); + int tau = tauOf(publicKey.length); + validateK(k, tau); + if (signature.length != k) { + throw new IllegalArgumentException("signature must contain exactly " + k + " values, got " + signature.length); + } + + int[] indices = messageIndices(hash(message), k, tau); + for (int j = 0; j < k; j++) { + if (!MessageDigest.isEqual(hash(signature[j]), publicKey[indices[j]])) { + return false; + } + } + return true; + } + + /** + * Splits the first {@code kΒ·Ο} bits of a digest into {@code k} unsigned {@code Ο}-bit indices, + * most significant bit first. Shared by sign and verify. + */ + static int[] messageIndices(byte[] digest, int k, int tau) { + if ((long) k * tau > digest.length * 8L) { + throw new IllegalArgumentException("digest is too short for " + k + " indices of " + tau + " bits"); + } + int[] indices = new int[k]; + for (int j = 0; j < k; j++) { + int index = 0; + for (int i = 0; i < tau; i++) { + int bit = j * tau + i; + // bit 0 is the most significant bit of digest[0]; & 0xFF reads the byte as unsigned + index = (index << 1) | (((digest[bit / 8] & 0xFF) >> (7 - bit % 8)) & 1); + } + indices[j] = index; + } + return indices; + } + + private static int tauOf(int t) { + if (t < MIN_T || t > MAX_T || (t & (t - 1)) != 0) { + throw new IllegalArgumentException("t must be a power of two between " + MIN_T + " and " + MAX_T + ", got " + t); + } + return Integer.numberOfTrailingZeros(t); + } + + private static void validateK(int k, int tau) { + // k > DIGEST_BITS / tau is the same as k * tau > DIGEST_BITS, but cannot overflow + if (k < 1 || k > DIGEST_BITS / tau) { + throw new IllegalArgumentException("k must be at least 1 and k * log2(t) at most " + DIGEST_BITS + ", got k = " + k); + } + } + + private static void validateValues(byte[][] values, String name) { + if (values == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + for (byte[] value : values) { + if (value == null || value.length != HASH_BYTES) { + throw new IllegalArgumentException(name + " values must be exactly " + HASH_BYTES + " bytes long"); + } + } + } + + private static byte[] hash(byte[] data) { + try { + return MessageDigest.getInstance("SHA-256").digest(data); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("SHA-256 is required by the Java SE specification", e); + } + } + + private static byte[][] deepCopy(byte[][] values) { + byte[][] copy = new byte[values.length][]; + for (int i = 0; i < values.length; i++) { + copy[i] = values[i].clone(); + } + return copy; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/LWEEncryption.java b/src/main/java/com/thealgorithms/ciphers/LWEEncryption.java new file mode 100644 index 000000000000..bf552c605af0 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/LWEEncryption.java @@ -0,0 +1,216 @@ +package com.thealgorithms.ciphers; + +import java.security.SecureRandom; + +/** + * Public-key encryption based on the Learning With Errors (LWE) problem, as proposed by Regev (2005). + * + *
Toy parameters for educational purposes only β NOT secure for real-world use. + * + *
The LWE problem: given many noisy samples {@code b = AΒ·s + e (mod q)} with a random matrix + * {@code A} and small random errors {@code e}, it is hard to recover the secret vector {@code s}. + * Without the errors, {@code s} could be found with Gaussian elimination. + * + *
The public key is {@code (A, b)} and the private key is {@code s}. To encrypt a bit, a random + * subset {@code r} of the samples is summed: {@code u = rα΅Β·A} and {@code v = rΒ·b + bitΒ·βq/2β}. + * Decryption computes {@code v - uΒ·s = rΒ·e + bitΒ·βq/2β}: a value close to 0 means bit 0, a value + * close to {@code q/2} means bit 1. + * + *
Decryption is always correct because the remaining noise is bounded: {@code r} is a 0/1 vector + * and {@code |e_i| β€ B}, so {@code |rΒ·e| β€ mΒ·B = 384 < q/4 β 832}. + * + *
The modulus {@code q = 3329} is the one used by Kyber / ML-KEM (FIPS 203), which uses the + * module-lattice version of this construction with discrete Gaussian-like noise and much larger + * parameters. For simplicity, one instance holds both the public and the private key; in real use + * they are kept separately. + * + *
Reference: Wikipedia: Learning with errors + * + * @author dilaraacetin + */ +public final class LWEEncryption { + + // dimension of the secret vector + private static final int N = 32; + // modulus, same as Kyber / ML-KEM + private static final int Q = 3329; + // number of samples in the public key + private static final int M = 128; + // errors are uniform in [-B, B] + private static final int B = 3; + private static final int BITS_PER_BYTE = 8; + + private final SecureRandom random = new SecureRandom(); + private final int[][] a; + private final int[] b; + private final int[] s; + + /** + * Generates a new key pair. + * + * @throws IllegalStateException if the parameters do not guarantee correct decryption + */ + public LWEEncryption() { + checkDecryptionBound(M, B, Q); + s = new int[N]; + for (int j = 0; j < N; j++) { + s[j] = random.nextInt(Q); + } + a = new int[M][N]; + b = new int[M]; + for (int i = 0; i < M; i++) { + long sum = 0; + for (int j = 0; j < N; j++) { + a[i][j] = random.nextInt(Q); + sum += (long) a[i][j] * s[j]; + } + int error = random.nextInt(2 * B + 1) - B; + // b_i = A_iΒ·s + e_i (mod q) + b[i] = (int) Math.floorMod(sum + error, (long) Q); + } + } + + /** + * Encrypts a single bit. Encryption is randomized, so the same bit gives different ciphertexts. + * + * @param bit the bit to encrypt, 0 or 1 + * @return the ciphertext {@code (u, v)} + * @throws IllegalArgumentException if {@code bit} is not 0 or 1 + */ + public Ciphertext encryptBit(int bit) { + if (bit != 0 && bit != 1) { + throw new IllegalArgumentException("bit must be 0 or 1, got " + bit); + } + long[] u = new long[N]; + long v = (long) bit * (Q / 2); + for (int i = 0; i < M; i++) { + // r_i is 0 or 1: add sample i or skip it + if (random.nextBoolean()) { + for (int j = 0; j < N; j++) { + u[j] += a[i][j]; + } + v += b[i]; + } + } + int[] reduced = new int[N]; + for (int j = 0; j < N; j++) { + reduced[j] = (int) Math.floorMod(u[j], (long) Q); + } + return new Ciphertext(reduced, (int) Math.floorMod(v, (long) Q)); + } + + /** + * Decrypts a single bit. + * + * @param ciphertext the ciphertext to decrypt + * @return the decrypted bit, 0 or 1 + * @throws IllegalArgumentException if the ciphertext is null + */ + public int decryptBit(Ciphertext ciphertext) { + if (ciphertext == null) { + throw new IllegalArgumentException("ciphertext must not be null"); + } + long dot = 0; + for (int j = 0; j < N; j++) { + dot += (long) ciphertext.u[j] * s[j]; + } + // d = rΒ·e + bitΒ·βq/2β (mod q); floorMod keeps it non-negative + int d = (int) Math.floorMod(ciphertext.v - dot, (long) Q); + return d >= Q / 4 && d < 3 * Q / 4 ? 1 : 0; + } + + /** + * Encrypts a message bit by bit, most significant bit of each byte first. + * + * @param message the message to encrypt + * @return eight ciphertexts per message byte + * @throws IllegalArgumentException if the message is null or empty + */ + public Ciphertext[] encrypt(byte[] message) { + if (message == null || message.length == 0) { + throw new IllegalArgumentException("message must not be null or empty"); + } + Ciphertext[] ciphertexts = new Ciphertext[message.length * BITS_PER_BYTE]; + for (int i = 0; i < message.length; i++) { + for (int k = 0; k < BITS_PER_BYTE; k++) { + ciphertexts[i * BITS_PER_BYTE + k] = encryptBit((message[i] >> (7 - k)) & 1); + } + } + return ciphertexts; + } + + /** + * Decrypts a message encrypted with {@link #encrypt(byte[])}. + * + * @param ciphertexts eight ciphertexts per message byte + * @return the decrypted message + * @throws IllegalArgumentException if the array is null, empty, contains null, or its length is + * not a multiple of 8 + */ + public byte[] decrypt(Ciphertext[] ciphertexts) { + if (ciphertexts == null || ciphertexts.length == 0) { + throw new IllegalArgumentException("ciphertexts must not be null or empty"); + } + if (ciphertexts.length % BITS_PER_BYTE != 0) { + throw new IllegalArgumentException("ciphertexts length must be a multiple of 8, got " + ciphertexts.length); + } + byte[] message = new byte[ciphertexts.length / BITS_PER_BYTE]; + for (int i = 0; i < message.length; i++) { + int value = 0; + for (int k = 0; k < BITS_PER_BYTE; k++) { + value = (value << 1) | decryptBit(ciphertexts[i * BITS_PER_BYTE + k]); + } + message[i] = (byte) value; + } + return message; + } + + /** + * Checks that the maximum decryption noise {@code mΒ·B} stays below {@code q/4}. + * + * @throws IllegalStateException if decryption could fail with these parameters + */ + static void checkDecryptionBound(int samples, int errorBound, int modulus) { + if (samples * errorBound >= modulus / 4) { + throw new IllegalStateException("m * B must be less than q / 4 for correct decryption"); + } + } + + /** + * An LWE ciphertext {@code (u, v)} for one bit. Immutable: the array is copied on construction + * and on access. + */ + public static final class Ciphertext { + private final int[] u; + private final int v; + + /** + * Creates a ciphertext. + * + * @param u the vector part, of length 32 + * @param v the scalar part + * @throws IllegalArgumentException if {@code u} is null or does not have length 32 + */ + public Ciphertext(int[] u, int v) { + if (u == null || u.length != N) { + throw new IllegalArgumentException("u must contain exactly " + N + " values"); + } + this.u = u.clone(); + this.v = v; + } + + /** + * @return a copy of the vector part + */ + public int[] getU() { + return u.clone(); + } + + /** + * @return the scalar part + */ + public int getV() { + return v; + } + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/LamportSignature.java b/src/main/java/com/thealgorithms/ciphers/LamportSignature.java new file mode 100644 index 000000000000..c4a6459fd4c3 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/LamportSignature.java @@ -0,0 +1,145 @@ +package com.thealgorithms.ciphers; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * Lamport one-time signatures (OTS) are a hash-based post-quantum signature scheme. + * + *
The scheme is based on a private key consisting of 256 pairs of random 32-byte secrets, + * one pair per bit position of a SHA-256 digest. The public key contains the SHA-256 hashes of + * those secrets. + * + *
To sign a message, the algorithm hashes the message and reveals the secret corresponding to + * each bit value in the digest. Verification recomputes the digest and checks that each revealed + * secret hashes to the expected public-key value. + * + *
This implementation is educational and intentionally keeps dependencies minimal. Each key pair + * is strictly one-time: signing a second message with the same key is rejected. + * + *
Reference: Wikipedia: Lamport signature + */ +public final class LamportSignature { + + private static final int DIGEST_BITS = 256; + private static final int SECRET_BYTES = 32; + private static final int NUM_VALUES = 2; + + private LamportSignature() { + } + + /** + * A Lamport key pair containing both the private and public material. + */ + public static final class KeyPair { + private final byte[][][] privateKey; + private final byte[][][] publicKey; + private boolean used; + + private KeyPair(byte[][][] privateKey, byte[][][] publicKey) { + this.privateKey = privateKey; + this.publicKey = publicKey; + } + + /** + * Signs a message using this key pair. + * + * @param message the message to sign + * @return the signature bytes; exactly 256 secret values ordered by digest bit positions + * @throws IllegalArgumentException if the message is null + * @throws IllegalStateException if this key pair has already been used to sign a message + */ + public byte[] sign(byte[] message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (used) { + throw new IllegalStateException("This Lamport key pair can only sign one message"); + } + used = true; + + byte[] digest = sha256(message); + byte[] signature = new byte[DIGEST_BITS * SECRET_BYTES]; + for (int bit = 0; bit < DIGEST_BITS; bit++) { + int value = (digest[bit / 8] >> (7 - (bit % 8))) & 0x01; + byte[] secret = privateKey[bit][value]; + System.arraycopy(secret, 0, signature, bit * SECRET_BYTES, SECRET_BYTES); + } + return signature; + } + + /** + * Verifies a message signature against this public key. + * + * @param message the message to verify + * @param signature the signature to verify + * @return true if the signature is valid for the provided message and public key + * @throws IllegalArgumentException if message or signature is null or malformed + */ + public boolean verify(byte[] message, byte[] signature) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (signature == null) { + throw new IllegalArgumentException("signature must not be null"); + } + if (signature.length != DIGEST_BITS * SECRET_BYTES) { + throw new IllegalArgumentException("signature length must be exactly 8192 bytes"); + } + + byte[] digest = sha256(message); + for (int bit = 0; bit < DIGEST_BITS; bit++) { + int value = (digest[bit / 8] >> (7 - (bit % 8))) & 0x01; + byte[] revealedSecret = Arrays.copyOfRange(signature, bit * SECRET_BYTES, (bit + 1) * SECRET_BYTES); + byte[] expectedHash = publicKey[bit][value]; + byte[] actualHash = sha256(revealedSecret); + if (!MessageDigest.isEqual(actualHash, expectedHash)) { + return false; + } + } + return true; + } + + public byte[][][] getPrivateKey() { + return privateKey; + } + + public byte[][][] getPublicKey() { + return publicKey; + } + } + + /** + * Generates a new Lamport key pair. + * + * @return a fresh Lamport key pair + */ + public static KeyPair generateKeyPair() { + SecureRandom secureRandom = new SecureRandom(); + byte[][][] privateKey = new byte[DIGEST_BITS][NUM_VALUES][SECRET_BYTES]; + byte[][][] publicKey = new byte[DIGEST_BITS][NUM_VALUES][SECRET_BYTES]; + + for (int bit = 0; bit < DIGEST_BITS; bit++) { + for (int value = 0; value < NUM_VALUES; value++) { + secureRandom.nextBytes(privateKey[bit][value]); + publicKey[bit][value] = sha256(privateKey[bit][value]); + } + } + return new KeyPair(privateKey, publicKey); + } + + static byte[] sha256(byte[] input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(input); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("SHA-256 is required by the Java SE specification", e); + } + } + + static byte[] sha256(byte[] message, int offset, int length) { + return sha256(Arrays.copyOfRange(message, offset, offset + length)); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/MerkleSignatureScheme.java b/src/main/java/com/thealgorithms/ciphers/MerkleSignatureScheme.java new file mode 100644 index 000000000000..783dc2863d7e --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/MerkleSignatureScheme.java @@ -0,0 +1,261 @@ +package com.thealgorithms.ciphers; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * The Merkle signature scheme (MSS) turns a one-time signature scheme into a many-time scheme. + * + *
Key generation creates {@code 2^h} Winternitz one-time key pairs and hashes each WOTS public + * key into a leaf of a binary hash tree of height {@code h}. The tree root is the single MSS public + * key. Every signature uses the next unused leaf, so one key pair can sign {@code 2^h} messages. + * + *
A signature contains the leaf index, the WOTS signature, the WOTS public key of that leaf and + * the authentication path: the {@code h} sibling hashes needed to recompute the root from the leaf. + * The verifier checks the WOTS signature, hashes the WOTS public key into the leaf and walks up the + * tree with the authentication path. Including the WOTS public key in the signature is an + * educational simplification; XMSS instead recomputes it from the WOTS signature. + * + *
MSS is the core of XMSS (RFC 8391). This implementation is educational and must not be used in + * production. + * + *
Reference: Wikipedia: Merkle signature scheme + * + * @author dilaraacetin + * @see WinternitzSignature + * @see LamportSignature + */ +public final class MerkleSignatureScheme { + + // SHA-256 output length in bytes + private static final int HASH_BYTES = 32; + private static final int MIN_HEIGHT = 2; + private static final int MAX_HEIGHT = 10; + private static final int DEFAULT_HEIGHT = 4; + private static final int DEFAULT_W = 16; + + private final int height; + private final WinternitzSignature[] oneTimeKeys; + // tree[level][node]: level 0 holds the leaves, level h holds the root + private final byte[][][] tree; + private int nextLeafIndex; + + /** + * Generates a new key pair with height {@code h = 4} (16 signatures) and {@code w = 16}. + */ + public MerkleSignatureScheme() { + this(DEFAULT_HEIGHT, DEFAULT_W); + } + + /** + * Generates a new key pair that can sign {@code 2^h} messages. + * + * @param h the tree height; must be between 2 and 10 + * @param w the Winternitz parameter of the one-time keys; must be 4, 16 or 256 + * @throws IllegalArgumentException if {@code h} or {@code w} is not supported + */ + public MerkleSignatureScheme(int h, int w) { + if (h < MIN_HEIGHT || h > MAX_HEIGHT) { + throw new IllegalArgumentException("h must be between " + MIN_HEIGHT + " and " + MAX_HEIGHT + ", got " + h); + } + this.height = h; + int leafCount = 1 << h; + oneTimeKeys = new WinternitzSignature[leafCount]; + tree = new byte[h + 1][][]; + tree[0] = new byte[leafCount][]; + for (int i = 0; i < leafCount; i++) { + oneTimeKeys[i] = new WinternitzSignature(w); + tree[0][i] = hashPublicKey(oneTimeKeys[i].getPublicKey()); + } + for (int level = 1; level <= h; level++) { + tree[level] = new byte[tree[level - 1].length / 2][]; + for (int i = 0; i < tree[level].length; i++) { + tree[level][i] = hash(tree[level - 1][2 * i], tree[level - 1][2 * i + 1]); + } + } + } + + /** + * Returns a copy of the public key, which is the root of the Merkle tree. + * + * @return the 32-byte tree root + */ + public byte[] getPublicKey() { + return tree[height][0].clone(); + } + + /** + * Returns how many messages this key pair can still sign. + * + * @return the number of unused one-time keys + */ + public int remainingSignatures() { + return oneTimeKeys.length - nextLeafIndex; + } + + /** + * Signs a message with the next unused one-time key. + * + * @param message the message to sign + * @return the Merkle signature + * @throws IllegalArgumentException if the message is null + * @throws IllegalStateException if all {@code 2^h} one-time keys have been used + */ + public MerkleSignature sign(byte[] message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (nextLeafIndex >= oneTimeKeys.length) { + throw new IllegalStateException("All " + oneTimeKeys.length + " signatures of this key pair have been used"); + } + int leafIndex = nextLeafIndex++; + WinternitzSignature oneTimeKey = oneTimeKeys[leafIndex]; + byte[][] wotsSignature = oneTimeKey.sign(message); + + // collect the sibling of the current node on every level, from the leaf up to the root + byte[][] authPath = new byte[height][]; + int index = leafIndex; + for (int level = 0; level < height; level++) { + authPath[level] = tree[level][index ^ 1]; + index >>= 1; + } + return new MerkleSignature(leafIndex, wotsSignature, oneTimeKey.getPublicKey(), authPath); + } + + /** + * Verifies a Merkle signature against a public key (tree root). The tree height is taken from + * the length of the authentication path. + * + * @param message the signed message + * @param signature the signature to check + * @param publicKey the tree root of the signer + * @param w the Winternitz parameter used to create the key pair; must be 4, 16 or 256 + * @return true if the signature is valid for the message and public key, false otherwise + * @throws IllegalArgumentException if an argument is null, {@code w} is unsupported, or the + * public key or WOTS part of the signature is malformed + */ + public static boolean verify(byte[] message, MerkleSignature signature, byte[] publicKey, int w) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (signature == null) { + throw new IllegalArgumentException("signature must not be null"); + } + if (publicKey == null || publicKey.length != HASH_BYTES) { + throw new IllegalArgumentException("publicKey must be exactly " + HASH_BYTES + " bytes long"); + } + if (!WinternitzSignature.verify(message, signature.wotsSignature, signature.wotsPublicKey, w)) { + return false; + } + + byte[] node = hashPublicKey(signature.wotsPublicKey); + int index = signature.leafIndex; + for (byte[] sibling : signature.authPath) { + // an even index is a left child, an odd index a right child + node = (index & 1) == 0 ? hash(node, sibling) : hash(sibling, node); + index >>= 1; + } + return MessageDigest.isEqual(node, publicKey); + } + + /** + * A Merkle signature: leaf index, WOTS signature, WOTS public key and authentication path. + * All arrays are copied on construction and on access. + */ + public static final class MerkleSignature { + private final int leafIndex; + private final byte[][] wotsSignature; + private final byte[][] wotsPublicKey; + private final byte[][] authPath; + + /** + * Creates a signature from its parts. + * + * @param leafIndex the index of the leaf used for signing + * @param wotsSignature the Winternitz signature of the message + * @param wotsPublicKey the Winternitz public key of the leaf + * @param authPath the sibling hashes from the leaf up to the root + * @throws IllegalArgumentException if an argument is null, the authentication path length is + * not between 2 and 10, an authentication path value is not 32 bytes long, or the leaf + * index is outside the tree + */ + public MerkleSignature(int leafIndex, byte[][] wotsSignature, byte[][] wotsPublicKey, byte[][] authPath) { + if (authPath == null || authPath.length < MIN_HEIGHT || authPath.length > MAX_HEIGHT) { + throw new IllegalArgumentException("authPath must contain between " + MIN_HEIGHT + " and " + MAX_HEIGHT + " values"); + } + for (byte[] sibling : authPath) { + if (sibling == null || sibling.length != HASH_BYTES) { + throw new IllegalArgumentException("authPath values must be exactly " + HASH_BYTES + " bytes long"); + } + } + if (leafIndex < 0 || leafIndex >= 1 << authPath.length) { + throw new IllegalArgumentException("leafIndex must be between 0 and " + ((1 << authPath.length) - 1) + ", got " + leafIndex); + } + this.leafIndex = leafIndex; + this.wotsSignature = deepCopy(wotsSignature, "wotsSignature"); + this.wotsPublicKey = deepCopy(wotsPublicKey, "wotsPublicKey"); + this.authPath = deepCopy(authPath, "authPath"); + } + + /** + * @return the index of the leaf used for signing + */ + public int getLeafIndex() { + return leafIndex; + } + + /** + * @return a copy of the Winternitz signature + */ + public byte[][] getWotsSignature() { + return deepCopy(wotsSignature, "wotsSignature"); + } + + /** + * @return a copy of the Winternitz public key of the leaf + */ + public byte[][] getWotsPublicKey() { + return deepCopy(wotsPublicKey, "wotsPublicKey"); + } + + /** + * @return a copy of the authentication path, ordered from the leaf level up to the root + */ + public byte[][] getAuthPath() { + return deepCopy(authPath, "authPath"); + } + } + + /** + * Hashes a WOTS public key into a Merkle leaf. Shared by key generation and verification. + */ + private static byte[] hashPublicKey(byte[][] wotsPublicKey) { + return hash(wotsPublicKey); + } + + private static byte[] hash(byte[]... parts) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + for (byte[] part : parts) { + digest.update(part); + } + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("SHA-256 is required by the Java SE specification", e); + } + } + + private static byte[][] deepCopy(byte[][] values, String name) { + if (values == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + byte[][] copy = new byte[values.length][]; + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException(name + " values must not be null"); + } + copy[i] = values[i].clone(); + } + return copy; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java new file mode 100644 index 000000000000..7733f5cb46f2 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java @@ -0,0 +1,89 @@ +package com.thealgorithms.ciphers; + +import java.security.SecureRandom; +import java.util.Objects; + +/** + * One-Time Pad (OTP) cipher implementation. + * + *
The One-Time Pad is information-theoretically secure if: + *
This implementation is for educational purposes only and should not be + * used in production systems. + */ +public final class OneTimePadCipher { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private OneTimePadCipher() { + // utility class + } + + /** + * Generates a random key of the given length in bytes. + * + * @param length the length of the key in bytes, must be non-negative + * @return a new random key + * @throws IllegalArgumentException if length is negative + */ + public static byte[] generateKey(int length) { + if (length < 0) { + throw new IllegalArgumentException("length must be non-negative"); + } + byte[] key = new byte[length]; + RANDOM.nextBytes(key); + return key; + } + + /** + * Encrypts the given plaintext bytes using the provided key. + *
The key length must be exactly the same as the plaintext length. + * + * @param plaintext the plaintext bytes, must not be {@code null} + * @param key the one-time pad key bytes, must not be {@code null} + * @return the ciphertext bytes + * @throws IllegalArgumentException if the key length does not match plaintext length + * @throws NullPointerException if plaintext or key is {@code null} + */ + public static byte[] encrypt(byte[] plaintext, byte[] key) { + validateInputs(plaintext, key); + return xor(plaintext, key); + } + + /** + * Decrypts the given ciphertext bytes using the provided key. + *
For a One-Time Pad, decryption is identical to encryption: + * {@code plaintext = ciphertext XOR key}. + * + * @param ciphertext the ciphertext bytes, must not be {@code null} + * @param key the one-time pad key bytes, must not be {@code null} + * @return the decrypted plaintext bytes + * @throws IllegalArgumentException if the key length does not match ciphertext length + * @throws NullPointerException if ciphertext or key is {@code null} + */ + public static byte[] decrypt(byte[] ciphertext, byte[] key) { + validateInputs(ciphertext, key); + return xor(ciphertext, key); + } + + private static void validateInputs(byte[] input, byte[] key) { + Objects.requireNonNull(input, "input must not be null"); + Objects.requireNonNull(key, "key must not be null"); + if (input.length != key.length) { + throw new IllegalArgumentException("Key length must match input length"); + } + } + + private static byte[] xor(byte[] data, byte[] key) { + byte[] result = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + result[i] = (byte) (data[i] ^ key[i]); + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java index f81252980468..324dc88e4f19 100644 --- a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java @@ -1,7 +1,5 @@ package com.thealgorithms.ciphers; -import java.util.Arrays; - /** * The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher. * It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails. @@ -14,28 +12,27 @@ public class RailFenceCipher { // Encrypts the input string using the rail fence cipher method with the given number of rails. public String encrypt(String str, int rails) { + checkInput(str, rails); + // Base case of single rail or rails are more than the number of characters in the string if (rails == 1 || rails >= str.length()) { return str; } - // Boolean flag to determine if the movement is downward or upward in the rail matrix. + // Boolean flag to determine if the movement is downward or upward in the rail pattern. boolean down = true; - // Create a 2D array to represent the rails (rows) and the length of the string (columns). - char[][] strRail = new char[rails][str.length()]; - - // Initialize all positions in the rail matrix with a placeholder character ('\n'). + // Collect the characters of every rail separately. Using one buffer per rail (instead of a + // rails x length matrix with a placeholder character) keeps every character of the input, + // including characters that would otherwise be indistinguishable from the placeholder. + StringBuilder[] railBuffers = new StringBuilder[rails]; for (int i = 0; i < rails; i++) { - Arrays.fill(strRail[i], '\n'); + railBuffers[i] = new StringBuilder(); } - int row = 0; // Start at the first row - int col = 0; // Start at the first column + int row = 0; // Start at the first rail - int i = 0; - - // Fill the rail matrix with characters from the string based on the rail pattern. - while (col < str.length()) { + // Distribute the characters of the string over the rails following the zigzag pattern. + for (int i = 0; i < str.length(); i++) { // Change direction to down when at the first row. if (row == 0) { down = true; @@ -45,33 +42,28 @@ else if (row == rails - 1) { down = false; } - // Place the character in the current position of the rail matrix. - strRail[row][col] = str.charAt(i); - col++; // Move to the next column. + // Append the character to the rail it belongs to. + railBuffers[row].append(str.charAt(i)); // Move to the next row based on the direction. if (down) { row++; } else { row--; } - - i++; } - // Construct the encrypted string by reading characters row by row. - StringBuilder encryptedString = new StringBuilder(); - for (char[] chRow : strRail) { - for (char ch : chRow) { - if (ch != '\n') { - encryptedString.append(ch); - } - } + // Construct the encrypted string by reading the rails top to bottom. + StringBuilder encryptedString = new StringBuilder(str.length()); + for (StringBuilder railBuffer : railBuffers) { + encryptedString.append(railBuffer); } return encryptedString.toString(); } // Decrypts the input string using the rail fence cipher method with the given number of rails. public String decrypt(String str, int rails) { + checkInput(str, rails); + // Base case of single rail or rails are more than the number of characters in the string if (rails == 1 || rails >= str.length()) { return str; @@ -144,4 +136,14 @@ else if (row == rails - 1) { return decryptedString.toString(); } + + // Rejects inputs the zigzag pattern is not defined for. + private static void checkInput(String str, int rails) { + if (str == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + if (rails <= 0) { + throw new IllegalArgumentException("Number of rails must be positive, but was " + rails); + } + } } diff --git a/src/main/java/com/thealgorithms/ciphers/WinternitzSignature.java b/src/main/java/com/thealgorithms/ciphers/WinternitzSignature.java new file mode 100644 index 000000000000..e4239b170adb --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/WinternitzSignature.java @@ -0,0 +1,227 @@ +package com.thealgorithms.ciphers; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +/** + * Winternitz one-time signatures (WOTS) are a hash-based post-quantum signature scheme and a + * size-optimized version of the Lamport signature. + * + *
The message digest is split into base-{@code w} digits and each digit is signed with a hash + * chain: secret {@code sk[i]} is hashed {@code m[i]} times, while the public key is the chain end + * after {@code w - 1} hashes. A larger {@code w} gives smaller signatures but more hashing + * (133, 67 and 34 values for {@code w} = 4, 16 and 256). + * + *
Security relies only on the one-wayness of the hash function, which is why the scheme is + * considered quantum-resistant. A checksum over the digits is signed too, so an attacker cannot + * hash revealed values further to forge a signature. Each key pair can sign only one message, + * since a second signature reveals enough chain values to forge new ones. + * + *
WOTS is the building block of XMSS and SPHINCS+, which use the WOTS+ variant with per-step + * bitmasks. This implementation is educational and must not be used in production. + * + *
Reference: Wikipedia: Hash-based cryptography + * + * @author dilaraacetin + * @see LamportSignature + */ +public final class WinternitzSignature { + + // SHA-256 output length in bytes + private static final int HASH_BYTES = 32; + private static final int DEFAULT_W = 16; + + private final Parameters parameters; + private final byte[][] privateKey; + private final byte[][] publicKey; + private boolean used; + + /** + * Generates a new key pair with the default Winternitz parameter {@code w = 16}. + */ + public WinternitzSignature() { + this(DEFAULT_W); + } + + /** + * Generates a new key pair with the given Winternitz parameter. + * + * @param w the Winternitz parameter; must be 4, 16 or 256 + * @throws IllegalArgumentException if {@code w} is not a supported value + */ + public WinternitzSignature(int w) { + this.parameters = Parameters.forW(w); + SecureRandom secureRandom = new SecureRandom(); + privateKey = new byte[parameters.len()][HASH_BYTES]; + publicKey = new byte[parameters.len()][]; + for (int i = 0; i < parameters.len(); i++) { + secureRandom.nextBytes(privateKey[i]); + publicKey[i] = chain(privateKey[i], w - 1); + } + } + + /** + * Returns a copy of the public key. + * + * @return {@code len} hash chain end values, each 32 bytes long + */ + public byte[][] getPublicKey() { + return deepCopy(publicKey); + } + + /** + * Signs a message. A key pair can sign only one message. + * + * @param message the message to sign + * @return the signature: {@code len} values of 32 bytes each (67 values for {@code w = 16}) + * @throws IllegalArgumentException if the message is null + * @throws IllegalStateException if this key pair has already been used to sign a message + */ + public byte[][] sign(byte[] message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (used) { + throw new IllegalStateException("This Winternitz key pair can only sign one message"); + } + used = true; + + int[] digits = messageDigits(hash(message), parameters); + byte[][] signature = new byte[parameters.len()][]; + for (int i = 0; i < parameters.len(); i++) { + signature[i] = chain(privateKey[i], digits[i]); + } + return signature; + } + + /** + * Verifies a signature against a public key. Using a different {@code w} than the signer + * causes a size mismatch and an {@link IllegalArgumentException}. + * + * @param message the signed message + * @param signature the signature to check + * @param publicKey the public key of the signer + * @param w the Winternitz parameter used to create the key pair; must be 4, 16 or 256 + * @return true if the signature is valid for the message and public key, false otherwise + * @throws IllegalArgumentException if an argument is null, {@code w} is unsupported, or the + * signature or public key is malformed + */ + public static boolean verify(byte[] message, byte[][] signature, byte[][] publicKey, int w) { + Parameters params = Parameters.forW(w); + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + validateShape(signature, params.len(), "signature"); + validateShape(publicKey, params.len(), "publicKey"); + + int[] digits = messageDigits(hash(message), params); + for (int i = 0; i < params.len(); i++) { + // complete the chain: m[i] + (w - 1 - m[i]) = w - 1 steps + byte[] chainEnd = chain(signature[i], w - 1 - digits[i]); + if (!MessageDigest.isEqual(chainEnd, publicKey[i])) { + return false; + } + } + return true; + } + + /** + * Parameters derived from {@code w}: {@code len1} message chains and {@code len2} checksum chains. + */ + record Parameters(int w, int log2w, int len1, int len2) { + + static Parameters forW(int w) { + if (w != 4 && w != 16 && w != 256) { + throw new IllegalArgumentException("w must be 4, 16 or 256, got " + w); + } + int log2w = Integer.numberOfTrailingZeros(w); + // len1 = ceil(8n / log2(w)) + int len1 = (8 * HASH_BYTES + log2w - 1) / log2w; + // len2 = floor(log2(len1 * (w - 1)) / log2(w)) + 1 + int maxChecksumBits = 32 - Integer.numberOfLeadingZeros(len1 * (w - 1)); + int len2 = (maxChecksumBits - 1) / log2w + 1; + return new Parameters(w, log2w, len1, len2); + } + + int len() { + return len1 + len2; + } + } + + /** + * Converts a digest into {@code len1} base-{@code w} message digits followed by {@code len2} + * checksum digits. Shared by sign and verify. + */ + private static int[] messageDigits(byte[] digest, Parameters params) { + int[] digits = new int[params.len()]; + int mask = params.w() - 1; + + // split the digest into log2(w)-bit digits + int bitsLeft = 0; + int currentByte = 0; + int byteIndex = 0; + for (int i = 0; i < params.len1(); i++) { + if (bitsLeft == 0) { + currentByte = digest[byteIndex++] & 0xFF; // unsigned byte + bitsLeft = 8; + } + bitsLeft -= params.log2w(); + digits[i] = (currentByte >> bitsLeft) & mask; + } + + // checksum C = sum(w - 1 - m[i]) + int checksum = 0; + for (int i = 0; i < params.len1(); i++) { + checksum += mask - digits[i]; + } + + // write C as len2 base-w digits, most significant first + for (int i = params.len() - 1; i >= params.len1(); i--) { + digits[i] = checksum & mask; + checksum >>>= params.log2w(); + } + return digits; + } + + /** + * Hashes {@code x} {@code iterations} times; returns a copy of {@code x} for zero iterations. + */ + private static byte[] chain(byte[] x, int iterations) { + byte[] result = x.clone(); + for (int i = 0; i < iterations; i++) { + result = hash(result); + } + return result; + } + + private static byte[] hash(byte[] data) { + try { + return MessageDigest.getInstance("SHA-256").digest(data); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("SHA-256 is required by the Java SE specification", e); + } + } + + private static void validateShape(byte[][] values, int expectedLength, String name) { + if (values == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + if (values.length != expectedLength) { + throw new IllegalArgumentException(name + " must contain exactly " + expectedLength + " values, got " + values.length); + } + for (byte[] value : values) { + if (value == null || value.length != HASH_BYTES) { + throw new IllegalArgumentException(name + " values must be exactly " + HASH_BYTES + " bytes long"); + } + } + } + + private static byte[][] deepCopy(byte[][] values) { + byte[][] copy = new byte[values.length][]; + for (int i = 0; i < values.length; i++) { + copy[i] = values[i].clone(); + } + return copy; + } +} 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: + *
This class supports conversions between the following units: + *
This class is final and cannot be instantiated. + * + * @author krishna-medapati (https://github.com/krishna-medapati) + * @see Wikipedia: Temperature Conversion + */ +public final class TemperatureConverter { + + private TemperatureConverter() { + } + + public static double celsiusToFahrenheit(double celsius) { + return celsius * 9.0 / 5.0 + 32.0; + } + + public static double celsiusToKelvin(double celsius) { + return celsius + 273.15; + } + + public static double fahrenheitToCelsius(double fahrenheit) { + return (fahrenheit - 32.0) * 5.0 / 9.0; + } + + public static double fahrenheitToKelvin(double fahrenheit) { + return (fahrenheit - 32.0) * 5.0 / 9.0 + 273.15; + } + + public static double kelvinToCelsius(double kelvin) { + return kelvin - 273.15; + } + + public static double kelvinToFahrenheit(double kelvin) { + return (kelvin - 273.15) * 9.0 / 5.0 + 32.0; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/SlidingWindowAggregator.java b/src/main/java/com/thealgorithms/datastructures/buffers/SlidingWindowAggregator.java new file mode 100644 index 000000000000..ae20c88cd334 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/buffers/SlidingWindowAggregator.java @@ -0,0 +1,447 @@ +package com.thealgorithms.datastructures.buffers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.BinaryOperator; +import java.util.function.Function; + +/** + * A fixed-capacity ring (circular) buffer that additionally keeps an aggregate of every element it + * currently holds, i.e. a sliding window with aggregation. + * + *
The window slides automatically: once the buffer is full, adding a new element evicts the + * oldest one, and the aggregate is kept in sync without rescanning the window. + * + *
The naive way to keep a windowed sum is "add the new value, subtract the evicted one". That + * trick only works for invertible operators: there is no way to subtract a value from a + * minimum. This class instead uses the classic two-stack sliding window aggregation + * algorithm, which needs nothing but associativity: + * + *
A pleasant side effect is numerical stability: because aggregates are recomputed from scratch + * on every flip, floating-point error cannot accumulate indefinitely the way it does with the + * add-then-subtract approach. + * + *
| Operation | Complexity |
|---|---|
| {@link #add(Object)} | O(1) amortised, O(n) worst case |
| {@link #removeOldest()} | O(1) amortised, O(n) worst case |
| {@link #aggregate()} | O(1) |
| {@link #get(int)}, {@link #peekOldest()}, {@link #peekNewest()} | O(1) |
| memory | O(capacity), no allocation after construction |
{@code
+ * // Maximum of the last three readings.
+ * SlidingWindowAggregator window = SlidingWindowAggregator.maximum(3, Comparator.naturalOrder());
+ * for (int value : new int[] {1, 3, 2, 5, 0}) {
+ * window.add(value);
+ * System.out.println(window.aggregate());
+ * }
+ * // prints 1, 3, 3, 5, 5
+ *
+ * // Moving average of the last 100 samples: the window knows both the sum and its own size.
+ * SlidingWindowAggregator sum = SlidingWindowAggregator.sumOfDoubles(100);
+ * sum.add(sample);
+ * double movingAverage = sum.aggregate() / sum.size();
+ * }
+ *
+ * Algorithm explanation: + * https://en.wikipedia.org/wiki/Breadth-first_search + * + *
Problem reference: + * https://leetcode.com/problems/rotting-oranges/ + * + *
Given a grid where: + *
Returns the minimum number of minutes required for all fresh oranges + * to become rotten. Returns {@code -1} if it is impossible. + * + *
Time Complexity: O(m Γ n)
+ * This HashMap does not allow modification of existing instances.
+ * Any update operation returns a new ImmutableHashMap.
+ *
+ * @param Key features:
+ * Complexities:
+ * {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
+ * {@code peek, isEmpty, size, contains} are O(1).
+ */
+public class IndexedPriorityQueue We use IdentityHashMap by default to:
+ * IMPORTANT: The mutator must not change {@code equals/hashCode} of {@code e}
+ * if you migrate this implementation to value-based indexing (HashMap).
+ *
+ * @throws IllegalArgumentException if {@code e} is not in the queue
+ */
+ public void changeKey(E e, Consumer Returns nothing; the standard {@code PriorityQueue} returns a displaced
+ * element in a rare case to help its iterator. We don't need that here, so
+ * we keep the API simple.
+ */
+ @SuppressWarnings("unchecked")
+ private void removeAt(int i) {
+ int n = --size; // last index after removal
+ E moved = (E) heap[n];
+ E removed = (E) heap[i];
+ heap[n] = null; // help GC
+ index.remove(removed); // drop mapping for removed element
+
+ if (i == n) {
+ return; // removed last element; done
+ }
+
+ heap[i] = moved;
+ index.put(moved, i);
+
+ // Try sift-up first (cheap if key decreased); if no movement, sift-down.
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java
index 58898ddc0fcf..462521e464cc 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java
@@ -7,7 +7,7 @@
*
* A linked list is similar to an array, it holds values. However, links in a
* linked list do not have indexes. With a linked list you do not need to
- * predetermine it's size as it grows and shrinks as it is edited. This is an
+ * predetermine its size as it grows and shrinks as it is edited. This is an
* example of a double ended, doubly linked list. Each link references the next
* link and the previous one.
*
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
new file mode 100644
index 000000000000..0ee788db2ff9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.datastructures.lists;
+
+/**
+ * Returns the middle node of a singly linked list using the two-pointer technique.
+ *
+ * The {@code slow} pointer advances by one node per iteration while {@code fast} advances by two.
+ * When {@code fast == null} or {@code fast.next == null}, {@code slow} points to the middle node.
+ * For even-length lists, this returns the second middle node. This method does not modify the input list. Reference: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare Complexity:
+ * The conversion follows a preorder traversal pattern (root β left β right)
+ * and uses parentheses to denote the tree structure.
+ * Empty parentheses "()" are used to explicitly represent missing left children
+ * when a right child exists, ensuring the structure is unambiguous.
+ *
+ * This implementation matches the logic from LeetCode problem 606:
+ * Construct String from Binary Tree.
+ *
+ * Time Complexity: O(N log N) for construction
+ * Space Complexity: O(N)
+ *
+ *
+ * Applications:
+ * - Distance queries on trees
+ * - Path counting problems
+ * - Nearest neighbor searches
+ *
+ * @see Centroid Decomposition
+ * @see Centroid Decomposition Tutorial
+ * @author lens161
+ */
+public final class CentroidDecomposition {
+
+ private CentroidDecomposition() {
+ }
+
+ /**
+ * Represents the centroid tree structure.
+ */
+ public static final class CentroidTree {
+ private final int n;
+ private final List In this implementation, a node's null left/right pointers are used
+ * to point to the in-order predecessor/successor respectively. Two flags
+ * indicate whether left/right pointers are real children or threads.
+ *
+ * @see Wikipedia:
+ * Threaded binary tree
+ */
+public final class ThreadedBinaryTree {
+
+ private Node root;
+
+ private static final class Node {
+ int value;
+ Node left;
+ Node right;
+ boolean leftIsThread;
+ boolean rightIsThread;
+
+ Node(int value) {
+ this.value = value;
+ this.left = null;
+ this.right = null;
+ this.leftIsThread = false;
+ this.rightIsThread = false;
+ }
+ }
+
+ public ThreadedBinaryTree() {
+ this.root = null;
+ }
+
+ /**
+ * Inserts a value into the threaded binary tree. Duplicate values are inserted
+ * to the right subtree (consistent deterministic rule).
+ *
+ * @param value the integer value to insert
+ */
+ public void insert(int value) {
+ Node newNode = new Node(value);
+ if (root == null) {
+ root = newNode;
+ return;
+ }
+
+ Node current = root;
+ Node parent = null;
+
+ while (true) {
+ parent = current;
+ if (value < current.value) {
+ if (!current.leftIsThread && current.left != null) {
+ current = current.left;
+ } else {
+ break;
+ }
+ } else { // value >= current.value
+ if (!current.rightIsThread && current.right != null) {
+ current = current.right;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (value < parent.value) {
+ // attach newNode as left child
+ newNode.left = parent.left;
+ newNode.leftIsThread = parent.leftIsThread;
+ newNode.right = parent;
+ newNode.rightIsThread = true;
+
+ parent.left = newNode;
+ parent.leftIsThread = false;
+ } else {
+ // attach newNode as right child
+ newNode.right = parent.right;
+ newNode.rightIsThread = parent.rightIsThread;
+ newNode.left = parent;
+ newNode.leftIsThread = true;
+
+ parent.right = newNode;
+ parent.rightIsThread = false;
+ }
+ }
+
+ /**
+ * Returns the in-order traversal of the tree as a list of integers.
+ * Traversal is done without recursion or an explicit stack by following threads.
+ *
+ * @return list containing the in-order sequence of node values
+ */
+ public List
+ * Example:
+ * countRangeWithDigitSum(1, 100, 5) returns 6 (numbers: 5, 14, 23, 32, 41, 50)
+ */
+public final class DigitDP {
+
+ // Maximum theoretical digit sum for a 64-bit signed long integer (9 * 19 digits
+ // = 171)
+ private static final int MAX_DIGIT_SUM = 171;
+
+ private DigitDP() {
+ // Prevent instantiation for utility/algorithm template class
+ }
+
+ /**
+ * Counts how many numbers in the range [L, R] have a digit sum equal to the
+ * target.
+ *
+ * @param l The lower bound of the range (inclusive).
+ * @param r The upper bound of the range (inclusive).
+ * @param target The exact sum of digits required.
+ * @return The count of valid integers.
+ */
+ public static long countRangeWithDigitSum(long l, long r, int target) {
+ if (l > r || target < 0 || target > MAX_DIGIT_SUM) {
+ return 0;
+ }
+ long countR = countWithDigitSum(r, target);
+ long countLMinus1 = countWithDigitSum(l - 1, target);
+ return countR - countLMinus1;
+ }
+
+ private static long countWithDigitSum(long number, int target) {
+ if (number < 0) {
+ return 0;
+ }
+ String numStr = Long.toString(number);
+ int length = numStr.length();
+
+ // dp[index][current_sum][tight]
+ long[][][] dp = new long[length][MAX_DIGIT_SUM + 1][2];
+ for (long[][] row : dp) {
+ for (long[] col : row) {
+ Arrays.fill(col, -1);
+ }
+ }
+
+ return solve(0, 0, 1, numStr, target, dp);
+ }
+
+ /**
+ * Recursive memoized function to explore digit placements.
+ *
+ * Time Complexity: O(number_of_digits * target_sum * 10)
+ * Space Complexity: O(number_of_digits * target_sum * 2)
+ *
+ * @param index Current digit position from left to right (most significant
+ * first).
+ * @param currentSum Cumulative sum of digits chosen so far.
+ * @param tight Flag indicating if current prefix matches the original
+ * number boundary.
+ * @param numStr String representation of the upper ceiling limit.
+ * @param target The exact required sum of digits.
+ * @param dp Memoization matrix cache table.
+ * @return Total valid combinations from the current state configuration.
+ */
+ private static long solve(int index, int currentSum, int tight, String numStr, int target, long[][][] dp) {
+ // Base case: If we have processed all digits
+ if (index == numStr.length()) {
+ return currentSum == target ? 1 : 0;
+ }
+
+ // Return memoized state if already evaluated
+ if (dp[index][currentSum][tight] != -1) {
+ return dp[index][currentSum][tight];
+ }
+
+ long ans = 0;
+ // Determine the maximum limit for the current position digit
+ int limit = (tight == 1) ? (numStr.charAt(index) - '0') : 9;
+
+ // Iterate through all possible valid digits for this position
+ for (int digit = 0; digit <= limit; digit++) {
+ int nextSum = currentSum + digit;
+
+ // Optimization: If the digit sum exceeds the target, prune branch
+ if (nextSum > target) {
+ continue;
+ }
+
+ // Next state remains tight only if current state is tight and we place the
+ // exact limit digit
+ int nextTight = (tight == 1 && digit == limit) ? 1 : 0;
+ ans += solve(index + 1, nextSum, nextTight, numStr, target, dp);
+ }
+
+ dp[index][currentSum][tight] = ans;
+ return ans;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
index 0d6aff2bbef3..df158112d233 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
@@ -1,10 +1,22 @@
package com.thealgorithms.dynamicprogramming;
-
import java.util.HashMap;
import java.util.Map;
-
/**
- * @author Varun Upadhyay (https://github.com/varunu28)
+ * Collection of Dynamic Programming techniques to solve for the n-th Fibonacci number.
+ *
+ * This file showcases Top-Down Memoization ({@code fibMemo}), Bottom-Up Tabulation ({@code fibBotUp}),
+ * and Space-Optimized Iteration ({@code fibOptimized}).
+ *
+ * For alternative structural paradigms, mathematical formulas, or verification steps, see:
+ * The algorithm sorts the keys, preserves the corresponding search frequencies, and uses
+ * dynamic programming with Knuth's optimization to compute the minimum weighted search cost.
+ *
+ * Example: if keys = [10, 12] and frequencies = [34, 50], the best tree puts 12 at the root
+ * and 10 as its left child. The total cost is 50 * 1 + 34 * 2 = 118.
+ *
+ * Reference:
+ * https://en.wikipedia.org/wiki/Optimal_binary_search_tree
+ */
+public final class OptimalBinarySearchTree {
+ private OptimalBinarySearchTree() {
+ }
+
+ /**
+ * Computes the minimum weighted search cost for the given keys and search frequencies.
+ *
+ * @param keys the BST keys
+ * @param frequencies the search frequencies associated with the keys
+ * @return the minimum search cost
+ * @throws IllegalArgumentException if the input is invalid
+ */
+ public static long findOptimalCost(int[] keys, int[] frequencies) {
+ validateInput(keys, frequencies);
+ if (keys.length == 0) {
+ return 0L;
+ }
+
+ int[][] sortedNodes = sortNodes(keys, frequencies);
+ int nodeCount = sortedNodes.length;
+ long[] prefixSums = buildPrefixSums(sortedNodes);
+ long[][] optimalCost = new long[nodeCount][nodeCount];
+ int[][] root = new int[nodeCount][nodeCount];
+
+ // Small example:
+ // keys = [10, 12]
+ // frequencies = [34, 50]
+ // Choosing 12 as the root gives cost 50 * 1 + 34 * 2 = 118,
+ // which is better than choosing 10 as the root.
+
+ // Base case: a subtree containing one key has cost equal to its frequency,
+ // because that key becomes the root of the subtree and is searched at depth 1.
+ for (int index = 0; index < nodeCount; index++) {
+ optimalCost[index][index] = sortedNodes[index][1];
+ root[index][index] = index;
+ }
+
+ // Build solutions for longer and longer key ranges.
+ // optimalCost[start][end] stores the minimum search cost for keys in that range.
+ for (int length = 2; length <= nodeCount; length++) {
+ for (int start = 0; start <= nodeCount - length; start++) {
+ int end = start + length - 1;
+
+ // Every key in this range moves one level deeper when we choose a root,
+ // so the sum of frequencies is added once to the subtree cost.
+ long frequencySum = prefixSums[end + 1] - prefixSums[start];
+ optimalCost[start][end] = Long.MAX_VALUE;
+
+ // Knuth's optimization:
+ // the best root for [start, end] lies between the best roots of
+ // [start, end - 1] and [start + 1, end], so we search only this interval.
+ int leftBoundary = root[start][end - 1];
+ int rightBoundary = root[start + 1][end];
+ for (int currentRoot = leftBoundary; currentRoot <= rightBoundary; currentRoot++) {
+ long leftCost = currentRoot > start ? optimalCost[start][currentRoot - 1] : 0L;
+ long rightCost = currentRoot < end ? optimalCost[currentRoot + 1][end] : 0L;
+ long currentCost = frequencySum + leftCost + rightCost;
+
+ if (currentCost < optimalCost[start][end]) {
+ optimalCost[start][end] = currentCost;
+ root[start][end] = currentRoot;
+ }
+ }
+ }
+ }
+
+ return optimalCost[0][nodeCount - 1];
+ }
+
+ private static void validateInput(int[] keys, int[] frequencies) {
+ if (keys == null || frequencies == null) {
+ throw new IllegalArgumentException("Keys and frequencies cannot be null");
+ }
+ if (keys.length != frequencies.length) {
+ throw new IllegalArgumentException("Keys and frequencies must have the same length");
+ }
+
+ for (int frequency : frequencies) {
+ if (frequency < 0) {
+ throw new IllegalArgumentException("Frequencies cannot be negative");
+ }
+ }
+ }
+
+ private static int[][] sortNodes(int[] keys, int[] frequencies) {
+ int[][] sortedNodes = new int[keys.length][2];
+ for (int index = 0; index < keys.length; index++) {
+ sortedNodes[index][0] = keys[index];
+ sortedNodes[index][1] = frequencies[index];
+ }
+
+ // Sort by key so the nodes can be treated as an in-order BST sequence.
+ Arrays.sort(sortedNodes, Comparator.comparingInt(node -> node[0]));
+
+ for (int index = 1; index < sortedNodes.length; index++) {
+ if (sortedNodes[index - 1][0] == sortedNodes[index][0]) {
+ throw new IllegalArgumentException("Keys must be distinct");
+ }
+ }
+
+ return sortedNodes;
+ }
+
+ private static long[] buildPrefixSums(int[][] sortedNodes) {
+ long[] prefixSums = new long[sortedNodes.length + 1];
+ for (int index = 0; index < sortedNodes.length; index++) {
+ // prefixSums[i] holds the total frequency of the first i sorted keys.
+ // This lets us get the frequency sum of any range in O(1) time.
+ prefixSums[index + 1] = prefixSums[index] + sortedNodes[index][1];
+ }
+ return prefixSums;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/LineIntersection.java b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
new file mode 100644
index 000000000000..8d65833816b3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/LineIntersection.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.geometry;
+
+import java.awt.geom.Point2D;
+import java.util.Optional;
+
+/**
+ * Utility methods for checking and computing 2D line segment intersections.
+ */
+public final class LineIntersection {
+ private LineIntersection() {
+ }
+
+ /**
+ * Checks whether two line segments intersect.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return true when the segments intersect (including touching endpoints)
+ */
+ public static boolean intersects(Point p1, Point p2, Point q1, Point q2) {
+ int o1 = orientation(p1, p2, q1);
+ int o2 = orientation(p1, p2, q2);
+ int o3 = orientation(q1, q2, p1);
+ int o4 = orientation(q1, q2, p2);
+
+ if (o1 != o2 && o3 != o4) {
+ return true;
+ }
+
+ if (o1 == 0 && onSegment(p1, q1, p2)) {
+ return true;
+ }
+ if (o2 == 0 && onSegment(p1, q2, p2)) {
+ return true;
+ }
+ if (o3 == 0 && onSegment(q1, p1, q2)) {
+ return true;
+ }
+ if (o4 == 0 && onSegment(q1, p2, q2)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes the single geometric intersection point between two non-parallel
+ * segments when it exists.
+ *
+ * For parallel/collinear overlap, this method returns {@code Optional.empty()}.
+ *
+ * @param p1 first endpoint of segment 1
+ * @param p2 second endpoint of segment 1
+ * @param q1 first endpoint of segment 2
+ * @param q2 second endpoint of segment 2
+ * @return the intersection point when uniquely defined and on both segments
+ */
+ public static Optional Each account is expected to be a list where the first element is the user name and the
+ * remaining elements are email addresses. Accounts that share at least one email are merged into a
+ * single record.
+ */
+public final class AccountMerge {
+ private AccountMerge() {
+ // Utility class; do not instantiate.
+ }
+
+ /**
+ * Merges accounts that share one or more email addresses.
+ *
+ * The returned list is sorted by account owner name, then by the first email address when
+ * multiple merged groups have the same owner name. Within each merged account, emails are
+ * returned in lexicographic order.
+ *
+ * @param accounts a list of accounts where each entry contains a user name followed by emails
+ * @return merged accounts, or an empty list when {@code accounts} is null or empty
+ */
+ public static List API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
+ *
+ * @see Wikipedia: GomoryβHu tree
+ */
+
+public final class GomoryHuTree {
+ private GomoryHuTree() {
+ }
+
+ public static int[][] buildTree(int[][] cap) {
+ validateCapacityMatrix(cap);
+ final int n = cap.length;
+ if (n == 1) {
+ return new int[][] {new int[] {-1}, new int[] {0}};
+ }
+
+ int[] parent = new int[n];
+ int[] weight = new int[n];
+ Arrays.fill(parent, 0);
+ parent[0] = -1;
+ weight[0] = 0;
+
+ for (int s = 1; s < n; s++) {
+ int t = parent[s];
+ MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t);
+ int f = res.flow;
+ weight[s] = f;
+
+ for (int v = 0; v < n; v++) {
+ if (v != s && parent[v] == t && res.reachable[v]) {
+ parent[v] = s;
+ }
+ }
+
+ if (t != 0 && res.reachable[parent[t]]) {
+ parent[s] = parent[t];
+ parent[t] = s;
+ weight[s] = weight[t];
+ weight[t] = f;
+ }
+ }
+ return new int[][] {parent, weight};
+ }
+
+ private static void validateCapacityMatrix(int[][] cap) {
+ if (cap == null || cap.length == 0) {
+ throw new IllegalArgumentException("Capacity matrix must not be null or empty");
+ }
+ final int n = cap.length;
+ for (int i = 0; i < n; i++) {
+ if (cap[i] == null || cap[i].length != n) {
+ throw new IllegalArgumentException("Capacity matrix must be square");
+ }
+ for (int j = 0; j < n; j++) {
+ if (cap[i][j] < 0) {
+ throw new IllegalArgumentException("Capacities must be non-negative");
+ }
+ }
+ }
+ }
+
+ private static final class MaxFlowResult {
+ final int flow;
+ final boolean[] reachable;
+ MaxFlowResult(int flow, boolean[] reachable) {
+ this.flow = flow;
+ this.reachable = reachable;
+ }
+ }
+
+ private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) {
+ final int n = capacity.length;
+ int[][] residual = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ residual[i] = Arrays.copyOf(capacity[i], n);
+ }
+
+ int[] parent = new int[n];
+ int maxFlow = 0;
+
+ while (bfs(residual, source, sink, parent)) {
+ int pathFlow = Integer.MAX_VALUE;
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ pathFlow = Math.min(pathFlow, residual[u][v]);
+ }
+ for (int v = sink; v != source; v = parent[v]) {
+ int u = parent[v];
+ residual[u][v] -= pathFlow;
+ residual[v][u] += pathFlow;
+ }
+ maxFlow += pathFlow;
+ }
+
+ boolean[] reachable = new boolean[n];
+ markReachable(residual, source, reachable);
+ return new MaxFlowResult(maxFlow, reachable);
+ }
+
+ private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
+ Arrays.fill(parent, -1);
+ parent[source] = source;
+ Queue 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: 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. 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)}. Note: This greedy approach works optimally for standard coin systems
+ * (like Indian currency), but may not work for all arbitrary coin sets.
+ * For arbitrary denominations, dynamic programming is preferred.
+ *
+ * @see Change-making problem
+ */
public final class CoinChange {
private CoinChange() {
}
- // Function to solve the coin change problem
+
+ /**
+ * Returns the list of coins used to make the given amount
+ * using a greedy algorithm with standard denominations.
+ *
+ * Time Complexity: O(n log n) where n is the number of coin denominations
+ * Space Complexity: O(n)
+ *
+ * @param amount the total amount to make change for
+ * @return list of coins used to make the amount
+ */
public static ArrayList
* This may also do the job to read first time data (the whole buffer is empty)
*/
diff --git a/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java
new file mode 100644
index 000000000000..02c3943365bf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/machinelearning/KNearestNeighbors.java
@@ -0,0 +1,203 @@
+package com.thealgorithms.machinelearning;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * K-Nearest Neighbors (KNN) classifier.
+ *
+ *
+ * K-Nearest Neighbors is a supervised machine learning algorithm that
+ * classifies a sample based on the majority class among its {@code k}
+ * nearest training samples using the Euclidean distance metric.
+ *
+ *
+ * The classifier stores the training dataset during the fitting phase and
+ * predicts class labels for new samples without building an explicit model.
+ *
+ * @see
+ * K-Nearest Neighbors
+ */
+public final class KNearestNeighbors {
+ private final int k;
+ private double[][] trainingFeatures;
+ private int[] trainingLabels;
+ private int numFeatures;
+
+ /**
+ * Constructs a K-Nearest Neighbors classifier with the specified number
+ * of neighbors.
+ *
+ * @param k the number of nearest neighbors to consider during prediction
+ */
+ public KNearestNeighbors(int k) {
+
+ if (k <= 0) {
+ throw new IllegalArgumentException("k must be greater than 0.");
+ }
+
+ this.k = k;
+ }
+
+ /**
+ * Represents a neighboring training sample and its distance from the test
+ * sample.
+ */
+ private record Neighbor(double distance, int label) {
+ }
+
+ /**
+ * Fits the classifier using the provided training dataset.
+ *
+ *
+ * The training feature vectors and their corresponding class labels are
+ * stored for use during prediction.
+ *
+ * @param features the training feature vectors
+ * @param labels the corresponding class labels
+ */
+ public void fit(double[][] features, int[] labels) {
+
+ if (features == null || labels == null) {
+ throw new IllegalArgumentException("Features and labels cannot be null.");
+ }
+
+ if (features.length == 0 || labels.length == 0) {
+ throw new IllegalArgumentException("Features and labels cannot be empty.");
+ }
+
+ if (features.length != labels.length) {
+ throw new IllegalArgumentException("Features and labels must have the same length.");
+ }
+
+ if (k > features.length) {
+ throw new IllegalArgumentException("k cannot be greater than the number of training samples.");
+ }
+
+ if (features[0] == null) {
+ throw new IllegalArgumentException("Feature vectors cannot be null.");
+ }
+
+ numFeatures = features[0].length;
+
+ if (numFeatures == 0) {
+ throw new IllegalArgumentException("Feature vectors cannot be empty.");
+ }
+
+ for (double[] sample : features) {
+ if (sample == null) {
+ throw new IllegalArgumentException("Feature vectors cannot be null.");
+ }
+
+ if (sample.length != numFeatures) {
+ throw new IllegalArgumentException("All feature vectors must have the same dimension.");
+ }
+ }
+
+ this.trainingFeatures = new double[features.length][];
+
+ for (int i = 0; i < features.length; i++) {
+ this.trainingFeatures[i] = features[i].clone();
+ }
+ this.trainingLabels = labels.clone();
+ }
+
+ /**
+ * Computes the Euclidean distance between two feature vectors.
+ *
+ * @param first the first feature vector
+ * @param second the second feature vector
+ * @return the Euclidean distance between the two vectors
+ */
+ private static double euclideanDistance(double[] first, double[] second) {
+ double sum = 0.0;
+
+ for (int i = 0; i < first.length; i++) {
+ double difference = first[i] - second[i];
+ sum += difference * difference;
+ }
+
+ return Math.sqrt(sum);
+ }
+
+ /**
+ * Predicts the class label for a single sample.
+ *
+ *
+ * The prediction is made by finding the {@code k} nearest neighbors
+ * among the training samples and selecting the class with the highest
+ * number of votes. In the event of a tie, the smaller class label is
+ * returned.
+ *
+ * @param testPoint the sample to classify
+ * @return the predicted class label
+ */
+ public int predict(double[] testPoint) {
+ if (trainingFeatures == null || trainingLabels == null) {
+ throw new IllegalStateException("Classifier has not been fitted.");
+ }
+
+ if (testPoint == null) {
+ throw new IllegalArgumentException("Sample cannot be null.");
+ }
+
+ if (testPoint.length != numFeatures) {
+ throw new IllegalArgumentException("Sample length must match training feature count.");
+ }
+
+ List Suited to discrete, count-based features (e.g. word frequencies in text
+ * classification). Class priors and feature likelihoods are estimated from
+ * training data with Laplace (add-alpha) smoothing to avoid zero
+ * probabilities for unseen feature/class combinations. Predictions are made
+ * by comparing summed log-probabilities across classes, which avoids the
+ * numerical underflow that repeated multiplication of small probabilities
+ * would cause.
+ *
+ * Reference:
+ * Naive Bayes classifier
+ *
+ * @author Vraj Prajapati(Rosander0)
+ */
+public final class MultinomialNaiveBayesClassifier {
+
+ private final double alpha;
+ private final Map The Perceptron is a single-layer neural network that learns a linear
+ * decision boundary. It updates its weights whenever a training sample is
+ * misclassified. Convergence is guaranteed for linearly separable data, but
+ * training stops after the configured epoch limit for non-separable data.
+ * Labels must be either {@code 0} or {@code 1}.
+ *
+ * The prediction rule is {@code 1} when the weighted sum plus bias is
+ * greater than or equal to zero, and {@code 0} otherwise. For a
+ * misclassified sample, the update is {@code weight += learningRate * error *
+ * feature} and {@code bias += learningRate * error}, where {@code error} is
+ * the true label minus the prediction. Samples are visited one at a time, so
+ * each prediction uses the parameters produced by the preceding updates of
+ * the same epoch.
+ *
+ * @see Perceptron
+ */
+public final class Perceptron {
+ private final double learningRate;
+ private final int maxEpochs;
+ private double[] weights;
+ private double bias;
+ private int epochsRun;
+ private boolean converged;
+
+ /**
+ * Constructs a Perceptron with the given training hyperparameters.
+ *
+ * @param learningRate positive step size used for each update
+ * @param maxEpochs positive maximum number of passes over the training data
+ * @throws IllegalArgumentException if a hyperparameter is invalid
+ */
+ public Perceptron(double learningRate, int maxEpochs) {
+ if (!Double.isFinite(learningRate) || learningRate <= 0.0) {
+ throw new IllegalArgumentException("learningRate must be finite and greater than 0");
+ }
+ if (maxEpochs <= 0) {
+ throw new IllegalArgumentException("maxEpochs must be greater than 0");
+ }
+ this.learningRate = learningRate;
+ this.maxEpochs = maxEpochs;
+ }
+
+ /**
+ * Fits the classifier using binary training labels.
+ *
+ * Fitting resets the weights and bias to zero before training. The
+ * method records whether an entire epoch completed without an update. A
+ * large {@code learningRate} combined with large feature values can push
+ * the parameters past the range of {@code double}; the classifier then
+ * returns to its unfitted state instead of reporting predictions derived
+ * from non-finite parameters.
+ *
+ * @param features training feature vectors
+ * @param labels corresponding binary labels, each either {@code 0} or
+ * {@code 1}
+ * @throws IllegalArgumentException if the training data is invalid
+ * @throws ArithmeticException if training diverges and the learned
+ * parameters stop being finite
+ */
+ public void fit(double[][] features, int[] labels) {
+ validateTrainingData(features, labels);
+
+ weights = new double[features[0].length];
+ bias = 0.0;
+ epochsRun = 0;
+ converged = false;
+
+ for (int epoch = 0; epoch < maxEpochs; epoch++) {
+ boolean updated = false;
+
+ for (int sampleIndex = 0; sampleIndex < features.length; sampleIndex++) {
+ int prediction = rawPredict(features[sampleIndex]);
+ int error = labels[sampleIndex] - prediction;
+
+ if (error != 0) {
+ update(features[sampleIndex], error);
+ updated = true;
+ }
+ }
+
+ epochsRun = epoch + 1;
+ if (!updated) {
+ converged = true;
+ break;
+ }
+ }
+
+ ensureParametersAreFinite();
+ }
+
+ /**
+ * Predicts the binary label for one sample.
+ *
+ * @param sample feature vector to classify
+ * @return {@code 0} or {@code 1}
+ * @throws IllegalStateException if the classifier has not been fitted
+ * @throws IllegalArgumentException if the sample is invalid
+ */
+ public int predict(double[] sample) {
+ ensureFitted();
+ validateSample(sample);
+ return rawPredict(sample);
+ }
+
+ /**
+ * Predicts binary labels for a batch of samples.
+ *
+ * @param samples feature vectors to classify
+ * @return one prediction for each sample
+ * @throws IllegalStateException if the classifier has not been fitted
+ * @throws IllegalArgumentException if the batch or one of its samples is
+ * invalid
+ */
+ public int[] predict(double[][] samples) {
+ ensureFitted();
+ if (samples == null) {
+ throw new IllegalArgumentException("samples cannot be null");
+ }
+
+ int[] predictions = new int[samples.length];
+ for (int sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) {
+ predictions[sampleIndex] = predict(samples[sampleIndex]);
+ }
+ return predictions;
+ }
+
+ /**
+ * Returns a defensive copy of the learned feature weights.
+ *
+ * @return learned weights in feature order
+ * @throws IllegalStateException if the classifier has not been fitted
+ */
+ public double[] getWeights() {
+ ensureFitted();
+ return weights.clone();
+ }
+
+ /**
+ * Returns the learned bias term.
+ *
+ * @return learned bias
+ * @throws IllegalStateException if the classifier has not been fitted
+ */
+ public double getBias() {
+ ensureFitted();
+ return bias;
+ }
+
+ /**
+ * Reports whether training completed with an update-free epoch.
+ *
+ * @return {@code true} if an epoch completed without an update
+ * @throws IllegalStateException if the classifier has not been fitted
+ */
+ public boolean hasConverged() {
+ ensureFitted();
+ return converged;
+ }
+
+ /**
+ * Returns the number of epochs performed by the last fit.
+ *
+ * @return number of completed epochs
+ * @throws IllegalStateException if the classifier has not been fitted
+ */
+ public int getEpochsRun() {
+ ensureFitted();
+ return epochsRun;
+ }
+
+ private int rawPredict(double[] sample) {
+ double weightedSum = bias;
+ for (int featureIndex = 0; featureIndex < weights.length; featureIndex++) {
+ weightedSum += weights[featureIndex] * sample[featureIndex];
+ }
+ return weightedSum >= 0.0 ? 1 : 0;
+ }
+
+ private void update(double[] sample, int error) {
+ for (int featureIndex = 0; featureIndex < weights.length; featureIndex++) {
+ weights[featureIndex] += learningRate * error * sample[featureIndex];
+ }
+ bias += learningRate * error;
+ }
+
+ private void ensureParametersAreFinite() {
+ if (!Double.isFinite(bias) || !isFinite(weights)) {
+ weights = null;
+ throw new ArithmeticException("training diverged; try a smaller learningRate or scaled features");
+ }
+ }
+
+ private void ensureFitted() {
+ if (weights == null) {
+ throw new IllegalStateException("classifier has not been fitted");
+ }
+ }
+
+ private void validateTrainingData(double[][] features, int[] labels) {
+ if (features == null || labels == null) {
+ throw new IllegalArgumentException("features and labels cannot be null");
+ }
+ if (features.length == 0 || labels.length == 0) {
+ throw new IllegalArgumentException("features and labels cannot be empty");
+ }
+ if (features.length != labels.length) {
+ throw new IllegalArgumentException("features and labels must have the same length");
+ }
+ if (features[0] == null || features[0].length == 0) {
+ throw new IllegalArgumentException("feature vectors cannot be null or empty");
+ }
+
+ int featureCount = features[0].length;
+ for (int sampleIndex = 0; sampleIndex < features.length; sampleIndex++) {
+ double[] sample = features[sampleIndex];
+ if (sample == null) {
+ throw new IllegalArgumentException("feature vectors cannot be null or empty");
+ }
+ if (sample.length != featureCount) {
+ throw new IllegalArgumentException("all feature vectors must have the same dimension");
+ }
+ validateFiniteValues(sample);
+ if (labels[sampleIndex] != 0 && labels[sampleIndex] != 1) {
+ throw new IllegalArgumentException("labels must be either 0 or 1");
+ }
+ }
+ }
+
+ private void validateSample(double[] sample) {
+ if (sample == null || sample.length != weights.length) {
+ throw new IllegalArgumentException("sample must match the training feature dimension");
+ }
+ validateFiniteValues(sample);
+ }
+
+ private static void validateFiniteValues(double[] values) {
+ if (!isFinite(values)) {
+ throw new IllegalArgumentException("feature values must be finite");
+ }
+ }
+
+ private static boolean isFinite(double[] values) {
+ for (double value : values) {
+ if (!Double.isFinite(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
index 1b9575a330dd..aab6fe0f426d 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
@@ -1,7 +1,5 @@
package com.thealgorithms.maths;
-import java.util.Arrays;
-
public final class AbsoluteMin {
private AbsoluteMin() {
}
@@ -13,14 +11,17 @@ private AbsoluteMin() {
* @return The absolute min value
*/
public static int getMinValue(int... numbers) {
- if (numbers.length == 0) {
- throw new IllegalArgumentException("Numbers array cannot be empty");
+ if (numbers == null || numbers.length == 0) {
+ throw new IllegalArgumentException("Numbers array cannot be empty or null");
}
- var absMinWrapper = new Object() { int value = numbers[0]; };
-
- Arrays.stream(numbers).skip(1).filter(number -> Math.abs(number) <= Math.abs(absMinWrapper.value)).forEach(number -> absMinWrapper.value = Math.min(absMinWrapper.value, number));
-
- return absMinWrapper.value;
+ long absMin = numbers[0];
+ for (int i = 1; i < numbers.length; i++) {
+ long current = numbers[i];
+ if (Math.abs(current) < Math.abs(absMin) || (Math.abs(current) == Math.abs(absMin) && current < absMin)) {
+ absMin = current;
+ }
+ }
+ return (int) absMin;
}
}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
index b9279d5a244a..acc7302eb451 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java
@@ -10,7 +10,7 @@ private AbsoluteValue() {
* @param number The number to be transformed
* @return The absolute value of the {@code number}
*/
- public static int getAbsValue(int number) {
- return number < 0 ? -number : number;
+ public static long getAbsValue(long number) {
+ return Math.abs(number);
}
}
diff --git a/src/main/java/com/thealgorithms/maths/AbundantNumber.java b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
new file mode 100644
index 000000000000..804ac4d71477
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/AbundantNumber.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an abundant number or excessive number is a positive integer for which
+ * the sum of its proper divisors is greater than the number.
+ * Equivalently, it is a number for which the sum of proper divisors (or aliquot sum) is greater than n.
+ *
+ * The integer 12 is the first abundant number. Its proper divisors are 1, 2, 3, 4 and 6 for a total of 16.
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Abundant_number
+ */
+public final class AbundantNumber {
+
+ private AbundantNumber() {
+ }
+
+ // Function to calculate sum of all divisors including n
+ private static int sumOfDivisors(int n) {
+ int sum = 1 + n; // 1 and n are always divisors
+ for (int i = 2; i <= n / 2; i++) {
+ if (n % i == 0) {
+ sum += i; // adding divisor to sum
+ }
+ }
+ return sum;
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking sum of divisors > 2n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Abundant number, otherwise false
+ */
+ public static boolean isAbundant(int number) {
+ validatePositiveNumber(number);
+
+ return sumOfDivisors(number) > 2 * number;
+ }
+
+ /**
+ * Check if {@code number} is an Abundant number or not by checking Aliquot Sum > n
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Abundant number, otherwise false
+ */
+ public static boolean isAbundantNumber(int number) {
+ validatePositiveNumber(number);
+
+ return AliquotSum.getAliquotSum(number) > number;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java
index a34ad6b01ab5..84fc67159379 100644
--- a/src/main/java/com/thealgorithms/maths/Area.java
+++ b/src/main/java/com/thealgorithms/maths/Area.java
@@ -10,17 +10,17 @@ private Area() {
/**
* String of IllegalArgumentException for radius
*/
- private static final String POSITIVE_RADIUS = "Must be a positive radius";
+ private static final String POSITIVE_RADIUS = "Radius must be greater than 0";
/**
* String of IllegalArgumentException for height
*/
- private static final String POSITIVE_HEIGHT = "Must be a positive height";
+ private static final String POSITIVE_HEIGHT = "Height must be greater than 0";
/**
* String of IllegalArgumentException for base
*/
- private static final String POSITIVE_BASE = "Must be a positive base";
+ private static final String POSITIVE_BASE = "Base must be greater than 0";
/**
* Calculate the surface area of a cube.
@@ -30,11 +30,32 @@ private Area() {
*/
public static double surfaceAreaCube(final double sideLength) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("Side length must be greater than 0");
}
return 6 * sideLength * sideLength;
}
+ /**
+ * Calculate the surface area of a cuboid.
+ *
+ * @param length length of the cuboid
+ * @param width width of the cuboid
+ * @param height height of the cuboid
+ * @return surface area of given cuboid
+ */
+ public static double surfaceAreaCuboid(final double length, double width, double height) {
+ if (length <= 0) {
+ throw new IllegalArgumentException("Length must be greater than 0");
+ }
+ if (width <= 0) {
+ throw new IllegalArgumentException("Width must be greater than 0");
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("Height must be greater than 0");
+ }
+ return 2 * (length * width + length * height + width * height);
+ }
+
/**
* Calculate the surface area of a sphere.
*
@@ -57,10 +78,10 @@ public static double surfaceAreaSphere(final double radius) {
*/
public static double surfaceAreaPyramid(final double sideLength, final double slantHeight) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("");
}
if (slantHeight <= 0) {
- throw new IllegalArgumentException("Must be a positive slantHeight");
+ throw new IllegalArgumentException("slant height must be greater than 0");
}
double baseArea = sideLength * sideLength;
double lateralSurfaceArea = 2 * sideLength * slantHeight;
@@ -76,10 +97,10 @@ public static double surfaceAreaPyramid(final double sideLength, final double sl
*/
public static double surfaceAreaRectangle(final double length, final double width) {
if (length <= 0) {
- throw new IllegalArgumentException("Must be a positive length");
+ throw new IllegalArgumentException("Length must be greater than 0");
}
if (width <= 0) {
- throw new IllegalArgumentException("Must be a positive width");
+ throw new IllegalArgumentException("Width must be greater than 0");
}
return length * width;
}
@@ -96,7 +117,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh
throw new IllegalArgumentException(POSITIVE_RADIUS);
}
if (height <= 0) {
- throw new IllegalArgumentException(POSITIVE_RADIUS);
+ throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
return 2 * (Math.PI * radius * radius + Math.PI * radius * height);
}
@@ -109,7 +130,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh
*/
public static double surfaceAreaSquare(final double sideLength) {
if (sideLength <= 0) {
- throw new IllegalArgumentException("Must be a positive sideLength");
+ throw new IllegalArgumentException("Side Length must be greater than 0");
}
return sideLength * sideLength;
}
@@ -121,14 +142,14 @@ public static double surfaceAreaSquare(final double sideLength) {
* @param height height of triangle
* @return area of given triangle
*/
- public static double surfaceAreaTriangle(final double base, final double height) {
- if (base <= 0) {
+ public static double surfaceAreaTriangle(final double baseLength, final double height) {
+ if (baseLength <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return base * height / 2;
+ return baseLength * height / 2;
}
/**
@@ -138,14 +159,14 @@ public static double surfaceAreaTriangle(final double base, final double height)
* @param height height of a parallelogram
* @return area of given parallelogram
*/
- public static double surfaceAreaParallelogram(final double base, final double height) {
- if (base <= 0) {
+ public static double surfaceAreaParallelogram(final double baseLength, final double height) {
+ if (baseLength <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return base * height;
+ return baseLength * height;
}
/**
@@ -156,17 +177,17 @@ public static double surfaceAreaParallelogram(final double base, final double he
* @param height height of trapezium
* @return area of given trapezium
*/
- public static double surfaceAreaTrapezium(final double base1, final double base2, final double height) {
- if (base1 <= 0) {
+ public static double surfaceAreaTrapezium(final double baseLength1, final double baseLength2, final double height) {
+ if (baseLength1 <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE + 1);
}
- if (base2 <= 0) {
+ if (baseLength2 <= 0) {
throw new IllegalArgumentException(POSITIVE_BASE + 2);
}
if (height <= 0) {
throw new IllegalArgumentException(POSITIVE_HEIGHT);
}
- return (base1 + base2) * height / 2;
+ return (baseLength1 + baseLength2) * height / 2;
}
/**
diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java
index a550a7f6504d..cf55af509ccc 100644
--- a/src/main/java/com/thealgorithms/maths/Average.java
+++ b/src/main/java/com/thealgorithms/maths/Average.java
@@ -1,9 +1,16 @@
package com.thealgorithms.maths;
+import java.util.Arrays;
+import java.util.OptionalDouble;
+
/**
* A utility class for computing the average of numeric arrays.
- * This class provides static methods to calculate the average of arrays
- * of both {@code double} and {@code int} values.
+ *
+ * This class provides static methods to calculate the arithmetic mean
+ * of arrays of both {@code double} and {@code int} values. It also offers
+ * a Stream-based alternative for modern, declarative usage.
+ *
+ * All methods guard against {@code null} or empty inputs.
*/
public final class Average {
@@ -13,11 +20,14 @@ private Average() {
}
/**
- * Computes the average of a {@code double} array.
+ * Computes the arithmetic mean of a {@code double} array.
+ *
+ * The average is calculated as the sum of all elements divided
+ * by the number of elements: {@code avg = Ξ£(numbers[i]) / n}.
*
- * @param numbers an array of {@code double} values
- * @return the average of the given numbers
- * @throws IllegalArgumentException if the input array is {@code null} or empty
+ * @param numbers a non-null, non-empty array of {@code double} values
+ * @return the arithmetic mean of the given numbers
+ * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty
*/
public static double average(double[] numbers) {
if (numbers == null || numbers.length == 0) {
@@ -31,11 +41,14 @@ public static double average(double[] numbers) {
}
/**
- * Computes the average of an {@code int} array.
+ * Computes the arithmetic mean of an {@code int} array.
+ *
+ * The sum is accumulated in a {@code long} to prevent integer overflow
+ * when processing large arrays or large values.
*
- * @param numbers an array of {@code int} values
- * @return the average of the given numbers
- * @throws IllegalArgumentException if the input array is {@code null} or empty
+ * @param numbers a non-null, non-empty array of {@code int} values
+ * @return the arithmetic mean as a {@code long} (truncated toward zero)
+ * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty
*/
public static long average(int[] numbers) {
if (numbers == null || numbers.length == 0) {
@@ -47,4 +60,21 @@ public static long average(int[] numbers) {
}
return sum / numbers.length;
}
+
+ /**
+ * Computes the arithmetic mean of a {@code double} array using Java Streams.
+ *
+ * This method is a declarative alternative to {@link #average(double[])}.
+ * Instead of throwing on empty input, it returns an empty {@link OptionalDouble},
+ * following the convention of the Stream API.
+ *
+ * @param numbers an array of {@code double} values, may be {@code null} or empty
+ * @return an {@link OptionalDouble} with the mean, or empty if input is null/empty
+ */
+ public static OptionalDouble averageStream(double[] numbers) {
+ if (numbers == null || numbers.length == 0) {
+ return OptionalDouble.empty();
+ }
+ return Arrays.stream(numbers).average();
+ }
}
diff --git a/src/main/java/com/thealgorithms/maths/BellNumbers.java b/src/main/java/com/thealgorithms/maths/BellNumbers.java
new file mode 100644
index 000000000000..d4dc1014f48b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/BellNumbers.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.maths;
+
+/**
+ * The Bell numbers count the number of partitions of a set.
+ * The n-th Bell number is the number of ways a set of n elements can be partitioned
+ * into nonempty subsets.
+ *
+ *
+ * This implementation uses the Bell Triangle (Aitken's array) method.
+ * Time Complexity: O(n^2)
+ * Space Complexity: O(n^2)
+ * This class provides a method to calculate the Euclidean distance between two points in a
+ * two-dimensional plane.
+ *
+ * Formula: d = sqrt((x2 - x1)^2 + (y2 - y1)^2)
+ *
+ * Reference: https://en.wikipedia.org/wiki/Euclidean_distance
+ */
+public final class DistanceBetweenTwoPoints {
+
+ private DistanceBetweenTwoPoints() {
+ // Utility class; prevent instantiation
+ }
+
+ /**
+ * Calculate the Euclidean distance between two points.
+ *
+ * @param x1 x-coordinate of the first point
+ * @param y1 y-coordinate of the first point
+ * @param x2 x-coordinate of the second point
+ * @param y2 y-coordinate of the second point
+ * @return Euclidean distance between the two points
+ */
+ public static double calculate(final double x1, final double y1, final double x2, final double y2) {
+ final double deltaX = x2 - x1;
+ final double deltaY = y2 - y1;
+ return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/EvilNumber.java b/src/main/java/com/thealgorithms/maths/EvilNumber.java
new file mode 100644
index 000000000000..a10794fb5a85
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/EvilNumber.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, an evil number is a non-negative integer that has an even number of 1s in its binary expansion.
+ * Non-negative integers that are not evil are called odious numbers.
+ *
+ * Evil Number Wiki: https://en.wikipedia.org/wiki/Evil_number
+ * Odious Number Wiki: https://en.wikipedia.org/wiki/Odious_number
+ */
+public final class EvilNumber {
+
+ private EvilNumber() {
+ }
+
+ // Function to count number of one bits in a number using bitwise operators
+ private static int countOneBits(int number) {
+ int oneBitCounter = 0;
+ while (number > 0) {
+ oneBitCounter += number & 1; // increment count if last bit is 1
+ number >>= 1; // right shift to next bit
+ }
+ return oneBitCounter;
+ }
+
+ /**
+ * Check either {@code number} is an Evil number or Odious number
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is an Evil number, otherwise false (in case of Odious number)
+ */
+ public static boolean isEvilNumber(int number) {
+ if (number < 0) {
+ throw new IllegalArgumentException("Negative numbers are not allowed.");
+ }
+
+ int noOfOneBits = countOneBits(number);
+ return noOfOneBits % 2 == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
new file mode 100644
index 000000000000..4934d4493bf2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.maths;
+
+/**
+ * In mathematics, the extended Euclidean algorithm is an extension to the
+ * Euclidean algorithm, and computes, in addition to the greatest common divisor
+ * (gcd) of integers a and b, also the coefficients of BΓ©zout's identity, which
+ * are integers x and y such that ax + by = gcd(a, b).
+ *
+ *
+ * For more details, see
+ * https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
+ */
+public final class ExtendedEuclideanAlgorithm {
+
+ private ExtendedEuclideanAlgorithm() {
+ }
+
+ /**
+ * This method implements the extended Euclidean algorithm.
+ *
+ * @param a The first number.
+ * @param b The second number.
+ * @return An array of three integers:
+ *
+ * This specific implementation uses {@link java.util.stream.Stream#iterate} and reductions to generate terms.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * This specific implementation uses an Iterative approach (Loop) with {@code O(n)} time complexity
+ * and {@code O(1)} space complexity.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * This class serves as a verification utility rather than a generation algorithm.
+ *
+ * For approaches that actively compute the n-th Fibonacci number, see:
+ *
+ * This specific implementation provides a closed-form solution with an expected {@code O(1)} time complexity.
+ *
+ * For alternative approaches to compute or verify Fibonacci numbers, see:
+ *
+ * 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
+ *
- * NOTE: The inputs to Dijkstra's algorithm are a directed and weighted graph
- * consisting of 2 or more nodes, generally represented by an adjacency matrix
- * or list, and a start node.
- *
- *
- * Original source of code:
- * https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Java Also most of the
- * comments are from RosettaCode.
- */
-public final class Dijkstra {
- private Dijkstra() {
- }
-
- private static final Graph.Edge[] GRAPH = {
- // Distance from node "a" to node "b" is 7.
- // In the current Graph there is no way to move the other way (e,g, from "b" to "a"),
- // a new edge would be needed for that
- new Graph.Edge("a", "b", 7),
- new Graph.Edge("a", "c", 9),
- new Graph.Edge("a", "f", 14),
- new Graph.Edge("b", "c", 10),
- new Graph.Edge("b", "d", 15),
- new Graph.Edge("c", "d", 11),
- new Graph.Edge("c", "f", 2),
- new Graph.Edge("d", "e", 6),
- new Graph.Edge("e", "f", 9),
- };
- private static final String START = "a";
- private static final String END = "e";
-
- /**
- * main function Will run the code with "GRAPH" that was defined above.
- */
- public static void main(String[] args) {
- Graph g = new Graph(GRAPH);
- g.dijkstra(START);
- g.printPath(END);
- // g.printAllPaths();
- }
-}
-
-class Graph {
-
- // mapping of vertex names to Vertex objects, built from a set of Edges
-
- private final Map
+ * The Difference Array is an auxiliary data structure that enables efficient range update operations.
+ * It is based on the mathematical concept of Finite Differences.
+ *
+ * Key Operations:
+ *
+ * This method uses a branchless approach by allocating an extra element at the end
+ * of the array, avoiding the conditional check for the right boundary.
+ * Prefix Sum is a technique used to preprocess an array such that
+ * range sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N) time.
+ *
+ * This implementation uses a long array for the prefix sums to prevent
+ * integer overflow when the sum of elements exceeds Integer.MAX_VALUE.
+ *
+ * @see Prefix Sum (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum {
+
+ private final long[] prefixSums;
+
+ /**
+ * Constructor to preprocess the input array.
+ *
+ * @param array The input integer array.
+ * @throws IllegalArgumentException if the array is null.
+ */
+ public PrefixSum(int[] array) {
+ if (array == null) {
+ throw new IllegalArgumentException("Input array cannot be null");
+ }
+ this.prefixSums = new long[array.length + 1];
+ this.prefixSums[0] = 0;
+
+ for (int i = 0; i < array.length; i++) {
+ // Automatically promotes int to long during addition
+ this.prefixSums[i + 1] = this.prefixSums[i] + array[i];
+ }
+ }
+
+ /**
+ * Calculates the sum of elements in the range [left, right].
+ * Indices are 0-based.
+ *
+ * @param left The starting index (inclusive).
+ * @param right The ending index (inclusive).
+ * @return The sum of elements from index left to right as a long.
+ * @throws IndexOutOfBoundsException if indices are out of valid range.
+ */
+ public long sumRange(int left, int right) {
+ if (left < 0 || right >= prefixSums.length - 1 || left > right) {
+ throw new IndexOutOfBoundsException("Invalid range indices");
+ }
+ return prefixSums[right + 1] - prefixSums[left];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
new file mode 100644
index 000000000000..9c168bc6bcc4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * A class that implements the 2D Prefix Sum algorithm.
+ *
+ * 2D Prefix Sum is a technique used to preprocess a 2D matrix such that
+ * sub-matrix sum queries can be answered in O(1) time.
+ * The preprocessing step takes O(N*M) time.
+ *
+ * This implementation uses a long array for the prefix sums to prevent
+ * integer overflow.
+ *
+ * @see Summed-area table (Wikipedia)
+ * @author Chahat Sandhu, singhc7
+ */
+public class PrefixSum2D {
+
+ private final long[][] prefixSums;
+
+ /**
+ * Constructor to preprocess the input matrix.
+ *
+ * @param matrix The input integer matrix.
+ * @throws IllegalArgumentException if the matrix is null or empty.
+ */
+ public PrefixSum2D(int[][] matrix) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
+ throw new IllegalArgumentException("Input matrix cannot be null or empty");
+ }
+
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ this.prefixSums = new long[rows + 1][cols + 1];
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ // P[i+1][j+1] = current + above + left - diagonal_overlap
+ this.prefixSums[i + 1][j + 1] = matrix[i][j] + this.prefixSums[i][j + 1] + this.prefixSums[i + 1][j] - this.prefixSums[i][j];
+ }
+ }
+ }
+
+ /**
+ * Calculates the sum of the sub-matrix defined by (row1, col1) to (row2, col2).
+ * Indices are 0-based.
+ *
+ * @param row1 Top row index.
+ * @param col1 Left column index.
+ * @param row2 Bottom row index.
+ * @param col2 Right column index.
+ * @return The sum of the sub-matrix.
+ * @throws IndexOutOfBoundsException if indices are invalid.
+ */
+ public long sumRegion(int row1, int col1, int row2, int col2) {
+ if (row1 < 0 || row2 >= prefixSums.length - 1 || row2 < row1) {
+ throw new IndexOutOfBoundsException("Invalid row indices");
+ }
+ if (col1 < 0 || col2 >= prefixSums[0].length - 1 || col2 < col1) {
+ throw new IndexOutOfBoundsException("Invalid column indices");
+ }
+
+ return prefixSums[row2 + 1][col2 + 1] - prefixSums[row1][col2 + 1] - prefixSums[row2 + 1][col1] + prefixSums[row1][col1];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java
new file mode 100644
index 000000000000..14a02a2de4d0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * Implements an algorithm to efficiently compute the sum of elements
+ * between any two indices in an integer array using the Prefix Sum technique.
+ *
+ *
+ * Given an array nums, this algorithm precomputes the prefix sum array
+ * to allow O(1) sum queries for any range [left, right].
+ *
+ * Let prefixSum[i] be the sum of elements from index 0 to i-1.
+ * The sum of elements from left to right is:
+ *
+ *
+ * Time Complexity: O(N) for preprocessing, O(1) per query
+ * 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:
+ *
+ *
+ * The HashMap stores the frequency of each prefix sum encountered so far.
+ *
+ * Time Complexity: O(N)
+ * 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.
+ *
+ * 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.
*
+ * See Tower of Hanoi on Wikipedia.
+ *
* This method is called recursively to move n-1 discs
@@ -51,15 +57,20 @@ private TowerOfHanoi() {
*
+ * Example:
+ * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...
+ *
+ * This specific implementation demonstrates a Naive Recursive approach with {@code O(2^n)} time complexity.
+ * For more performant variations or different programming paradigms, see:
+ *
- * Worst-case performance O(log n) Best-case performance O(1) Average
- * performance O(log n) Worst-case space complexity O(1)
+ * Binary search is one of the most efficient searching algorithms for finding a target element
+ * in a SORTED array. It works by repeatedly dividing the search space in half, eliminating half of
+ * the remaining elements in each step.
+ *
+ * IMPORTANT: This algorithm ONLY works correctly if the input array is sorted in ascending
+ * order.
+ *
+ * Algorithm Overview: 1. Start with the entire array (left = 0, right = array.length - 1) 2.
+ * Calculate the middle index 3. Compare the middle element with the target: - If middle element
+ * equals target: Found! Return the index - If middle element is less than target: Search the right
+ * half - If middle element is greater than target: Search the left half 4. Repeat until element is
+ * found or search space is exhausted
+ *
+ * Performance Analysis: - Best-case time complexity: O(1) - Element found at middle on first
+ * try - Average-case time complexity: O(log n) - Most common scenario - Worst-case time
+ * complexity: O(log n) - Element not found or at extreme end - Space complexity: O(1) - Only uses
+ * a constant amount of extra space
+ *
+ * Example Walkthrough: Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] Target: 7
+ *
+ * Step 1: left=0, right=9, mid=4, array[4]=9 (9 > 7, search left half) Step 2: left=0,
+ * right=3, mid=1, array[1]=3 (3 < 7, search right half) Step 3: left=2, right=3, mid=2,
+ * array[2]=5 (5 < 7, search right half) Step 4: left=3, right=3, mid=3, array[3]=7 (Found!
+ * Return index 3)
*
* @author Varun Upadhyay (https://github.com/varunu28)
* @author Podshivalov Nikita (https://github.com/nikitap492)
@@ -18,38 +38,96 @@
class BinarySearch implements SearchAlgorithm {
/**
- * @param array is an array where the element should be found
- * @param key is an element which should be found
- * @param Example Usage:
+ * How it works:
+ * Time Complexity: O(log n) because we halve the search space each time.
+ * Space Complexity: O(log n) due to recursive call stack.
*
- * @param array The array to make the binary search
- * @param key The number you are looking for
- * @param left The lower bound
- * @param right The upper bound
- * @return the location of the key
+ * @param
- * 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:
+ *
- * 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:
+ *
- * Worst-case performance: O(βN)
+ * Time Complexity:
+ * Space Complexity: O(1) - only uses a constant amount of extra space
+ *
+ *
+ * Edge Cases:
+ *
+ * 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 {
+public final 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
public How it works step-by-step:
+ * Example:
+ *
- * Worst-case performance O(n) Best-case performance O(1) Average performance
- * O(n) Worst-case space complexity
+ * If target = 7:
+ * Output: -1 (not found)
+ *
- * Worst-case performance O(log n) Best-case performance O(1) Average
+ * Worst-case performance O(log n) Best-case performance O(log n) Average
* performance O(log n) Worst-case space complexity O(1)
*
* @author Pratik Padalia (https://github.com/15pratik)
@@ -26,10 +26,13 @@ class LowerBound implements SearchAlgorithm {
* @param array is an array where the LowerBound value is to be found
* @param key is an element for which the LowerBound is to be found
* @param
- * 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
+ * Example:
+ * {@code [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]}
+ *
+ *
+ * This is a modified binary search. When the array contains no duplicates, the
+ * time complexity is {@code O(log n)}. With duplicates, the algorithm still
+ * works but may degrade to {@code O(n)} in the worst case. The algorithm uses
+ * {@code O(1)} additional space and returns {@code -1} for null or empty input,
+ * or when the key is not present.
+ *
+ * @param array sorted array rotated at an unknown pivot
+ * @param key value to search for
+ * @return an index containing {@code key}, or {@code -1} if it is not present
+ *
+ * @see Search in rotated sorted array
+ * @see SearchAlgorithm
+ */
+public final class RotatedBinarySearch implements SearchAlgorithm {
+
+ @Override
+ public
- * Worst-case performance Ξ(log3(N)) Best-case performance O(1) Average
- * performance Ξ(log3(N)) Worst-case space complexity O(1)
+ * Worst-case performance: Ξ(log3(N))
- * Worst-case performance O(log n) Best-case performance O(1) Average
+ * Worst-case performance O(log n) Best-case performance O(log n) Average
* performance O(log n) Worst-case space complexity O(1)
*
* @author Pratik Padalia (https://github.com/15pratik)
@@ -26,10 +26,13 @@ class UpperBound implements SearchAlgorithm {
* @param array is an array where the UpperBound value is to be found
* @param key is an element for which the UpperBound is to be found
* @param This implementation utilizes a divide-and-conquer strategy, distributing
+ * the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}.
+ * To prevent the overhead of thread creation and context switching from outweighing
+ * the benefits of concurrency, it falls back to a standard sequential merge sort
+ * when the sub-array size drops below a predefined threshold, or when the maximum
+ * concurrency depth is reached (preventing thread starvation deadlocks).
+ *
+ * Complexity:
+ * The algorithm selects a pivot element and partitions the array into two
+ * subarrays such that:
+ * The subarrays are then recursively sorted until the entire array is ordered.
+ *
+ * This implementation uses randomization to reduce the probability of
+ * encountering worst-case performance on already sorted inputs.
+ *
+ * Time Complexity:
+ * Space Complexity: O(log n) due to recursion stack (in-place sorting).
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see SortAlgorithm
*/
+
class QuickSort implements SortAlgorithm {
- /**
- * This method implements the Generic Quick Sort
- *
- * @param array The array to be sorted Sorts the array in increasing order
- */
@Override
public It can be viewed as a variant of heapsort that maintains a forest of heap-ordered Leonardo trees
+ * (trees whose sizes are Leonardo numbers). The algorithm is adaptive: when the input is already
+ * sorted or nearly sorted, the heap invariants are often satisfied and the expensive rebalancing
+ * operations do little work, yielding near-linear behavior.
+ *
+ * Time Complexity:
+ * Space Complexity: O(1) auxiliary space (in-place).
+ *
+ * @see Smoothsort
+ * @see Leonardo numbers
+ * @see SortAlgorithm
+ */
+public class SmoothSort implements SortAlgorithm {
+
+ /**
+ * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that
+ * fits into a signed 32-bit integer.
+ */
+ private static final int[] LEONARDO = {1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703, 48315633, 78176337,
+ 126491971, 204668309, 331160281, 535828591, 866988873, 1402817465};
+
+ /**
+ * Sorts the given array in ascending order using Smooth Sort.
+ *
+ * @param array the array to sort
+ * @param The 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 Every windowed estimator forces the same bad choice. A long window is accurate while nothing
+ * changes and hopelessly slow once something does; a short one reacts immediately and is noisy the
+ * rest of the time. ADWIN refuses the choice: it keeps a window of recent values and, after every
+ * sample, looks for a way to split it into an old part and a recent part whose means are too far
+ * apart to be explained by chance. When it finds one, the old part is dropped. The window therefore
+ * grows on its own while the stream is stationary and collapses as soon as the stream moves, and the
+ * length it settles at is an estimate of how long the current regime has been running.
+ *
+ * "Too far apart" is a variance sensitive Hoeffding bound. For a cut into sub-windows of
+ * {@code n0} and {@code n1} elements, with {@code v} the variance of the whole window:
+ *
+ * The {@code delta} parameter is a confidence level: the probability of cutting a window that
+ * never changed is bounded by it, which is the guarantee that makes the window length trustworthy.
+ * Smaller values make the detector more conservative and slower.
+ *
+ * Keeping every sample would cost O(n) memory, so the window is stored as an exponential
+ * histogram: buckets of 1, 2, 4, 8 ... elements, at most {@code MAX_BUCKETS} of each size, each
+ * holding the sum and the variance of the elements it covers. That is O(log n) buckets for a window
+ * of n elements, and cuts are only tried at bucket boundaries, which is what keeps a sample O(log n)
+ * instead of O(n) while costing only a bounded loss of resolution.
+ *
+ * This class is not thread-safe.
+ *
+ * @see CusumDetector
+ * @see Concept drift, the problem ADWIN was written for; the algorithm is due to A. Bifet and R. Gavalda, Learning from Time-Changing Data with Adaptive Windowing, SDM 2007
+ */
+public final class Adwin {
+
+ /** Confidence level used when none is given. */
+ public static final double DEFAULT_DELTA = 0.002;
+
+ /** How many buckets of the same size the histogram holds before merging the two oldest. */
+ public static final int MAX_BUCKETS = 5;
+
+ private static final int MIN_SUBWINDOW = 5;
+ private static final long MIN_WIDTH_FOR_DETECTION = 2L * MIN_SUBWINDOW;
+
+ private final double delta;
+
+ private Row newest;
+ private Row oldest;
+
+ private long width;
+ private double total;
+ private double variance;
+ private int bucketCount;
+ private long count;
+ private long changeCount;
+
+ /**
+ * Creates a window with the customary confidence level of {@code 0.002}.
+ */
+ public Adwin() {
+ this(DEFAULT_DELTA);
+ }
+
+ /**
+ * Creates a window.
+ *
+ * @param delta the confidence level, in {@code (0, 1)}; smaller values cut less eagerly
+ * @throws IllegalArgumentException if {@code delta} is outside {@code (0, 1)}
+ */
+ public Adwin(double delta) {
+ if (!(delta > 0.0) || !(delta < 1.0)) {
+ throw new IllegalArgumentException("The delta must lie in (0, 1), but was " + delta);
+ }
+ this.delta = delta;
+ start();
+ }
+
+ /**
+ * Feeds one sample into the window.
+ *
+ * @param value the incoming sample
+ * @return {@code true} if the window was cut, that is if the stream changed
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public boolean accept(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ count++;
+ insert(value);
+ return detectChange();
+ }
+
+ /**
+ * Runs the window over a whole signal.
+ *
+ * @param signal the samples to inspect
+ * @return a new array of the same length saying for every sample whether it cut the window
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public boolean[] scan(double[] signal) {
+ boolean[] cuts = new boolean[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ cuts[i] = accept(signal[i]);
+ }
+ return cuts;
+ }
+
+ /**
+ * Returns the current estimate of the level of the stream.
+ *
+ * @return the mean of the window, {@code 0} while the window is empty
+ */
+ public double estimate() {
+ return width == 0 ? 0.0 : total / width;
+ }
+
+ /**
+ * Returns the length of the window, which is how many recent samples the estimate rests on.
+ *
+ * @return the window width
+ */
+ public long width() {
+ return width;
+ }
+
+ /**
+ * Returns the variance of the samples inside the window.
+ *
+ * @return the window variance, {@code 0} while the window holds fewer than two samples
+ */
+ public double variance() {
+ return width < 2 ? 0.0 : variance / width;
+ }
+
+ /**
+ * Returns how many buckets the histogram holds, which grows like the logarithm of the width.
+ *
+ * @return the bucket count
+ */
+ public int bucketCount() {
+ return bucketCount;
+ }
+
+ /**
+ * Returns how many samples have been inspected since the last reset.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Returns how many times the window has been cut since the last reset.
+ *
+ * @return the number of detected changes
+ */
+ public long changeCount() {
+ return changeCount;
+ }
+
+ /**
+ * Returns the configured confidence level.
+ *
+ * @return the delta given at construction time
+ */
+ public double delta() {
+ return delta;
+ }
+
+ /**
+ * Empties the window.
+ */
+ public void reset() {
+ start();
+ count = 0;
+ changeCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Adwin{width=" + width + ", estimate=" + estimate() + ", buckets=" + bucketCount + ", changes=" + changeCount + "}";
+ }
+
+ private void start() {
+ newest = new Row(0);
+ oldest = newest;
+ width = 0;
+ total = 0.0;
+ variance = 0.0;
+ bucketCount = 0;
+ }
+
+ private void insert(double value) {
+ width++;
+ newest.add(value, 0.0);
+ bucketCount++;
+ if (width > 1) {
+ double deviation = value - total / (width - 1);
+ variance += (width - 1) * deviation * deviation / width;
+ }
+ total += value;
+ compress();
+ }
+
+ /**
+ * Merges the two oldest buckets of every row that has run out of room into one bucket of the next
+ * row, which is what keeps the number of buckets logarithmic in the width.
+ */
+ private void compress() {
+ Row row = newest;
+ while (row != null && row.size > MAX_BUCKETS) {
+ if (row.older == null) {
+ row.older = new Row(row.level + 1);
+ row.older.newer = row;
+ oldest = row.older;
+ }
+ long size = 1L << row.level;
+ double firstMean = row.totals[0] / size;
+ double secondMean = row.totals[1] / size;
+ double merged = size * size * (firstMean - secondMean) * (firstMean - secondMean) / (size + size);
+ row.older.add(row.totals[0] + row.totals[1], row.variances[0] + row.variances[1] + merged);
+ row.removeOldest(2);
+ bucketCount--;
+ row = row.older;
+ }
+ }
+
+ /**
+ * Tries every cut the histogram allows, from the oldest boundary inwards, and drops the oldest
+ * bucket whenever a cut is significant. Repeats until no cut is left.
+ *
+ * @return whether anything was dropped
+ */
+ private boolean detectChange() {
+ boolean changed = false;
+ boolean searching = true;
+
+ while (searching && width >= MIN_WIDTH_FOR_DETECTION) {
+ searching = false;
+ long oldWidth = 0;
+ double oldTotal = 0.0;
+
+ outer:
+ for (Row row = oldest; row != null; row = row.newer) {
+ for (int bucket = 0; bucket < row.size; bucket++) {
+ if (row.newer == null && bucket == row.size - 1) {
+ break outer;
+ }
+ oldWidth += 1L << row.level;
+ oldTotal += row.totals[bucket];
+ long recentWidth = width - oldWidth;
+ double recentTotal = total - oldTotal;
+ if (recentWidth < MIN_SUBWINDOW) {
+ break outer;
+ }
+ if (oldWidth >= MIN_SUBWINDOW && isSignificant(oldWidth, recentWidth, oldTotal / oldWidth - recentTotal / recentWidth)) {
+ changed = true;
+ searching = true;
+ changeCount++;
+ dropOldestBucket();
+ break outer;
+ }
+ }
+ }
+ }
+ return changed;
+ }
+
+ private boolean isSignificant(long oldWidth, long recentWidth, double difference) {
+ double harmonic = 1.0 / (oldWidth - MIN_SUBWINDOW + 1) + 1.0 / (recentWidth - MIN_SUBWINDOW + 1);
+ double confidence = Math.log(2.0 * Math.log(width) / delta);
+ double windowVariance = variance / width;
+ double epsilon = Math.sqrt(2.0 * harmonic * windowVariance * confidence) + 2.0 / 3.0 * confidence * harmonic;
+ return Math.abs(difference) > epsilon;
+ }
+
+ private void dropOldestBucket() {
+ Row row = oldest;
+ long size = 1L << row.level;
+ double bucketTotal = row.totals[0];
+
+ width -= size;
+ total -= bucketTotal;
+ if (width > 0) {
+ double bucketMean = bucketTotal / size;
+ double difference = bucketMean - total / width;
+ variance -= row.variances[0] + size * width * difference * difference / (size + width);
+ } else {
+ variance = 0.0;
+ }
+ if (variance < 0.0) {
+ variance = 0.0;
+ }
+
+ row.removeOldest(1);
+ bucketCount--;
+ if (row.size == 0 && row.newer != null) {
+ oldest = row.newer;
+ oldest.older = null;
+ }
+ }
+
+ /**
+ * One row of the exponential histogram: up to {@code MAX_BUCKETS + 1} buckets that each cover
+ * {@code 2^level} samples, the oldest at index zero.
+ */
+ private static final class Row {
+
+ private final int level;
+ private final double[] totals = new double[MAX_BUCKETS + 1];
+ private final double[] variances = new double[MAX_BUCKETS + 1];
+ private int size;
+ private Row older;
+ private Row newer;
+
+ Row(int level) {
+ this.level = level;
+ }
+
+ void add(double total, double variance) {
+ totals[size] = total;
+ variances[size] = variance;
+ size++;
+ }
+
+ void removeOldest(int buckets) {
+ for (int i = buckets; i < size; i++) {
+ totals[i - buckets] = totals[i];
+ variances[i - buckets] = variances[i];
+ }
+ size -= buckets;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/ComplementaryFilter.java b/src/main/java/com/thealgorithms/streaming/ComplementaryFilter.java
new file mode 100644
index 000000000000..c4dac5799d79
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/ComplementaryFilter.java
@@ -0,0 +1,239 @@
+package com.thealgorithms.streaming;
+
+/**
+ * The complementary filter: one estimate out of two sensors that are each wrong in a
+ * different way.
+ *
+ * The classic pair is an accelerometer and a gyroscope measuring the same tilt. The accelerometer
+ * knows where down is and never drifts, but every vibration of the frame shows up in it. The
+ * gyroscope is smooth and immune to vibration, but it measures a rate, so using it means integrating,
+ * and the smallest bias in that rate integrates into an angle that walks away without limit. Neither
+ * is usable alone; their errors live in different parts of the spectrum, which is exactly the
+ * situation this filter is for.
+ *
+ * Read as a pair of filters that add up to one, it is a high pass on the integrated rate and a low
+ * pass on the absolute reading: the drift of the first is cut off below the corner frequency and the
+ * noise of the second above it. The two transfer functions sum to unity at every frequency, so the
+ * true signal passes through untouched whatever {@code a} is. That is where the name comes from, and
+ * it is also why the filter cannot introduce a lag of its own the way a plain low pass on the
+ * accelerometer would.
+ *
+ * The single parameter is best thought of as a time constant rather than as a number near one:
+ *
+ * Below {@code tau} the answer comes from the gyroscope, above it from the accelerometer. That
+ * also fixes the price of the trade exactly: a gyroscope with a constant bias {@code b} leaves a
+ * steady state error of {@code tau * b} and no more, where plain integration would have grown without
+ * limit. Use {@link #ofTimeConstant(double, double)} to set it that way round.
+ *
+ * Against {@link KalmanFilter}: the Kalman filter is the right answer when the noise of both
+ * sensors is known and worth modelling, and it will beat this one when it is. The complementary
+ * filter needs no covariance, no model of the process, two multiplications per sample and one number
+ * of state, and it degrades gracefully when the noise is not what anybody assumed. That is why it is
+ * what actually runs on small flight controllers.
+ *
+ * Each sample costs O(1) time and the filter keeps one number of state. This class is not
+ * thread-safe.
+ *
+ * @see KalmanFilter
+ * @see Complementary filter
+ */
+public final class ComplementaryFilter {
+
+ /** Weight given to the integrated rate when none is chosen, the usual setting for an IMU. */
+ public static final double DEFAULT_COEFFICIENT = 0.98;
+
+ private final double coefficient;
+
+ private double value;
+ private long count;
+
+ /**
+ * Creates a filter that leans on the rate with the customary weight of {@code 0.98}.
+ */
+ public ComplementaryFilter() {
+ this(DEFAULT_COEFFICIENT);
+ }
+
+ /**
+ * Creates a filter.
+ *
+ * @param coefficient how much of the estimate comes from the integrated rate, in {@code (0, 1)};
+ * closer to one trusts the rate for longer, closer to zero follows the reference more quickly
+ * @throws IllegalArgumentException if {@code coefficient} is outside {@code (0, 1)}
+ */
+ public ComplementaryFilter(double coefficient) {
+ if (!(coefficient > 0.0) || !(coefficient < 1.0)) {
+ throw new IllegalArgumentException("The coefficient must lie in (0, 1), but was " + coefficient);
+ }
+ this.coefficient = coefficient;
+ }
+
+ /**
+ * Creates a filter from the time constant that separates the two sensors, which is usually the
+ * quantity that is actually known: {@code a = tau / (tau + dt)}.
+ *
+ * @param timeConstant how long the rate is trusted before the reference takes over, strictly positive
+ * @param samplingInterval the interval between samples, strictly positive and in the same unit
+ * @return a new filter
+ * @throws IllegalArgumentException if either argument is not finite and strictly positive
+ */
+ public static ComplementaryFilter ofTimeConstant(double timeConstant, double samplingInterval) {
+ requirePositive(timeConstant, "time constant");
+ requirePositive(samplingInterval, "sampling interval");
+ return new ComplementaryFilter(timeConstant / (timeConstant + samplingInterval));
+ }
+
+ /**
+ * Feeds one pair of readings taken one unit of time after the previous one.
+ *
+ * @param rate the reading of the drifting sensor, a derivative of the estimated quantity
+ * @param reference the reading of the noisy but drift free sensor, in the unit of the estimate
+ * @return the updated estimate
+ * @throws IllegalArgumentException if a reading is NaN or infinite
+ */
+ public double accept(double rate, double reference) {
+ return accept(rate, reference, 1.0);
+ }
+
+ /**
+ * Feeds one pair of readings.
+ *
+ * @param rate the reading of the drifting sensor, a derivative of the estimated quantity
+ * @param reference the reading of the noisy but drift free sensor, in the unit of the estimate
+ * @param elapsed time since the previous pair, strictly positive
+ * @return the updated estimate; the very first pair is answered with the reference alone, because
+ * there is nothing yet to integrate from
+ * @throws IllegalArgumentException if a reading is NaN or infinite, or if {@code elapsed} is not
+ * finite and strictly positive
+ */
+ public double accept(double rate, double reference, double elapsed) {
+ requireFinite(rate, "rate");
+ requireFinite(reference, "reference");
+ requirePositive(elapsed, "elapsed time");
+
+ if (count == 0) {
+ value = reference;
+ } else {
+ value = coefficient * (value + rate * elapsed) + (1.0 - coefficient) * reference;
+ }
+ count++;
+ return value;
+ }
+
+ /**
+ * Runs the filter over a whole pair of recordings sampled at unit intervals.
+ *
+ * @param rates the readings of the drifting sensor
+ * @param references the readings of the drift free sensor, as many as there are rates
+ * @return a new array of the same length holding the estimate after every sample
+ * @throws IllegalArgumentException if the two recordings differ in length or hold a reading that
+ * is NaN or infinite
+ * @throws NullPointerException if a recording is {@code null}
+ */
+ public double[] scan(double[] rates, double[] references) {
+ if (rates.length != references.length) {
+ throw new IllegalArgumentException("Every rate needs a reference, but there were " + rates.length + " and " + references.length);
+ }
+ double[] estimates = new double[rates.length];
+ for (int i = 0; i < rates.length; i++) {
+ estimates[i] = accept(rates[i], references[i]);
+ }
+ return estimates;
+ }
+
+ /**
+ * Returns the current estimate.
+ *
+ * @return the estimate after the last pair of readings, {@code 0} before the first one
+ */
+ public double value() {
+ return value;
+ }
+
+ /**
+ * Returns the weight given to the integrated rate.
+ *
+ * @return the coefficient given at construction time
+ */
+ public double coefficient() {
+ return coefficient;
+ }
+
+ /**
+ * Returns the time constant the filter works out to at a given sampling interval, that is
+ * {@code a * dt / (1 - a)}: the horizon below which the rate decides the answer and above which
+ * the reference does.
+ *
+ * @param samplingInterval the interval between samples, strictly positive
+ * @return the time constant, in the unit of the interval
+ * @throws IllegalArgumentException if {@code samplingInterval} is not finite and strictly positive
+ */
+ public double timeConstant(double samplingInterval) {
+ requirePositive(samplingInterval, "sampling interval");
+ return coefficient * samplingInterval / (1.0 - coefficient);
+ }
+
+ /**
+ * Returns how many pairs of readings have been filtered since the last reset.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Forgets everything seen so far, so that the next reference seeds the estimate again.
+ */
+ public void reset() {
+ value = 0.0;
+ count = 0;
+ }
+
+ /**
+ * Restarts the filter from a known estimate, which is what to do after the process has been moved
+ * by something the sensors could not see.
+ *
+ * @param estimate the value to carry on from
+ * @throws IllegalArgumentException if {@code estimate} is NaN or infinite
+ */
+ public void reset(double estimate) {
+ requireFinite(estimate, "estimate");
+ value = estimate;
+ count = 1;
+ }
+
+ @Override
+ public String toString() {
+ return "ComplementaryFilter{coefficient=" + coefficient + ", value=" + value + ", samples=" + count + "}";
+ }
+
+ private static void requireFinite(double value, String name) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite, but was " + value);
+ }
+ }
+
+ private static void requirePositive(double value, String name) {
+ if (!(value > 0.0) || !Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite and strictly positive, but was " + value);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/CusumDetector.java b/src/main/java/com/thealgorithms/streaming/CusumDetector.java
new file mode 100644
index 000000000000..618edb25a9af
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/CusumDetector.java
@@ -0,0 +1,254 @@
+package com.thealgorithms.streaming;
+
+/**
+ * CUSUM (cumulative sum) change detection: it notices that the level of a stream has shifted,
+ * long before the shift is visible in a moving average.
+ *
+ * A threshold on the raw samples can only catch a change that is large compared with the noise.
+ * CUSUM instead accumulates evidence. Every sample is normalised into
+ * {@code z = (x - target) / sigma} and pushed into two one-sided sums:
+ *
+ * The allowance {@code k} is a toll paid on every step. While the stream sits at its
+ * target the toll exceeds the average evidence, both sums are pinned at zero and the detector stays
+ * quiet no matter how long it runs. As soon as the mean shifts by more than {@code k} standard
+ * deviations, the corresponding sum starts drifting upwards, and it keeps drifting: a small but
+ * persistent bias accumulates until it crosses the decision threshold {@code h}. That is the whole
+ * point of the method - a shift of half a standard deviation is invisible in any single sample, yet
+ * unmistakable after twenty of them.
+ *
+ * The classic tuning is {@code k = delta / 2} for the shift size {@code delta} one wants to catch
+ * quickly, together with {@code h} between 4 and 5, which keeps false alarms rare while detecting a
+ * one sigma shift within roughly ten samples. Both sums are cleared whenever an alarm fires, so the
+ * detector immediately starts looking for the next change instead of latching.
+ *
+ * Each sample costs O(1) time and the detector keeps O(1) state. This class is not thread-safe.
+ *
+ * @see CUSUM
+ */
+public final class CusumDetector {
+
+ /** Allowance used when none is given; it targets shifts of one standard deviation. */
+ public static final double DEFAULT_ALLOWANCE = 0.5;
+
+ /** Decision threshold used when none is given. */
+ public static final double DEFAULT_THRESHOLD = 5.0;
+
+ private final double target;
+ private final double standardDeviation;
+ private final double allowance;
+ private final double threshold;
+
+ private double upperSum;
+ private double lowerSum;
+ private long count;
+ private long alarmCount;
+ private ShiftSignal lastSignal = ShiftSignal.NONE;
+
+ /**
+ * Creates a detector tuned for shifts of about one standard deviation.
+ *
+ * @param target the level the stream is expected to sit at
+ * @param standardDeviation the noise level of the stream, strictly positive
+ * @throws IllegalArgumentException if {@code target} is not finite or {@code standardDeviation} is not strictly positive
+ */
+ public CusumDetector(double target, double standardDeviation) {
+ this(target, standardDeviation, DEFAULT_ALLOWANCE, DEFAULT_THRESHOLD);
+ }
+
+ /**
+ * Creates a detector.
+ *
+ * @param target the level the stream is expected to sit at
+ * @param standardDeviation the noise level of the stream, strictly positive
+ * @param allowance the toll subtracted on every step, in standard deviations; half of the shift
+ * size one wants to detect quickly
+ * @param threshold how much accumulated evidence raises an alarm, in standard deviations
+ * @throws IllegalArgumentException if any argument is not finite, if {@code standardDeviation} or
+ * {@code threshold} is not strictly positive, or if {@code allowance} is negative
+ */
+ public CusumDetector(double target, double standardDeviation, double allowance, double threshold) {
+ requireFinite(target, "target");
+ if (!(standardDeviation > 0.0) || !Double.isFinite(standardDeviation)) {
+ throw new IllegalArgumentException("The standard deviation must be finite and strictly positive, but was " + standardDeviation);
+ }
+ if (!(allowance >= 0.0) || !Double.isFinite(allowance)) {
+ throw new IllegalArgumentException("The allowance must be finite and non-negative, but was " + allowance);
+ }
+ if (!(threshold > 0.0) || !Double.isFinite(threshold)) {
+ throw new IllegalArgumentException("The threshold must be finite and strictly positive, but was " + threshold);
+ }
+ this.target = target;
+ this.standardDeviation = standardDeviation;
+ this.allowance = allowance;
+ this.threshold = threshold;
+ }
+
+ /**
+ * Feeds one sample into the detector.
+ *
+ * @param value the incoming sample
+ * @return {@link ShiftSignal#NONE} while the stream stays in control, otherwise the direction of
+ * the detected shift; the accumulated sums are cleared on an alarm
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public ShiftSignal accept(double value) {
+ requireFinite(value, "sample");
+ count++;
+
+ double normalized = (value - target) / standardDeviation;
+ upperSum = Math.max(0.0, upperSum + normalized - allowance);
+ lowerSum = Math.max(0.0, lowerSum - normalized - allowance);
+
+ if (upperSum > threshold) {
+ lastSignal = ShiftSignal.UPWARD;
+ } else if (lowerSum > threshold) {
+ lastSignal = ShiftSignal.DOWNWARD;
+ } else {
+ lastSignal = ShiftSignal.NONE;
+ }
+
+ if (lastSignal.isAlarm()) {
+ alarmCount++;
+ upperSum = 0.0;
+ lowerSum = 0.0;
+ }
+ return lastSignal;
+ }
+
+ /**
+ * Runs the detector over a whole signal.
+ *
+ * @param signal the samples to inspect
+ * @return a new array of the same length holding the verdict for every sample
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public ShiftSignal[] scan(double[] signal) {
+ ShiftSignal[] signals = new ShiftSignal[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ signals[i] = accept(signal[i]);
+ }
+ return signals;
+ }
+
+ /**
+ * Returns the evidence accumulated in favour of an upward shift.
+ *
+ * @return the one-sided upper sum, never negative
+ */
+ public double upperSum() {
+ return upperSum;
+ }
+
+ /**
+ * Returns the evidence accumulated in favour of a downward shift.
+ *
+ * @return the one-sided lower sum, never negative
+ */
+ public double lowerSum() {
+ return lowerSum;
+ }
+
+ /**
+ * Returns the verdict on the most recent sample.
+ *
+ * @return the last signal, {@link ShiftSignal#NONE} before the first sample
+ */
+ public ShiftSignal lastSignal() {
+ return lastSignal;
+ }
+
+ /**
+ * Returns how many samples have been inspected since the last reset.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Returns how many alarms have been raised since the last reset.
+ *
+ * @return the alarm count
+ */
+ public long alarmCount() {
+ return alarmCount;
+ }
+
+ /**
+ * Returns the expected level of the stream.
+ *
+ * @return the target given at construction time
+ */
+ public double target() {
+ return target;
+ }
+
+ /**
+ * Returns the assumed noise level.
+ *
+ * @return the standard deviation given at construction time
+ */
+ public double standardDeviation() {
+ return standardDeviation;
+ }
+
+ /**
+ * Returns the per-step allowance.
+ *
+ * @return the allowance given at construction time
+ */
+ public double allowance() {
+ return allowance;
+ }
+
+ /**
+ * Returns the decision threshold.
+ *
+ * @return the threshold given at construction time
+ */
+ public double threshold() {
+ return threshold;
+ }
+
+ /**
+ * Clears the accumulated evidence and the counters.
+ */
+ public void reset() {
+ upperSum = 0.0;
+ lowerSum = 0.0;
+ count = 0;
+ alarmCount = 0;
+ lastSignal = ShiftSignal.NONE;
+ }
+
+ @Override
+ public String toString() {
+ return "CusumDetector{target=" + target + ", upperSum=" + upperSum + ", lowerSum=" + lowerSum + ", alarms=" + alarmCount + '}';
+ }
+
+ private static void requireFinite(double value, String name) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite, but was " + value);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/DynamicTimeWarping.java b/src/main/java/com/thealgorithms/streaming/DynamicTimeWarping.java
new file mode 100644
index 000000000000..52e188cee2c5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/DynamicTimeWarping.java
@@ -0,0 +1,190 @@
+package com.thealgorithms.streaming;
+
+import java.util.Arrays;
+
+/**
+ * Dynamic time warping: how far apart two series are once one of them is allowed to be
+ * stretched and squeezed in time.
+ *
+ * The Euclidean distance compares sample {@code i} with sample {@code i} and nothing else, so two
+ * recordings of the same gesture, one performed slightly faster, come out as far apart as two
+ * unrelated ones. Dynamic time warping instead looks for the cheapest way to line the two series up:
+ * every point of the first has to be matched to at least one point of the second and the other way
+ * round, the matching may never go backwards, and the cost is the sum over the matched pairs. That
+ * alignment is a shortest path through a grid, and the dynamic program is the obvious one:
+ *
+ * The three predecessors are exactly the three legal moves: consume a point of the first series,
+ * of the second, or of both. The answer is the bottom right corner.
+ *
+ * Left alone, the alignment may match one point of a series against an arbitrarily long stretch of
+ * the other, which is rarely meaningful and costs {@code O(n * m)} regardless. The Sakoe-Chiba band
+ * forbids matches further apart in time than a given width, which both rules out those degenerate
+ * alignments and narrows the grid that has to be filled. The band has to be at least the difference
+ * in length, or no alignment exists at all.
+ *
+ * Note that the result is not a metric: it does not satisfy the triangle inequality, so it can be
+ * used to rank candidates but not to index them without further care.
+ *
+ * The distance costs O(n * m) time and O(min(n, m)) memory; the path costs O(n * m) of both,
+ * because it has to remember the grid.
+ *
+ * @see Dynamic time warping
+ */
+public final class DynamicTimeWarping {
+
+ private DynamicTimeWarping() {
+ }
+
+ /**
+ * Returns the warping distance between two series.
+ *
+ * @param first the first series, left untouched
+ * @param second the second series, left untouched
+ * @return the cost of the cheapest alignment
+ * @throws IllegalArgumentException if a series is empty or holds a non-finite value
+ * @throws NullPointerException if a series is {@code null}
+ */
+ public static double distance(double[] first, double[] second) {
+ return distance(first, second, Math.max(first.length, second.length));
+ }
+
+ /**
+ * Returns the warping distance between two series, with the alignment confined to a Sakoe-Chiba
+ * band.
+ *
+ * @param first the first series, left untouched
+ * @param second the second series, left untouched
+ * @param band how far apart in time two matched points may be, at least the difference in length
+ * @return the cost of the cheapest alignment inside the band
+ * @throws IllegalArgumentException if a series is empty or holds a non-finite value, or if the
+ * band is too narrow for any alignment to exist
+ * @throws NullPointerException if a series is {@code null}
+ */
+ public static double distance(double[] first, double[] second, int band) {
+ requireSeries(first, "first");
+ requireSeries(second, "second");
+ requireBand(band, first.length, second.length);
+
+ double[] previous = new double[second.length + 1];
+ double[] current = new double[second.length + 1];
+ Arrays.fill(previous, Double.POSITIVE_INFINITY);
+ previous[0] = 0.0;
+
+ for (int i = 1; i <= first.length; i++) {
+ Arrays.fill(current, Double.POSITIVE_INFINITY);
+ int from = Math.max(1, i - band);
+ int to = Math.min(second.length, i + band);
+ for (int j = from; j <= to; j++) {
+ double cost = Math.abs(first[i - 1] - second[j - 1]);
+ double best = Math.min(previous[j], Math.min(current[j - 1], previous[j - 1]));
+ current[j] = cost + best;
+ }
+ double[] swap = previous;
+ previous = current;
+ current = swap;
+ }
+ return previous[second.length];
+ }
+
+ /**
+ * Returns the cheapest alignment itself.
+ *
+ * @param first the first series, left untouched
+ * @param second the second series, left untouched
+ * @return the matched pairs of indices, from {@code (0, 0)} to the two last indices
+ * @throws IllegalArgumentException if a series is empty or holds a non-finite value
+ * @throws NullPointerException if a series is {@code null}
+ */
+ public static int[][] path(double[] first, double[] second) {
+ return path(first, second, Math.max(first.length, second.length));
+ }
+
+ /**
+ * Returns the cheapest alignment inside a Sakoe-Chiba band.
+ *
+ * @param first the first series, left untouched
+ * @param second the second series, left untouched
+ * @param band how far apart in time two matched points may be, at least the difference in length
+ * @return the matched pairs of indices, from {@code (0, 0)} to the two last indices
+ * @throws IllegalArgumentException if a series is empty or holds a non-finite value, or if the
+ * band is too narrow for any alignment to exist
+ * @throws NullPointerException if a series is {@code null}
+ */
+ public static int[][] path(double[] first, double[] second, int band) {
+ requireSeries(first, "first");
+ requireSeries(second, "second");
+ requireBand(band, first.length, second.length);
+
+ double[][] grid = new double[first.length + 1][second.length + 1];
+ for (double[] row : grid) {
+ Arrays.fill(row, Double.POSITIVE_INFINITY);
+ }
+ grid[0][0] = 0.0;
+
+ for (int i = 1; i <= first.length; i++) {
+ int from = Math.max(1, i - band);
+ int to = Math.min(second.length, i + band);
+ for (int j = from; j <= to; j++) {
+ double cost = Math.abs(first[i - 1] - second[j - 1]);
+ grid[i][j] = cost + Math.min(grid[i - 1][j], Math.min(grid[i][j - 1], grid[i - 1][j - 1]));
+ }
+ }
+
+ int steps = 0;
+ int row = first.length;
+ int column = second.length;
+ int[][] reversed = new int[first.length + second.length][2];
+ while (row > 0 && column > 0) {
+ reversed[steps][0] = row - 1;
+ reversed[steps][1] = column - 1;
+ steps++;
+ double diagonal = grid[row - 1][column - 1];
+ double above = grid[row - 1][column];
+ double left = grid[row][column - 1];
+ if (diagonal <= above && diagonal <= left) {
+ row--;
+ column--;
+ } else if (above <= left) {
+ row--;
+ } else {
+ column--;
+ }
+ }
+
+ int[][] alignment = new int[steps][2];
+ for (int i = 0; i < steps; i++) {
+ alignment[i] = reversed[steps - 1 - i];
+ }
+ return alignment;
+ }
+
+ private static void requireSeries(double[] series, String name) {
+ if (series.length == 0) {
+ throw new IllegalArgumentException("The " + name + " series must not be empty");
+ }
+ for (double value : series) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but the " + name + " series held " + value);
+ }
+ }
+ }
+
+ private static void requireBand(int band, int firstLength, int secondLength) {
+ int minimum = Math.abs(firstLength - secondLength);
+ if (band < minimum) {
+ throw new IllegalArgumentException("The band must be at least the difference in length, " + minimum + ", but was " + band);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/EwmaChangeDetector.java b/src/main/java/com/thealgorithms/streaming/EwmaChangeDetector.java
new file mode 100644
index 000000000000..88d60da02988
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/EwmaChangeDetector.java
@@ -0,0 +1,251 @@
+package com.thealgorithms.streaming;
+
+/**
+ * An EWMA control chart: change detection built on the exponentially weighted moving average.
+ *
+ * The detector smooths the stream with
+ * {@link ExponentialMovingAverage} seeded at the target level and compares the smoothed value with a
+ * band around that target:
+ *
+ * The band is the exact standard deviation of {@code z} under the null hypothesis, multiplied by
+ * the desired width in sigmas. It starts narrow and widens towards its asymptote
+ * {@code width * sigma * sqrt(alpha / (2 - alpha))}, which keeps the false alarm rate steady during
+ * the warm-up instead of letting the first few samples trip the alarm. An alarm fires as soon as the
+ * smoothed value leaves the band; the average is then reset to the target so that the detector
+ * starts fresh on the next change rather than latching.
+ *
+ * Where {@link CusumDetector} accumulates evidence without limit and so excels at small,
+ * persistent shifts, an EWMA chart looks at a decaying window of the recent past: {@code alpha}
+ * around {@code 0.1 - 0.3} is a good compromise, larger values reacting faster to big jumps and
+ * smaller ones being more sensitive to slow drifts. A width of 3 sigmas is the customary setting.
+ *
+ * Each sample costs O(1) time and the detector keeps O(1) state. This class is not thread-safe.
+ *
+ * @see CusumDetector
+ * @see ExponentialMovingAverage
+ * @see EWMA chart
+ */
+public final class EwmaChangeDetector {
+
+ /** Smoothing factor used when none is given. */
+ public static final double DEFAULT_ALPHA = 0.2;
+
+ /** Half-width of the control band, in sigmas, used when none is given. */
+ public static final double DEFAULT_WIDTH = 3.0;
+
+ private final double target;
+ private final double standardDeviation;
+ private final double alpha;
+ private final double width;
+
+ private final ExponentialMovingAverage average;
+ private long count;
+ private long stepsSinceAlarm;
+ private long alarmCount;
+ private ShiftSignal lastSignal = ShiftSignal.NONE;
+
+ /**
+ * Creates a detector with the customary smoothing factor of {@code 0.2} and a band of three
+ * sigmas.
+ *
+ * @param target the level the stream is expected to sit at
+ * @param standardDeviation the noise level of the stream, strictly positive
+ * @throws IllegalArgumentException if {@code target} is not finite or {@code standardDeviation} is not strictly positive
+ */
+ public EwmaChangeDetector(double target, double standardDeviation) {
+ this(target, standardDeviation, DEFAULT_ALPHA, DEFAULT_WIDTH);
+ }
+
+ /**
+ * Creates a detector.
+ *
+ * @param target the level the stream is expected to sit at
+ * @param standardDeviation the noise level of the stream, strictly positive
+ * @param alpha smoothing factor in {@code (0, 1]}; larger values react faster but tolerate less noise
+ * @param width half-width of the control band in sigmas, strictly positive
+ * @throws IllegalArgumentException if any argument is not finite, if {@code standardDeviation} or
+ * {@code width} is not strictly positive, or if {@code alpha} is outside {@code (0, 1]}
+ */
+ public EwmaChangeDetector(double target, double standardDeviation, double alpha, double width) {
+ if (!Double.isFinite(target)) {
+ throw new IllegalArgumentException("The target must be finite, but was " + target);
+ }
+ if (!(standardDeviation > 0.0) || !Double.isFinite(standardDeviation)) {
+ throw new IllegalArgumentException("The standard deviation must be finite and strictly positive, but was " + standardDeviation);
+ }
+ if (!(width > 0.0) || !Double.isFinite(width)) {
+ throw new IllegalArgumentException("The width must be finite and strictly positive, but was " + width);
+ }
+ this.target = target;
+ this.standardDeviation = standardDeviation;
+ this.width = width;
+ this.average = ExponentialMovingAverage.ofAlpha(alpha, target);
+ this.alpha = this.average.alpha();
+ }
+
+ /**
+ * Feeds one sample into the detector.
+ *
+ * @param value the incoming sample
+ * @return {@link ShiftSignal#NONE} while the smoothed value stays inside the control band,
+ * otherwise the direction in which it left the band; the average is reset to the target on an alarm
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public ShiftSignal accept(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ count++;
+ stepsSinceAlarm++;
+ double smoothed = average.add(value);
+ double deviation = smoothed - target;
+ double limit = controlLimit();
+
+ if (deviation > limit) {
+ lastSignal = ShiftSignal.UPWARD;
+ } else if (deviation < -limit) {
+ lastSignal = ShiftSignal.DOWNWARD;
+ } else {
+ lastSignal = ShiftSignal.NONE;
+ }
+
+ if (lastSignal.isAlarm()) {
+ alarmCount++;
+ stepsSinceAlarm = 0;
+ average.reset();
+ }
+ return lastSignal;
+ }
+
+ /**
+ * Runs the detector over a whole signal.
+ *
+ * @param signal the samples to inspect
+ * @return a new array of the same length holding the verdict for every sample
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public ShiftSignal[] scan(double[] signal) {
+ ShiftSignal[] signals = new ShiftSignal[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ signals[i] = accept(signal[i]);
+ }
+ return signals;
+ }
+
+ /**
+ * Returns the smoothed value the alarm decision is based on.
+ *
+ * @return the exponentially weighted average of the stream
+ */
+ public double statistic() {
+ return average.value();
+ }
+
+ /**
+ * Returns the current half-width of the control band, which widens during the warm-up and then
+ * settles.
+ *
+ * @return the distance from the target at which an alarm fires
+ */
+ public double controlLimit() {
+ double asymptotic = alpha / (2.0 - alpha);
+ double warmUp = -Math.expm1(2.0 * stepsSinceAlarm * Math.log1p(-alpha));
+ return width * standardDeviation * Math.sqrt(asymptotic * warmUp);
+ }
+
+ /**
+ * Returns the verdict on the most recent sample.
+ *
+ * @return the last signal, {@link ShiftSignal#NONE} before the first sample
+ */
+ public ShiftSignal lastSignal() {
+ return lastSignal;
+ }
+
+ /**
+ * Returns how many samples have been inspected since the last reset.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Returns how many alarms have been raised since the last reset.
+ *
+ * @return the alarm count
+ */
+ public long alarmCount() {
+ return alarmCount;
+ }
+
+ /**
+ * Returns the expected level of the stream.
+ *
+ * @return the target given at construction time
+ */
+ public double target() {
+ return target;
+ }
+
+ /**
+ * Returns the assumed noise level.
+ *
+ * @return the standard deviation given at construction time
+ */
+ public double standardDeviation() {
+ return standardDeviation;
+ }
+
+ /**
+ * Returns the smoothing factor in use.
+ *
+ * @return alpha
+ */
+ public double alpha() {
+ return alpha;
+ }
+
+ /**
+ * Returns the configured band half-width.
+ *
+ * @return the width in sigmas given at construction time
+ */
+ public double width() {
+ return width;
+ }
+
+ /**
+ * Returns the average to the target and clears the counters.
+ */
+ public void reset() {
+ average.reset();
+ count = 0;
+ stepsSinceAlarm = 0;
+ alarmCount = 0;
+ lastSignal = ShiftSignal.NONE;
+ }
+
+ @Override
+ public String toString() {
+ return "EwmaChangeDetector{target=" + target + ", statistic=" + statistic() + ", limit=" + controlLimit() + ", alarms=" + alarmCount + '}';
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/ExponentialMovingAverage.java b/src/main/java/com/thealgorithms/streaming/ExponentialMovingAverage.java
new file mode 100644
index 000000000000..ce97c6339c8c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/ExponentialMovingAverage.java
@@ -0,0 +1,233 @@
+package com.thealgorithms.streaming;
+
+/**
+ * Exponentially weighted moving average (EWMA) and, alongside it, the exponentially weighted
+ * variance of the same stream.
+ *
+ * A plain moving average has to remember the whole window. An exponentially weighted one does
+ * not: every sample simply decays, so a single number carries the entire history.
+ *
+ * The smoothing factor {@code alpha} in {@code (0, 1]} sets how fast the past is forgotten:
+ * {@code alpha == 1} keeps only the latest sample, while a small {@code alpha} produces a smooth but
+ * sluggish estimate. It is usually easier to specify the responsiveness in terms of a window, which
+ * the factory methods do for you:
+ *
+ * The average is seeded with the first sample, which avoids the warm-up bias that a zero seed
+ * would introduce. Pass an explicit seed to {@link #ofAlpha(double, double)} when the resting level
+ * of the signal is known in advance, as a control chart does.
+ *
+ * Both the update and every query run in O(1) time and O(1) memory. This class is not
+ * thread-safe.
+ *
+ * @see EwmaChangeDetector
+ * @see Exponential moving average
+ */
+public final class ExponentialMovingAverage {
+
+ private final double alpha;
+ private final double seed;
+ private final boolean seeded;
+
+ private double mean;
+ private double variance;
+ private long count;
+ private boolean initialized;
+
+ private ExponentialMovingAverage(double alpha, double seed, boolean seeded) {
+ if (!(alpha > 0.0) || alpha > 1.0) {
+ throw new IllegalArgumentException("The smoothing factor alpha must lie in (0, 1], but was " + alpha);
+ }
+ if (seeded && !Double.isFinite(seed)) {
+ throw new IllegalArgumentException("The seed must be finite, but was " + seed);
+ }
+ this.alpha = alpha;
+ this.seed = seed;
+ this.seeded = seeded;
+ reset();
+ }
+
+ /**
+ * Creates an average that is seeded with its first sample.
+ *
+ * @param alpha smoothing factor in {@code (0, 1]}
+ * @return a new average
+ * @throws IllegalArgumentException if {@code alpha} is outside {@code (0, 1]}
+ */
+ public static ExponentialMovingAverage ofAlpha(double alpha) {
+ return new ExponentialMovingAverage(alpha, 0.0, false);
+ }
+
+ /**
+ * Creates an average that starts from a known level instead of waiting for the first sample.
+ *
+ * @param alpha smoothing factor in {@code (0, 1]}
+ * @param seed the initial value of the average
+ * @return a new average
+ * @throws IllegalArgumentException if {@code alpha} is outside {@code (0, 1]} or {@code seed} is not finite
+ */
+ public static ExponentialMovingAverage ofAlpha(double alpha, double seed) {
+ return new ExponentialMovingAverage(alpha, seed, true);
+ }
+
+ /**
+ * Creates an average whose responsiveness matches a simple moving average of {@code span}
+ * samples, that is {@code alpha = 2 / (span + 1)}.
+ *
+ * @param span the equivalent window length, greater than or equal to one
+ * @return a new average
+ * @throws IllegalArgumentException if {@code span} is smaller than one or not finite
+ */
+ public static ExponentialMovingAverage ofSpan(double span) {
+ if (!(span >= 1.0) || !Double.isFinite(span)) {
+ throw new IllegalArgumentException("The span must be finite and at least 1, but was " + span);
+ }
+ return ofAlpha(2.0 / (span + 1.0));
+ }
+
+ /**
+ * Creates an average in which a sample loses half of its weight after {@code halfLife} updates,
+ * that is {@code alpha = 1 - exp(-ln2 / halfLife)}.
+ *
+ * @param halfLife number of updates after which a weight is halved, strictly positive
+ * @return a new average
+ * @throws IllegalArgumentException if {@code halfLife} is not strictly positive or not finite
+ */
+ public static ExponentialMovingAverage ofHalfLife(double halfLife) {
+ if (!(halfLife > 0.0) || !Double.isFinite(halfLife)) {
+ throw new IllegalArgumentException("The half-life must be finite and positive, but was " + halfLife);
+ }
+ return ofAlpha(-Math.expm1(-Math.log(2.0) / halfLife));
+ }
+
+ /**
+ * Incorporates one sample.
+ *
+ * @param value the sample to add
+ * @return the updated average
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public double add(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ count++;
+ if (!initialized) {
+ initialized = true;
+ mean = value;
+ variance = 0.0;
+ return mean;
+ }
+ double deviation = value - mean;
+ mean += alpha * deviation;
+ variance = (1.0 - alpha) * (variance + alpha * deviation * deviation);
+ return mean;
+ }
+
+ /**
+ * Incorporates every given sample, in order.
+ *
+ * @param values the samples to add
+ * @return the average after the last sample
+ * @throws IllegalArgumentException if any value is NaN or infinite
+ * @throws IllegalStateException if {@code values} is empty and the average is not initialized yet
+ * @throws NullPointerException if {@code values} is {@code null}
+ */
+ public double addAll(double... values) {
+ for (double value : values) {
+ add(value);
+ }
+ return value();
+ }
+
+ /**
+ * Returns the current average.
+ *
+ * @return the exponentially weighted mean
+ * @throws IllegalStateException if the average has neither a seed nor a sample yet
+ */
+ public double value() {
+ if (!initialized) {
+ throw new IllegalStateException("The average has not seen any sample yet");
+ }
+ return mean;
+ }
+
+ /**
+ * Returns the exponentially weighted variance of the stream, the natural companion of
+ * {@link #value()} when the spread matters as much as the level.
+ *
+ * @return the weighted variance, {@code 0} until the second sample arrives
+ * @throws IllegalStateException if the average has neither a seed nor a sample yet
+ */
+ public double variance() {
+ if (!initialized) {
+ throw new IllegalStateException("The average has not seen any sample yet");
+ }
+ return variance;
+ }
+
+ /**
+ * Returns the square root of {@link #variance()}.
+ *
+ * @return the weighted standard deviation
+ * @throws IllegalStateException if the average has neither a seed nor a sample yet
+ */
+ public double standardDeviation() {
+ return Math.sqrt(variance());
+ }
+
+ /**
+ * Returns the smoothing factor in use.
+ *
+ * @return alpha
+ */
+ public double alpha() {
+ return alpha;
+ }
+
+ /**
+ * Returns the number of samples added since the last reset.
+ *
+ * @return the sample count, which excludes the seed
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Tells whether {@link #value()} may be queried.
+ *
+ * @return {@code true} once a seed or at least one sample is available
+ */
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ /**
+ * Restores the state the average had right after construction.
+ */
+ public void reset() {
+ count = 0;
+ variance = 0.0;
+ mean = seeded ? seed : 0.0;
+ initialized = seeded;
+ }
+
+ @Override
+ public String toString() {
+ return "ExponentialMovingAverage{alpha=" + alpha + ", value=" + (initialized ? mean : Double.NaN) + ", count=" + count + '}';
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/ExtendedKalmanFilter.java b/src/main/java/com/thealgorithms/streaming/ExtendedKalmanFilter.java
new file mode 100644
index 000000000000..21ef27212a50
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/ExtendedKalmanFilter.java
@@ -0,0 +1,422 @@
+package com.thealgorithms.streaming;
+
+import com.thealgorithms.matrix.InverseOfMatrix;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * The extended Kalman filter: the Kalman filter for a process that does not move in straight
+ * lines.
+ *
+ * The ordinary Kalman filter is optimal, but only for a linear model with Gaussian noise. Most
+ * things worth tracking are not linear: a bearing is an arctangent of the state, a range is a square
+ * root of it, an orbit is not a straight line. The extended filter keeps the whole machinery and
+ * replaces the one step that needs linearity. The state is still pushed forward through the real,
+ * nonlinear functions, but the covariance is pushed through their Jacobians, evaluated afresh
+ * at the current estimate:
+ *
+ * The covariance update is written in Joseph form rather than as the shorter
+ * {@code (I - K H) P}. The two are equal in exact arithmetic, but the short one subtracts two nearly
+ * equal matrices and, after a few thousand steps of rounding, can leave a covariance that is no
+ * longer symmetric or has a negative variance on its diagonal, at which point the filter quietly
+ * stops being a filter. The Joseph form is a sum of two products of the form {@code A P A'}, so it
+ * stays symmetric by construction and costs one extra multiplication.
+ *
+ * What the linearisation costs is worth stating plainly: the filter is no longer optimal and no
+ * longer guaranteed to converge. A Jacobian is a good description of the model only near the point
+ * it was taken at, so a large error, a sharp nonlinearity or too long a step between updates can all
+ * make the estimate diverge, and the covariance will not warn about it, because the covariance was
+ * computed from the same linearisation. When that happens the answer is smaller steps, a better
+ * initial estimate, or the unscented variant, which samples the nonlinear function instead of
+ * differentiating it.
+ *
+ * For a one dimensional linear process {@link KalmanFilter} is smaller, faster and exactly
+ * equivalent; this class earns its place once the state is a vector or the model bends.
+ *
+ * A step costs O(n^3) in the size of the state, dominated by the matrix products, plus the
+ * inversion of the m by m innovation covariance, which is done by
+ * {@link InverseOfMatrix#invert(double[][])} rather than by another copy of Gaussian elimination.
+ * This class is not thread-safe.
+ *
+ * @see KalmanFilter
+ * @see Extended Kalman filter
+ */
+public final class ExtendedKalmanFilter {
+
+ private final int stateSize;
+
+ private double[] state;
+ private double[][] covariance;
+ private double[] innovation;
+ private double[][] gain;
+ private long predictions;
+ private long corrections;
+
+ /**
+ * Creates a filter.
+ *
+ * @param initialState the first guess at the state, copied
+ * @param initialCovariance how uncertain that guess is, a square matrix of the same size, copied
+ * @throws IllegalArgumentException if the state is empty, if the covariance is not square and of
+ * the same size, or if either holds a value that is not finite
+ * @throws NullPointerException if an argument is {@code null}
+ */
+ public ExtendedKalmanFilter(double[] initialState, double[][] initialCovariance) {
+ if (initialState.length == 0) {
+ throw new IllegalArgumentException("The state must not be empty");
+ }
+ this.stateSize = initialState.length;
+ requireVector(initialState, stateSize, "initial state");
+ requireMatrix(initialCovariance, stateSize, stateSize, "initial covariance");
+ this.state = initialState.clone();
+ this.covariance = copy(initialCovariance);
+ this.innovation = new double[0];
+ this.gain = new double[0][0];
+ }
+
+ /**
+ * Pushes the estimate forward through a nonlinear model.
+ *
+ * @param transition the model itself, which maps a state to the state one step later
+ * @param jacobian the derivative of that model, evaluated by the filter at the current estimate
+ * @param processNoise how much the model is trusted, a square matrix of the size of the state
+ * @return a copy of the predicted state
+ * @throws IllegalArgumentException if the model returns the wrong shape or a value that is not
+ * finite, or if the process noise has the wrong shape
+ * @throws NullPointerException if an argument is {@code null}
+ */
+ public double[] predict(UnaryOperator Grubbs' test asks whether the single most extreme point is an outlier, and it breaks as soon as
+ * there are two of them: each one inflates the standard deviation the other is measured against, so
+ * a pair of outliers can hide one another completely. This is the masking problem, and the
+ * generalized test is the standard answer to it. It removes the most extreme point, recomputes the
+ * statistics on what is left, and repeats {@code r} times:
+ *
+ * The number of outliers is the largest {@code i} whose statistic exceeds its critical
+ * value, not the first one. That is what defeats masking: in a sample with three outliers the first
+ * two tests can easily fall short while the third one succeeds, and the test then reports all three.
+ *
+ * This is a batch test and it needs the whole sample, which is exactly the trade it makes against
+ * the streaming detectors in this package: it has a stated significance level and it decides how many
+ * points are outliers, where {@link HampelFilter} answers one sample at a time against a threshold the
+ * caller has to choose. Rosner's own recommendation is to use it on at least 25 points, and {@code r}
+ * is an upper bound that may be set generously, because overstating it costs accuracy only in extreme
+ * cases.
+ *
+ * The test costs O(r * n) time and allocates O(n). The t quantile behind the critical value is
+ * computed from the regularized incomplete beta function, so no table is needed.
+ *
+ * @see HampelFilter
+ * @see Grubbs's test and its generalization
+ */
+public final class GeneralizedEsd {
+
+ /** Significance level used when none is given. */
+ public static final double DEFAULT_SIGNIFICANCE_LEVEL = 0.05;
+
+ private static final int MINIMUM_SAMPLE_SIZE = 3;
+ private static final double[] LANCZOS = {
+ 0.99999999999980993,
+ 676.5203681218851,
+ -1259.1392167224028,
+ 771.32342877765313,
+ -176.61502916214059,
+ 12.507343278686905,
+ -0.13857109526572012,
+ 9.9843695780195716e-6,
+ 1.5056327351493116e-7,
+ };
+
+ private final double significanceLevel;
+
+ /**
+ * Creates a test at the customary significance level of {@code 0.05}.
+ */
+ public GeneralizedEsd() {
+ this(DEFAULT_SIGNIFICANCE_LEVEL);
+ }
+
+ /**
+ * Creates a test.
+ *
+ * @param significanceLevel the probability of declaring an outlier in clean data, in {@code (0, 1)}
+ * @throws IllegalArgumentException if {@code significanceLevel} is not inside {@code (0, 1)}
+ */
+ public GeneralizedEsd(double significanceLevel) {
+ if (!(significanceLevel > 0.0) || !(significanceLevel < 1.0)) {
+ throw new IllegalArgumentException("The significance level must lie in (0, 1), but was " + significanceLevel);
+ }
+ this.significanceLevel = significanceLevel;
+ }
+
+ /**
+ * Finds the outliers of a sample.
+ *
+ * @param sample the data to inspect, left untouched
+ * @param maxOutliers upper bound on the number of outliers, at least one and at most
+ * {@code sample.length - 2}
+ * @return the indices of the outliers in ascending order, empty if the sample looks clean
+ * @throws IllegalArgumentException if the sample holds fewer than three points or a non-finite
+ * value, or if {@code maxOutliers} is out of range
+ * @throws NullPointerException if {@code sample} is {@code null}
+ */
+ public int[] findOutliers(double[] sample, int maxOutliers) {
+ if (sample.length < MINIMUM_SAMPLE_SIZE) {
+ throw new IllegalArgumentException("The sample must hold at least " + MINIMUM_SAMPLE_SIZE + " points, but held " + sample.length);
+ }
+ if (maxOutliers < 1 || maxOutliers > sample.length - 2) {
+ throw new IllegalArgumentException("The number of outliers to look for must lie in [1, " + (sample.length - 2) + "], but was " + maxOutliers);
+ }
+ for (double value : sample) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ }
+
+ int size = sample.length;
+ double[] values = Arrays.copyOf(sample, size);
+ int[] indices = new int[size];
+ for (int i = 0; i < size; i++) {
+ indices[i] = i;
+ }
+
+ int[] removed = new int[maxOutliers];
+ int outliers = 0;
+ int remaining = size;
+
+ for (int step = 1; step <= maxOutliers; step++) {
+ double mean = mean(values, remaining);
+ double deviation = standardDeviation(values, remaining, mean);
+ if (deviation == 0.0) {
+ break;
+ }
+
+ int worst = indexOfLargestDeviation(values, remaining, mean);
+ double statistic = Math.abs(values[worst] - mean) / deviation;
+ removed[step - 1] = indices[worst];
+ if (statistic > criticalValue(size, step)) {
+ outliers = step;
+ }
+
+ remaining--;
+ values[worst] = values[remaining];
+ indices[worst] = indices[remaining];
+ }
+
+ int[] result = Arrays.copyOf(removed, outliers);
+ Arrays.sort(result);
+ return result;
+ }
+
+ /**
+ * Returns the critical value a statistic has to exceed at one step of the test.
+ *
+ * @param sampleSize the size of the whole sample
+ * @param step which removal this is, counting from one
+ * @return the critical value, that is {@code lambda_i}
+ * @throws IllegalArgumentException if {@code sampleSize} is smaller than three, or if {@code step}
+ * is outside {@code [1, sampleSize - 2]}
+ */
+ public double criticalValue(int sampleSize, int step) {
+ if (sampleSize < MINIMUM_SAMPLE_SIZE) {
+ throw new IllegalArgumentException("The sample size must be at least " + MINIMUM_SAMPLE_SIZE + ", but was " + sampleSize);
+ }
+ if (step < 1 || step > sampleSize - 2) {
+ throw new IllegalArgumentException("The step must lie in [1, " + (sampleSize - 2) + "], but was " + step);
+ }
+ int size = sampleSize - step + 1;
+ int degreesOfFreedom = size - 2;
+ double probability = 1.0 - significanceLevel / (2.0 * size);
+ double t = studentTQuantile(probability, degreesOfFreedom);
+ return (size - 1) * t / Math.sqrt((degreesOfFreedom + t * t) * size);
+ }
+
+ /**
+ * Returns the significance level of the test.
+ *
+ * @return the level given at construction time
+ */
+ public double significanceLevel() {
+ return significanceLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "GeneralizedEsd{significanceLevel=" + significanceLevel + "}";
+ }
+
+ private static double mean(double[] values, int size) {
+ double sum = 0.0;
+ for (int i = 0; i < size; i++) {
+ sum += values[i];
+ }
+ return sum / size;
+ }
+
+ private static double standardDeviation(double[] values, int size, double mean) {
+ double sum = 0.0;
+ for (int i = 0; i < size; i++) {
+ double deviation = values[i] - mean;
+ sum += deviation * deviation;
+ }
+ return Math.sqrt(sum / (size - 1));
+ }
+
+ private static int indexOfLargestDeviation(double[] values, int size, double mean) {
+ int worst = 0;
+ double largest = -1.0;
+ for (int i = 0; i < size; i++) {
+ double deviation = Math.abs(values[i] - mean);
+ if (deviation > largest) {
+ largest = deviation;
+ worst = i;
+ }
+ }
+ return worst;
+ }
+
+ /**
+ * Returns the {@code probability} quantile of a t distribution, found by bisecting its cumulative
+ * distribution function.
+ *
+ * @param probability the probability to invert, in {@code (0, 1)}
+ * @param degreesOfFreedom the degrees of freedom, at least one
+ * @return the quantile
+ */
+ private static double studentTQuantile(double probability, int degreesOfFreedom) {
+ if (probability < 0.5) {
+ return -studentTQuantile(1.0 - probability, degreesOfFreedom);
+ }
+ double low = 0.0;
+ double high = 1.0;
+ while (studentTCumulative(high, degreesOfFreedom) < probability && high < 1e12) {
+ high *= 2.0;
+ }
+ for (int i = 0; i < 200; i++) {
+ double middle = 0.5 * (low + high);
+ if (studentTCumulative(middle, degreesOfFreedom) < probability) {
+ low = middle;
+ } else {
+ high = middle;
+ }
+ }
+ return 0.5 * (low + high);
+ }
+
+ /**
+ * Returns the probability that a t distributed variable stays below {@code t}.
+ *
+ * @param t the point to evaluate at
+ * @param degreesOfFreedom the degrees of freedom, at least one
+ * @return the cumulative probability
+ */
+ private static double studentTCumulative(double t, int degreesOfFreedom) {
+ double x = degreesOfFreedom / (degreesOfFreedom + t * t);
+ double tail = 0.5 * regularizedIncompleteBeta(0.5 * degreesOfFreedom, 0.5, x);
+ return t >= 0.0 ? 1.0 - tail : tail;
+ }
+
+ /**
+ * Returns the regularized incomplete beta function, evaluated with the continued fraction of
+ * Lentz.
+ *
+ * @param a first shape parameter, strictly positive
+ * @param b second shape parameter, strictly positive
+ * @param x the point to evaluate at, in {@code [0, 1]}
+ * @return {@code I_x(a, b)}
+ */
+ private static double regularizedIncompleteBeta(double a, double b, double x) {
+ if (x <= 0.0) {
+ return 0.0;
+ }
+ if (x >= 1.0) {
+ return 1.0;
+ }
+ double logBeta = logGamma(a) + logGamma(b) - logGamma(a + b);
+ if (x < (a + 1.0) / (a + b + 2.0)) {
+ return Math.exp(a * Math.log(x) + b * Math.log1p(-x) - logBeta) * betaContinuedFraction(a, b, x) / a;
+ }
+ return 1.0 - Math.exp(b * Math.log1p(-x) + a * Math.log(x) - logBeta) * betaContinuedFraction(b, a, 1.0 - x) / b;
+ }
+
+ private static double betaContinuedFraction(double a, double b, double x) {
+ double tiny = 1e-300;
+ double c = 1.0;
+ double d = 1.0 - (a + b) * x / (a + 1.0);
+ if (Math.abs(d) < tiny) {
+ d = tiny;
+ }
+ d = 1.0 / d;
+ double fraction = d;
+
+ for (int m = 1; m <= 300; m++) {
+ int even = 2 * m;
+ double numerator = m * (b - m) * x / ((a + even - 1.0) * (a + even));
+ d = 1.0 + numerator * d;
+ if (Math.abs(d) < tiny) {
+ d = tiny;
+ }
+ c = 1.0 + numerator / c;
+ if (Math.abs(c) < tiny) {
+ c = tiny;
+ }
+ d = 1.0 / d;
+ fraction *= d * c;
+
+ numerator = -(a + m) * (a + b + m) * x / ((a + even) * (a + even + 1.0));
+ d = 1.0 + numerator * d;
+ if (Math.abs(d) < tiny) {
+ d = tiny;
+ }
+ c = 1.0 + numerator / c;
+ if (Math.abs(c) < tiny) {
+ c = tiny;
+ }
+ d = 1.0 / d;
+ double step = d * c;
+ fraction *= step;
+
+ if (Math.abs(step - 1.0) < 1e-15) {
+ break;
+ }
+ }
+ return fraction;
+ }
+
+ /**
+ * Returns the logarithm of the gamma function, using the Lanczos approximation.
+ *
+ * @param x the point to evaluate at, strictly positive
+ * @return {@code log(gamma(x))}
+ */
+ private static double logGamma(double x) {
+ double shifted = x - 1.0;
+ double series = LANCZOS[0];
+ for (int i = 1; i < LANCZOS.length; i++) {
+ series += LANCZOS[i] / (shifted + i);
+ }
+ double t = shifted + 7.5;
+ return 0.5 * Math.log(2.0 * Math.PI) + (shifted + 0.5) * Math.log(t) - t + Math.log(series);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/HampelFilter.java b/src/main/java/com/thealgorithms/streaming/HampelFilter.java
new file mode 100644
index 000000000000..f6969d4cbca6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/HampelFilter.java
@@ -0,0 +1,263 @@
+package com.thealgorithms.streaming;
+
+/**
+ * The Hampel filter, also known as the Hampel identifier: a decision rule that replaces the
+ * samples of a signal that look like outliers and leaves every other sample untouched.
+ *
+ * A {@link MedianFilter} rewrites every sample, which throws away detail even where the signal
+ * was perfectly clean. The Hampel filter is the conservative version. For each incoming sample it
+ * looks at the window that ends at that sample and computes two robust statistics:
+ *
+ * The MAD is rescaled by {@code 1.4826}, the factor that makes it match the standard deviation
+ * for normally distributed data, and the sample is declared an outlier when
+ *
+ * Outliers are reported and replaced by the window median; everything else passes through
+ * unchanged. Both statistics are robust, so the spike being tested cannot inflate the very
+ * yardstick it is measured against - which is exactly what happens if one uses a mean and a standard
+ * deviation instead.
+ *
+ * A threshold of 3 is the usual starting point, but how eager the filter turns out to be depends
+ * just as much on the window, because the MAD of a handful of samples is itself a noisy estimate of
+ * the spread. Measured on clean Gaussian noise with a threshold of 3, the filter flags about 7% of
+ * the samples with a window of 5, 3% with a window of 11 and 0.5% with a window of 101, slowly
+ * approaching the 0.3% one would get from a perfect estimate of sigma. Widen the window, or raise
+ * the threshold, when too many good samples are being rewritten.
+ *
+ * The window is causal and includes the sample being judged, so the filter introduces no delay,
+ * and the raw sample - not its replacement - is what enters the window, which keeps the statistics
+ * honest. Before the window has filled up the statistics are computed over the samples seen so far.
+ * If more than half of the window holds one and the same value the MAD is zero, and any sample that
+ * differs from the median at all is then flagged; that is the textbook behaviour of the identifier.
+ *
+ * Each sample costs O(w) time for a window of {@code w} samples and nothing is allocated after
+ * construction. This class is not thread-safe.
+ *
+ * @see MedianFilter
+ * @see Median absolute deviation
+ */
+public final class HampelFilter {
+
+ /** Makes the MAD of normally distributed data an unbiased estimator of its standard deviation. */
+ public static final double GAUSSIAN_MAD_SCALE = 1.4826;
+
+ /** Threshold, in robust standard deviations, used when none is given. */
+ public static final double DEFAULT_THRESHOLD = 3.0;
+
+ private final MedianFilter window;
+ private final double threshold;
+ private final double[] sortedWindow;
+ private final double[] deviations;
+
+ private boolean lastWasOutlier;
+ private long outlierCount;
+
+ /**
+ * Creates a filter with the customary threshold of three robust standard deviations.
+ *
+ * @param windowSize how many recent samples the statistics are computed over; odd sizes are the usual choice
+ * @throws IllegalArgumentException if {@code windowSize} is not positive
+ */
+ public HampelFilter(int windowSize) {
+ this(windowSize, DEFAULT_THRESHOLD);
+ }
+
+ /**
+ * Creates a filter.
+ *
+ * @param windowSize how many recent samples the statistics are computed over
+ * @param threshold how many robust standard deviations a sample may deviate before it counts as an
+ * outlier; must be non-negative
+ * @throws IllegalArgumentException if {@code windowSize} is not positive or {@code threshold} is negative or not finite
+ */
+ public HampelFilter(int windowSize, double threshold) {
+ if (!(threshold >= 0.0) || !Double.isFinite(threshold)) {
+ throw new IllegalArgumentException("The threshold must be finite and non-negative, but was " + threshold);
+ }
+ this.window = new MedianFilter(windowSize);
+ this.threshold = threshold;
+ this.sortedWindow = new double[windowSize];
+ this.deviations = new double[windowSize];
+ }
+
+ /**
+ * Feeds one sample into the filter.
+ *
+ * @param value the incoming sample
+ * @return the sample itself, or the window median if the sample was judged to be an outlier
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public double accept(double value) {
+ double median = window.accept(value);
+ double scaledDeviation = threshold * GAUSSIAN_MAD_SCALE * medianAbsoluteDeviation();
+ lastWasOutlier = Math.abs(value - median) > scaledDeviation;
+ if (lastWasOutlier) {
+ outlierCount++;
+ return median;
+ }
+ return value;
+ }
+
+ /**
+ * Filters a whole signal, one sample at a time, starting from the current state.
+ *
+ * @param signal the samples to filter
+ * @return a new array of the same length in which flagged samples are replaced by the local median
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public double[] filter(double[] signal) {
+ double[] filtered = new double[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ filtered[i] = accept(signal[i]);
+ }
+ return filtered;
+ }
+
+ /**
+ * Runs the filter over a signal and reports which samples were flagged, without altering them.
+ *
+ * @param signal the samples to inspect
+ * @return a new array of the same length, {@code true} where the sample was judged an outlier
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public boolean[] detectOutliers(double[] signal) {
+ boolean[] flags = new boolean[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ accept(signal[i]);
+ flags[i] = lastWasOutlier;
+ }
+ return flags;
+ }
+
+ /**
+ * Returns the median absolute deviation of the current window, the robust counterpart of the
+ * standard deviation.
+ *
+ * @return the MAD, {@code 0} when the window holds a single sample
+ * @throws IllegalStateException if no sample has been accepted yet
+ */
+ public double medianAbsoluteDeviation() {
+ int size = window.copySortedWindow(sortedWindow);
+ if (size == 0) {
+ throw new IllegalStateException("The window is empty");
+ }
+ double median = window.median();
+
+ // The window is sorted, so the absolute deviations form two already sorted runs that meet at
+ // the median: descending to its left, ascending to its right. Merging them is linear.
+ int split = size;
+ for (int i = 0; i < size; i++) {
+ if (sortedWindow[i] > median) {
+ split = i;
+ break;
+ }
+ }
+ int left = split - 1;
+ int right = split;
+ for (int i = 0; i < size; i++) {
+ boolean takeLeft = right >= size || (left >= 0 && median - sortedWindow[left] <= sortedWindow[right] - median);
+ if (takeLeft) {
+ deviations[i] = median - sortedWindow[left];
+ left--;
+ } else {
+ deviations[i] = sortedWindow[right] - median;
+ right++;
+ }
+ }
+
+ int middle = size / 2;
+ return size % 2 != 0 ? deviations[middle] : 0.5 * (deviations[middle - 1] + deviations[middle]);
+ }
+
+ /**
+ * Returns the median of the current window.
+ *
+ * @return the window median
+ * @throws IllegalStateException if no sample has been accepted yet
+ */
+ public double median() {
+ return window.median();
+ }
+
+ /**
+ * Tells whether the most recently accepted sample was flagged.
+ *
+ * @return {@code true} if the last sample was replaced by the median
+ */
+ public boolean lastWasOutlier() {
+ return lastWasOutlier;
+ }
+
+ /**
+ * Returns how many samples have been flagged since the last reset.
+ *
+ * @return the number of detected outliers
+ */
+ public long outlierCount() {
+ return outlierCount;
+ }
+
+ /**
+ * Returns the configured window length.
+ *
+ * @return the window size given at construction time
+ */
+ public int windowSize() {
+ return window.windowSize();
+ }
+
+ /**
+ * Returns the configured threshold.
+ *
+ * @return the threshold in robust standard deviations
+ */
+ public double threshold() {
+ return threshold;
+ }
+
+ /**
+ * Returns how many samples the window currently holds.
+ *
+ * @return the current fill level, at most {@link #windowSize()}
+ */
+ public int size() {
+ return window.size();
+ }
+
+ /**
+ * Empties the window and forgets the outlier count.
+ */
+ public void reset() {
+ window.reset();
+ lastWasOutlier = false;
+ outlierCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ return "HampelFilter{windowSize=" + windowSize() + ", threshold=" + threshold + ", outliers=" + outlierCount + '}';
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/KalmanFilter.java b/src/main/java/com/thealgorithms/streaming/KalmanFilter.java
new file mode 100644
index 000000000000..5e6d5143b12d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/KalmanFilter.java
@@ -0,0 +1,287 @@
+package com.thealgorithms.streaming;
+
+/**
+ * A scalar (one dimensional) Kalman filter: the optimal way to track a quantity that drifts
+ * slowly while every measurement of it is noisy.
+ *
+ * The filter carries two numbers: the current estimate {@code x} and how much it distrusts that
+ * estimate, the error variance {@code p}. Each step has two halves.
+ *
+ * where {@code q} is the process noise (how much the tracked quantity is expected to wander
+ * between two steps), {@code r} the measurement noise, {@code z} the measurement and {@code k} the
+ * Kalman gain. The gain is the whole story: it is the share of the measurement that gets believed.
+ * When the filter is unsure ({@code p} large) or the sensor is good ({@code r} small), {@code k}
+ * approaches 1 and the filter follows the sensor; in the opposite case it clings to its own
+ * prediction. Nothing tunes this by hand, the variances do it.
+ *
+ * Fusing several sensors is not a separate algorithm: it is what happens when the same estimate
+ * is corrected once per sensor, each with its own noise level. A cheap sensor with a large {@code r}
+ * nudges the estimate a little, a precise one pulls it a lot, and the result is exactly the
+ * inverse-variance weighted combination that {@link #fuse(double[], double[])} computes in closed
+ * form:
+ *
+ * Both steps run in O(1) time and memory. This class is not thread-safe.
+ *
+ * @see Kalman filter
+ */
+public final class KalmanFilter {
+
+ private final double processNoise;
+ private final double measurementNoise;
+
+ private double estimate;
+ private double errorCovariance;
+ private double lastGain;
+
+ private final double initialEstimate;
+ private final double initialErrorCovariance;
+
+ /**
+ * Creates a filter.
+ *
+ * @param initialEstimate the starting guess for the tracked quantity
+ * @param initialErrorCovariance how uncertain that guess is; a large value makes the filter trust
+ * the first measurements almost completely
+ * @param processNoise variance added on every {@link #predict()}, i.e. how fast the quantity is
+ * expected to change on its own
+ * @param measurementNoise default variance of a measurement, used by {@link #update(double)}
+ * @throws IllegalArgumentException if any argument is not finite, or if a variance is negative,
+ * or if {@code measurementNoise} is zero
+ */
+ public KalmanFilter(double initialEstimate, double initialErrorCovariance, double processNoise, double measurementNoise) {
+ requireFinite(initialEstimate, "initialEstimate");
+ requireNonNegativeVariance(initialErrorCovariance, "initialErrorCovariance");
+ requireNonNegativeVariance(processNoise, "processNoise");
+ requirePositiveVariance(measurementNoise, "measurementNoise");
+
+ this.initialEstimate = initialEstimate;
+ this.initialErrorCovariance = initialErrorCovariance;
+ this.processNoise = processNoise;
+ this.measurementNoise = measurementNoise;
+ reset();
+ }
+
+ /**
+ * Advances the model by one step without any control input, growing the uncertainty by the
+ * process noise.
+ *
+ * @return the predicted estimate, unchanged in value for this constant model
+ */
+ public double predict() {
+ return predict(0.0);
+ }
+
+ /**
+ * Advances the model by one step, shifting the estimate by a known control input.
+ *
+ * @param controlInput the change the estimate is expected to undergo, e.g. velocity times the
+ * time step when tracking a position
+ * @return the predicted estimate
+ * @throws IllegalArgumentException if {@code controlInput} is not finite
+ */
+ public double predict(double controlInput) {
+ requireFinite(controlInput, "controlInput");
+ estimate += controlInput;
+ errorCovariance += processNoise;
+ return estimate;
+ }
+
+ /**
+ * Corrects the estimate with a measurement taken by the default sensor.
+ *
+ * @param measurement the observed value
+ * @return the corrected estimate
+ * @throws IllegalArgumentException if {@code measurement} is not finite
+ */
+ public double update(double measurement) {
+ return update(measurement, measurementNoise);
+ }
+
+ /**
+ * Corrects the estimate with a measurement whose noise differs from the default one. Calling this
+ * several times per step, once per sensor, is the whole of sensor fusion.
+ *
+ * @param measurement the observed value
+ * @param noise variance of this particular measurement, strictly positive
+ * @return the corrected estimate
+ * @throws IllegalArgumentException if {@code measurement} is not finite or {@code noise} is not strictly positive
+ */
+ public double update(double measurement, double noise) {
+ requireFinite(measurement, "measurement");
+ requirePositiveVariance(noise, "noise");
+
+ double innovationVariance = errorCovariance + noise;
+ lastGain = errorCovariance / innovationVariance;
+ estimate += lastGain * (measurement - estimate);
+ // Algebraically this is (1 - gain) * p, but computing 1 - gain cancels away most of the
+ // significant digits whenever the gain is close to one, as it is on the first measurements.
+ errorCovariance = errorCovariance * noise / innovationVariance;
+ return estimate;
+ }
+
+ /**
+ * Runs one full cycle: predict, then correct with the given measurement.
+ *
+ * @param measurement the observed value
+ * @return the filtered estimate
+ * @throws IllegalArgumentException if {@code measurement} is not finite
+ */
+ public double filter(double measurement) {
+ predict();
+ return update(measurement);
+ }
+
+ /**
+ * Filters a whole signal offline, one cycle per sample.
+ *
+ * @param measurements the noisy signal
+ * @return a new array holding the filtered signal, of the same length
+ * @throws IllegalArgumentException if any measurement is not finite
+ * @throws NullPointerException if {@code measurements} is {@code null}
+ */
+ public double[] filter(double[] measurements) {
+ double[] filtered = new double[measurements.length];
+ for (int i = 0; i < measurements.length; i++) {
+ filtered[i] = filter(measurements[i]);
+ }
+ return filtered;
+ }
+
+ /**
+ * Combines simultaneous readings of the same quantity taken by independent sensors, weighting
+ * each by the inverse of its variance. This is the closed form of what repeated
+ * {@link #update(double, double)} calls achieve within one step.
+ *
+ * @param measurements one reading per sensor
+ * @param variances the noise variance of each sensor, strictly positive, same length as {@code measurements}
+ * @return the fused reading together with its variance, which is never larger than the variance of
+ * the best single sensor
+ * @throws IllegalArgumentException if the arrays are empty, differ in length, hold a non-finite
+ * measurement or a non-positive variance
+ * @throws NullPointerException if either array is {@code null}
+ */
+ public static Estimate fuse(double[] measurements, double[] variances) {
+ if (measurements.length != variances.length) {
+ throw new IllegalArgumentException("There must be exactly one variance per measurement, but got " + measurements.length + " and " + variances.length);
+ }
+ if (measurements.length == 0) {
+ throw new IllegalArgumentException("At least one measurement is required");
+ }
+
+ double weightSum = 0.0;
+ double weightedSum = 0.0;
+ for (int i = 0; i < measurements.length; i++) {
+ requireFinite(measurements[i], "measurement");
+ requirePositiveVariance(variances[i], "variance");
+ double weight = 1.0 / variances[i];
+ weightSum += weight;
+ weightedSum += weight * measurements[i];
+ }
+ return new Estimate(weightedSum / weightSum, 1.0 / weightSum);
+ }
+
+ /**
+ * Returns the current estimate of the tracked quantity.
+ *
+ * @return the state estimate
+ */
+ public double estimate() {
+ return estimate;
+ }
+
+ /**
+ * Returns the variance of the current estimate; it shrinks with every update and grows with every
+ * prediction.
+ *
+ * @return the error covariance
+ */
+ public double errorCovariance() {
+ return errorCovariance;
+ }
+
+ /**
+ * Returns the Kalman gain used by the most recent update, a number in {@code [0, 1)} telling how
+ * much of that measurement was believed.
+ *
+ * @return the last gain, {@code 0} if no update has happened yet
+ */
+ public double lastGain() {
+ return lastGain;
+ }
+
+ /**
+ * Returns the process noise variance.
+ *
+ * @return the value given at construction time
+ */
+ public double processNoise() {
+ return processNoise;
+ }
+
+ /**
+ * Returns the default measurement noise variance.
+ *
+ * @return the value given at construction time
+ */
+ public double measurementNoise() {
+ return measurementNoise;
+ }
+
+ /**
+ * Restores the state the filter had right after construction.
+ */
+ public void reset() {
+ estimate = initialEstimate;
+ errorCovariance = initialErrorCovariance;
+ lastGain = 0.0;
+ }
+
+ @Override
+ public String toString() {
+ return "KalmanFilter{estimate=" + estimate + ", errorCovariance=" + errorCovariance + ", lastGain=" + lastGain + '}';
+ }
+
+ private static void requireFinite(double value, String name) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite, but was " + value);
+ }
+ }
+
+ private static void requireNonNegativeVariance(double value, String name) {
+ if (!(value >= 0.0) || !Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite and non-negative, but was " + value);
+ }
+ }
+
+ private static void requirePositiveVariance(double value, String name) {
+ if (!(value > 0.0) || !Double.isFinite(value)) {
+ throw new IllegalArgumentException("The " + name + " must be finite and strictly positive, but was " + value);
+ }
+ }
+
+ /**
+ * A value paired with the variance that describes how much it can be trusted.
+ *
+ * @param value the estimated quantity
+ * @param variance the variance of that estimate
+ */
+ public record Estimate(double value, double variance) {
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/MedianFilter.java b/src/main/java/com/thealgorithms/streaming/MedianFilter.java
new file mode 100644
index 000000000000..fa876a4feece
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/MedianFilter.java
@@ -0,0 +1,229 @@
+package com.thealgorithms.streaming;
+
+import java.util.Arrays;
+
+/**
+ * A streaming median filter: it reports the median of the last {@code windowSize} samples of
+ * a signal.
+ *
+ * The median filter is the standard first line of defence against impulsive noise. A single
+ * corrupted sample - a dropped bit, a spike on an analogue line, a sensor glitch - drags a moving
+ * average with it, but it cannot drag a median: as long as fewer than half of the samples in the
+ * window are corrupted, the output stays on the true signal. Unlike a linear low-pass filter, a
+ * median filter also preserves sharp edges instead of smearing them.
+ *
+ * Two arrays of {@code windowSize} doubles are kept: the samples in arrival order, so the oldest
+ * one can be evicted, and the same samples in ascending order, so the median is the middle element.
+ * Insertion and removal in the sorted copy are binary search plus a memory move, which makes an
+ * update O(w) in the worst case but with a very small constant, and nothing is allocated after
+ * construction.
+ *
+ * The filter is causal: the value it reports for a sample is the median of that sample and the
+ * {@code windowSize - 1} preceding ones, so the output lags the input by roughly half a window.
+ * Before the window is full the median is taken over however many samples have arrived. With an even
+ * window size the median is the average of the two middle samples.
+ *
+ * This class is not thread-safe.
+ *
+ * @see HampelFilter for a variant that only replaces samples identified as outliers
+ * @see Median filter
+ */
+public final class MedianFilter {
+
+ private final double[] arrivalOrder;
+ private final double[] ascending;
+ private final int windowSize;
+
+ private int size;
+ private int oldest;
+
+ /**
+ * Creates an empty filter.
+ *
+ * @param windowSize how many recent samples the median is taken over; odd sizes are the usual choice
+ * @throws IllegalArgumentException if {@code windowSize} is not positive
+ */
+ public MedianFilter(int windowSize) {
+ if (windowSize <= 0) {
+ throw new IllegalArgumentException("The window size must be positive, but was " + windowSize);
+ }
+ this.windowSize = windowSize;
+ this.arrivalOrder = new double[windowSize];
+ this.ascending = new double[windowSize];
+ }
+
+ /**
+ * Feeds one sample into the filter.
+ *
+ * @param value the incoming sample
+ * @return the median of the window that now ends at this sample
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public double accept(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ if (size == windowSize) {
+ removeFromSorted(arrivalOrder[oldest]);
+ size--;
+ }
+ insertIntoSorted(value);
+ arrivalOrder[oldest] = value;
+ oldest = (oldest + 1) % windowSize;
+ size++;
+ return median();
+ }
+
+ /**
+ * Filters a whole signal, one sample at a time, starting from the current state.
+ *
+ * @param signal the samples to filter
+ * @return a new array of the same length holding the running medians
+ * @throws IllegalArgumentException if any sample is NaN or infinite
+ * @throws NullPointerException if {@code signal} is {@code null}
+ */
+ public double[] filter(double[] signal) {
+ double[] filtered = new double[signal.length];
+ for (int i = 0; i < signal.length; i++) {
+ filtered[i] = accept(signal[i]);
+ }
+ return filtered;
+ }
+
+ /**
+ * Returns the median of the samples currently in the window.
+ *
+ * @return the median, averaging the two middle samples when the window holds an even number of them
+ * @throws IllegalStateException if no sample has been accepted yet
+ */
+ public double median() {
+ if (size == 0) {
+ throw new IllegalStateException("The window is empty");
+ }
+ int middle = size / 2;
+ if (size % 2 != 0) {
+ return ascending[middle];
+ }
+ return 0.5 * (ascending[middle - 1] + ascending[middle]);
+ }
+
+ /**
+ * Returns the smallest sample currently in the window.
+ *
+ * @return the minimum of the window
+ * @throws IllegalStateException if no sample has been accepted yet
+ */
+ public double min() {
+ requireNonEmpty();
+ return ascending[0];
+ }
+
+ /**
+ * Returns the largest sample currently in the window.
+ *
+ * @return the maximum of the window
+ * @throws IllegalStateException if no sample has been accepted yet
+ */
+ public double max() {
+ requireNonEmpty();
+ return ascending[size - 1];
+ }
+
+ /**
+ * Returns the configured window length.
+ *
+ * @return the window size given at construction time
+ */
+ public int windowSize() {
+ return windowSize;
+ }
+
+ /**
+ * Returns how many samples the window currently holds.
+ *
+ * @return the current fill level, at most {@link #windowSize()}
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Tells whether the window has warmed up.
+ *
+ * @return {@code true} once the window holds {@link #windowSize()} samples
+ */
+ public boolean isFull() {
+ return size == windowSize;
+ }
+
+ /**
+ * Tells whether the window holds no samples.
+ *
+ * @return {@code true} if nothing has been accepted since the last reset
+ */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * Returns the window contents in ascending order.
+ *
+ * @return a new array holding the sorted window
+ */
+ public double[] sortedWindow() {
+ return Arrays.copyOf(ascending, size);
+ }
+
+ /**
+ * Empties the window.
+ */
+ public void reset() {
+ size = 0;
+ oldest = 0;
+ }
+
+ @Override
+ public String toString() {
+ return "MedianFilter{windowSize=" + windowSize + ", size=" + size + ", median=" + (size == 0 ? Double.NaN : median()) + '}';
+ }
+
+ /**
+ * Copies the sorted window into a caller supplied buffer, which lets tight loops avoid allocating.
+ *
+ * @param destination buffer of at least {@link #windowSize()} elements
+ * @return the number of values written
+ */
+ int copySortedWindow(double[] destination) {
+ System.arraycopy(ascending, 0, destination, 0, size);
+ return size;
+ }
+
+ private void insertIntoSorted(double value) {
+ int position = Arrays.binarySearch(ascending, 0, size, value);
+ if (position < 0) {
+ position = -(position + 1);
+ }
+ System.arraycopy(ascending, position, ascending, position + 1, size - position);
+ ascending[position] = value;
+ }
+
+ private void removeFromSorted(double value) {
+ int position = Arrays.binarySearch(ascending, 0, size, value);
+ System.arraycopy(ascending, position + 1, ascending, position, size - position - 1);
+ }
+
+ private void requireNonEmpty() {
+ if (size == 0) {
+ throw new IllegalStateException("The window is empty");
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/P2QuantileEstimator.java b/src/main/java/com/thealgorithms/streaming/P2QuantileEstimator.java
new file mode 100644
index 000000000000..9fa4bcf701b9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/P2QuantileEstimator.java
@@ -0,0 +1,310 @@
+package com.thealgorithms.streaming;
+
+import java.util.Arrays;
+
+/**
+ * The P-square (PΒ²) algorithm of Jain and Chlamtac: it estimates one quantile of a stream
+ * using five numbers and no memory of the samples themselves.
+ *
+ * Computing an exact quantile requires storing everything seen so far, which is impossible for an
+ * unbounded stream. PΒ² instead keeps five markers that track the minimum, the quantile
+ * {@code p/2}, the quantile {@code p} itself, the quantile {@code (1 + p)/2} and the maximum. Each
+ * marker has a height (its current value) and a position (how many samples are known to be below
+ * it). Every new sample shifts the positions of the markers it falls below, and each marker is then
+ * nudged back towards the position it is supposed to occupy. The nudge follows a piecewise
+ * parabolic prediction fitted through the three neighbouring markers, falling back to a linear one
+ * whenever the parabola would break the ordering of the heights.
+ *
+ * Accuracy is typically a fraction of a percent in rank for stationary streams and improves as
+ * more samples arrive. The markers adapt continuously, so a drifting distribution is tracked rather
+ * than averaged away, but no accuracy bound is guaranteed for adversarial inputs.
+ *
+ * The first five samples are stored verbatim, so the estimate is exact until the sixth one
+ * arrives. This class is not thread-safe.
+ *
+ * @see R. Jain, I. Chlamtac, The P-square algorithm (1985)
+ */
+public final class P2QuantileEstimator {
+
+ private static final int MARKERS = 5;
+
+ private final double probability;
+
+ /** Marker heights, kept in non-decreasing order; also the raw buffer for the first five samples. */
+ private final double[] heights = new double[MARKERS];
+
+ /** One-based rank of each marker, i.e. how many samples are known to sit at or below it. */
+ private final int[] positions = new int[MARKERS];
+
+ /** Position each marker should ideally occupy. */
+ private final double[] desiredPositions = new double[MARKERS];
+
+ /** How much {@link #desiredPositions} grows per sample. */
+ private final double[] positionIncrements = new double[MARKERS];
+
+ private long count;
+
+ /**
+ * Creates an estimator for a single quantile.
+ *
+ * @param probability the quantile to track, strictly between 0 and 1, e.g. {@code 0.5} for the median
+ * @throws IllegalArgumentException if {@code probability} is outside {@code (0, 1)}
+ */
+ public P2QuantileEstimator(double probability) {
+ if (!(probability > 0.0) || !(probability < 1.0)) {
+ throw new IllegalArgumentException("The quantile probability must lie strictly between 0 and 1, but was " + probability);
+ }
+ this.probability = probability;
+ }
+
+ /**
+ * Incorporates one sample.
+ *
+ * @param value the sample to add
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public void add(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ if (count < MARKERS) {
+ collectInitialSample(value);
+ return;
+ }
+
+ int cell = locate(value);
+ for (int i = cell + 1; i < MARKERS; i++) {
+ positions[i]++;
+ }
+ for (int i = 0; i < MARKERS; i++) {
+ desiredPositions[i] += positionIncrements[i];
+ }
+ for (int i = 1; i < MARKERS - 1; i++) {
+ adjustMarker(i);
+ }
+ count++;
+ }
+
+ /**
+ * Incorporates every given sample, in order.
+ *
+ * @param values the samples to add
+ * @throws IllegalArgumentException if any value is NaN or infinite
+ * @throws NullPointerException if {@code values} is {@code null}
+ */
+ public void addAll(double... values) {
+ for (double value : values) {
+ add(value);
+ }
+ }
+
+ /**
+ * Returns the current estimate of the tracked quantile.
+ *
+ * @return the estimated quantile, exact while fewer than five samples have been seen
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double quantile() {
+ requireNonEmpty();
+ if (count < MARKERS) {
+ return exactQuantileOfInitialSamples();
+ }
+ return heights[2];
+ }
+
+ /**
+ * Returns the smallest sample seen so far, which PΒ² tracks exactly.
+ *
+ * @return the running minimum
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double min() {
+ requireNonEmpty();
+ return count < MARKERS ? Arrays.stream(heights, 0, (int) count).min().orElse(Double.NaN) : heights[0];
+ }
+
+ /**
+ * Returns the largest sample seen so far, which PΒ² tracks exactly.
+ *
+ * @return the running maximum
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double max() {
+ requireNonEmpty();
+ return count < MARKERS ? Arrays.stream(heights, 0, (int) count).max().orElse(Double.NaN) : heights[MARKERS - 1];
+ }
+
+ /**
+ * Returns the quantile this estimator was configured for.
+ *
+ * @return the probability given at construction time
+ */
+ public double probability() {
+ return probability;
+ }
+
+ /**
+ * Returns the number of samples seen so far.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Tells whether any sample has been added.
+ *
+ * @return {@code true} if the estimator holds no samples
+ */
+ public boolean isEmpty() {
+ return count == 0;
+ }
+
+ /**
+ * Forgets every sample.
+ */
+ public void reset() {
+ count = 0;
+ Arrays.fill(heights, 0.0);
+ Arrays.fill(positions, 0);
+ Arrays.fill(desiredPositions, 0.0);
+ Arrays.fill(positionIncrements, 0.0);
+ }
+
+ @Override
+ public String toString() {
+ return "P2QuantileEstimator{p=" + probability + ", count=" + count + ", quantile=" + (count == 0 ? Double.NaN : quantile()) + '}';
+ }
+
+ /**
+ * Stores one of the first five samples and, on the fifth, lays out the markers.
+ */
+ private void collectInitialSample(double value) {
+ heights[(int) count] = value;
+ count++;
+ if (count < MARKERS) {
+ return;
+ }
+ Arrays.sort(heights);
+ // Marker positions are one-based, exactly as in the paper. Only differences of positions are
+ // ever used, so the origin is arithmetically irrelevant - but the desired positions are grown
+ // by repeated addition, and starting them one lower would round differently and flip the
+ // strict comparisons below on the samples where a desired position lands on a whole number.
+ for (int i = 0; i < MARKERS; i++) {
+ positions[i] = i + 1;
+ }
+ desiredPositions[0] = 1.0;
+ desiredPositions[1] = 1.0 + 2.0 * probability;
+ desiredPositions[2] = 1.0 + 4.0 * probability;
+ desiredPositions[3] = 3.0 + 2.0 * probability;
+ desiredPositions[4] = 5.0;
+ positionIncrements[0] = 0.0;
+ positionIncrements[1] = probability / 2.0;
+ positionIncrements[2] = probability;
+ positionIncrements[3] = (1.0 + probability) / 2.0;
+ positionIncrements[4] = 1.0;
+ }
+
+ /**
+ * Finds the cell the sample falls into, stretching the outer markers when it falls outside the
+ * range seen so far.
+ *
+ * @param value the incoming sample
+ * @return the index of the marker immediately below the sample
+ */
+ private int locate(double value) {
+ if (value < heights[0]) {
+ heights[0] = value;
+ return 0;
+ }
+ for (int i = 1; i < MARKERS - 1; i++) {
+ if (value < heights[i]) {
+ return i - 1;
+ }
+ }
+ if (value > heights[MARKERS - 1]) {
+ heights[MARKERS - 1] = value;
+ }
+ return MARKERS - 2;
+ }
+
+ /**
+ * Moves one inner marker by a single position if it has drifted too far from where it should be.
+ */
+ private void adjustMarker(int i) {
+ double drift = desiredPositions[i] - positions[i];
+ boolean shiftRight = drift >= 1.0 && positions[i + 1] - positions[i] > 1;
+ boolean shiftLeft = drift <= -1.0 && positions[i - 1] - positions[i] < -1;
+ if (!shiftRight && !shiftLeft) {
+ return;
+ }
+
+ int direction = shiftRight ? 1 : -1;
+ double candidate = parabolicPrediction(i, direction);
+ heights[i] = heights[i - 1] < candidate && candidate < heights[i + 1] ? candidate : linearPrediction(i, direction);
+ positions[i] += direction;
+ }
+
+ /**
+ * Piecewise parabolic prediction: fits a parabola through markers {@code i - 1}, {@code i} and
+ * {@code i + 1} and evaluates it one position away from the current one.
+ */
+ private double parabolicPrediction(int i, int direction) {
+ double left = positions[i] - positions[i - 1];
+ double right = positions[i + 1] - positions[i];
+ // Grouped exactly as in the paper. The result then decides, by a strict comparison against the
+ // neighbouring heights, whether the parabola is used at all, so regrouping the arithmetic would
+ // flip that decision on knife-edge inputs and make this estimator drift away from the reference.
+ return heights[i] + direction / (left + right) * ((left + direction) * (heights[i + 1] - heights[i]) / right + (right - direction) * (heights[i] - heights[i - 1]) / left);
+ }
+
+ /**
+ * Linear fallback used whenever the parabolic prediction would violate the ordering of the
+ * marker heights.
+ */
+ private double linearPrediction(int i, int direction) {
+ int neighbour = i + direction;
+ return heights[i] + direction * (heights[neighbour] - heights[i]) / (positions[neighbour] - positions[i]);
+ }
+
+ /**
+ * Exact quantile of the fewer than five samples buffered so far, by linear interpolation between
+ * the two order statistics surrounding the requested rank.
+ */
+ private double exactQuantileOfInitialSamples() {
+ double[] sorted = Arrays.copyOf(heights, (int) count);
+ Arrays.sort(sorted);
+ if (sorted.length == 1) {
+ return sorted[0];
+ }
+ double rank = probability * (sorted.length - 1);
+ int lower = (int) Math.floor(rank);
+ int upper = Math.min(lower + 1, sorted.length - 1);
+ return sorted[lower] + (rank - lower) * (sorted[upper] - sorted[lower]);
+ }
+
+ private void requireNonEmpty() {
+ if (count == 0) {
+ throw new IllegalStateException("The estimator has not seen any sample yet");
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/ShiftSignal.java b/src/main/java/com/thealgorithms/streaming/ShiftSignal.java
new file mode 100644
index 000000000000..467dcf060e0e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/ShiftSignal.java
@@ -0,0 +1,29 @@
+package com.thealgorithms.streaming;
+
+/**
+ * What a change detector reports after looking at one sample: either the stream still behaves as
+ * expected, or its level has shifted, in one direction or the other.
+ *
+ * @see CusumDetector
+ * @see EwmaChangeDetector
+ */
+public enum ShiftSignal {
+
+ /** No evidence of a change; the stream is in control. */
+ NONE,
+
+ /** The level of the stream has moved above the target. */
+ UPWARD,
+
+ /** The level of the stream has moved below the target. */
+ DOWNWARD;
+
+ /**
+ * Tells whether this signal reports a change.
+ *
+ * @return {@code true} for {@link #UPWARD} and {@link #DOWNWARD}
+ */
+ public boolean isAlarm() {
+ return this != NONE;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/TDigest.java b/src/main/java/com/thealgorithms/streaming/TDigest.java
new file mode 100644
index 000000000000..b1c826b1857c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/TDigest.java
@@ -0,0 +1,540 @@
+package com.thealgorithms.streaming;
+
+import java.util.Arrays;
+
+/**
+ * A t-digest: a small, mergeable sketch that answers any quantile of a stream, with
+ * much better accuracy at the tails than in the middle.
+ *
+ * The sketch is a list of centroids, each holding a mean and a weight, kept sorted by mean.
+ * Together they approximate the distribution of everything ever added. What makes a t-digest more
+ * than a histogram is the rule that decides how big a centroid is allowed to be: instead of a fixed
+ * bucket width, the size limit is expressed through a scale function
+ *
+ * and a centroid may absorb points only while it spans at most one unit of {@code k}. Because
+ * {@code asin} is steep near {@code q = 0} and {@code q = 1}, centroids near the tails are forced to
+ * stay tiny - often a single point - while centroids near the median are allowed to grow large.
+ * That is exactly the trade one wants in practice: the rank error is of the order of
+ * {@code 1 / compression} around the median and shrinks as {@code q} approaches 0 or 1, so
+ * {@code p99.9} comes out nearly exact. On smooth data the interpolation between centroids brings
+ * the error down by another order of magnitude, and the whole sketch still fits in a few kilobytes.
+ *
+ * Insertions are buffered and folded into the centroid list in batches, which keeps the amortised
+ * cost per sample low. The minimum and the maximum are tracked separately and reported exactly.
+ *
+ * Query methods fold the pending insertion buffer in before answering, so they mutate internal
+ * state; they are not safe to call concurrently. This class is not thread-safe.
+ *
+ * @see P2QuantileEstimator for a constant-memory estimator of one fixed quantile
+ * @see T. Dunning, O. Ertl, Computing extremely accurate quantiles using t-digests
+ */
+public final class TDigest {
+
+ /** Below roughly this value the scale function stops leaving room for a useful number of centroids. */
+ private static final double MIN_COMPRESSION = 10.0;
+
+ private static final double TWO_PI = 2.0 * Math.PI;
+
+ /** Buffered points per centroid slot; larger buffers mean fewer, bigger merge passes. */
+ private static final int BUFFER_FACTOR = 5;
+
+ /**
+ * How many centroid slots to reserve per unit of compression. Repeated merges make centroids
+ * atomic, so they pack less tightly than the scale function alone would suggest: in practice the
+ * sketch settles around {@code 0.6 * compression} centroids rather than the ideal
+ * {@code compression / 2}. Twice the compression leaves ample headroom.
+ */
+ private static final int CENTROID_SLOTS_PER_UNIT = 2;
+
+ /** Below this length the parallel sort switches to insertion sort. */
+ private static final int INSERTION_SORT_THRESHOLD = 16;
+
+ private final double compression;
+
+ private final double[] centroidMeans;
+ private final double[] centroidWeights;
+ private int centroidCount;
+
+ private final double[] bufferMeans;
+ private final double[] bufferWeights;
+ private int bufferCount;
+
+ private final double[] scratchMeans;
+ private final double[] scratchWeights;
+
+ private double totalWeight;
+ private double min = Double.POSITIVE_INFINITY;
+ private double max = Double.NEGATIVE_INFINITY;
+
+ /**
+ * Creates an empty digest with a compression of 100, a good default for most streams.
+ */
+ public TDigest() {
+ this(100.0);
+ }
+
+ /**
+ * Creates an empty digest.
+ *
+ * @param compression the size/accuracy trade-off; around {@code 0.6 * compression} centroids end
+ * up being kept, so larger values cost more memory and give smaller quantile errors
+ * @throws IllegalArgumentException if {@code compression} is smaller than 10 or not finite
+ */
+ public TDigest(double compression) {
+ if (!(compression >= MIN_COMPRESSION) || !Double.isFinite(compression)) {
+ throw new IllegalArgumentException("The compression must be finite and at least " + MIN_COMPRESSION + ", but was " + compression);
+ }
+ this.compression = compression;
+
+ int scale = (int) Math.ceil(compression) + 2;
+ int centroidCapacity = CENTROID_SLOTS_PER_UNIT * scale;
+ this.centroidMeans = new double[centroidCapacity];
+ this.centroidWeights = new double[centroidCapacity];
+
+ int bufferCapacity = BUFFER_FACTOR * scale;
+ this.bufferMeans = new double[bufferCapacity];
+ this.bufferWeights = new double[bufferCapacity];
+
+ this.scratchMeans = new double[centroidCapacity + bufferCapacity];
+ this.scratchWeights = new double[centroidCapacity + bufferCapacity];
+ }
+
+ /**
+ * Adds one sample of unit weight.
+ *
+ * @param value the sample to add
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public void add(double value) {
+ add(value, 1.0);
+ }
+
+ /**
+ * Adds a sample that stands for several observations of the same value.
+ *
+ * @param value the sample to add
+ * @param weight how many observations the sample represents, strictly positive
+ * @throws IllegalArgumentException if {@code value} is not finite or {@code weight} is not strictly positive
+ */
+ public void add(double value, double weight) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ if (!(weight > 0.0) || !Double.isFinite(weight)) {
+ throw new IllegalArgumentException("The weight must be finite and positive, but was " + weight);
+ }
+ if (bufferCount == bufferMeans.length) {
+ flushBuffer();
+ }
+ bufferMeans[bufferCount] = value;
+ bufferWeights[bufferCount] = weight;
+ bufferCount++;
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ }
+
+ /**
+ * Adds every given sample with unit weight.
+ *
+ * @param values the samples to add
+ * @throws IllegalArgumentException if any value is NaN or infinite
+ * @throws NullPointerException if {@code values} is {@code null}
+ */
+ public void addAll(double... values) {
+ for (double value : values) {
+ add(value);
+ }
+ }
+
+ /**
+ * Folds another digest into this one. The result approximates the union of the two streams, which
+ * is what makes t-digests usable as a map-reduce style aggregate.
+ *
+ * @param other the digest to absorb; it is left unchanged apart from its pending buffer being folded in
+ * @throws NullPointerException if {@code other} is {@code null}
+ */
+ public void merge(TDigest other) {
+ other.flushBuffer();
+ for (int i = 0; i < other.centroidCount; i++) {
+ add(other.centroidMeans[i], other.centroidWeights[i]);
+ }
+ if (!other.isEmpty()) {
+ min = Math.min(min, other.min);
+ max = Math.max(max, other.max);
+ }
+ }
+
+ /**
+ * Estimates the value below which the given fraction of the stream lies.
+ *
+ * @param q the requested quantile, between 0 and 1 inclusive
+ * @return the estimated quantile; exactly {@link #min()} for {@code q == 0} and {@link #max()} for {@code q == 1}
+ * @throws IllegalArgumentException if {@code q} is outside {@code [0, 1]} or is NaN
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double quantile(double q) {
+ if (!(q >= 0.0) || !(q <= 1.0)) {
+ throw new IllegalArgumentException("The quantile probability must lie in [0, 1], but was " + q);
+ }
+ flushBuffer();
+ requireNonEmpty();
+ if (centroidCount == 1) {
+ return centroidMeans[0];
+ }
+
+ double index = q * totalWeight;
+ if (index <= 0.0) {
+ return min;
+ }
+ if (index >= totalWeight) {
+ return max;
+ }
+
+ // The centroids are treated as knots of a piecewise linear quantile function, each sitting at
+ // the centre of the weight it carries, with min and max closing the two ends.
+ double previousValue = min;
+ double previousIndex = 0.0;
+ double currentIndex = 0.0;
+ for (int i = 0; i < centroidCount; i++) {
+ currentIndex += i == 0 ? centroidWeights[0] / 2.0 : (centroidWeights[i - 1] + centroidWeights[i]) / 2.0;
+ if (index < currentIndex) {
+ return interpolate(previousIndex, previousValue, currentIndex, centroidMeans[i], index);
+ }
+ previousValue = centroidMeans[i];
+ previousIndex = currentIndex;
+ }
+ return interpolate(previousIndex, previousValue, totalWeight, max, index);
+ }
+
+ /**
+ * Estimates the fraction of the stream that is smaller than the given value, i.e. the empirical
+ * cumulative distribution function.
+ *
+ * @param value the value to look up
+ * @return a number in {@code [0, 1]}
+ * @throws IllegalArgumentException if {@code value} is NaN
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double cdf(double value) {
+ if (Double.isNaN(value)) {
+ throw new IllegalArgumentException("The value must not be NaN");
+ }
+ flushBuffer();
+ requireNonEmpty();
+ if (value < min) {
+ return 0.0;
+ }
+ if (value > max) {
+ return 1.0;
+ }
+ if (max <= min) {
+ return 0.5;
+ }
+
+ double previousValue = min;
+ double previousIndex = 0.0;
+ double currentIndex = 0.0;
+ for (int i = 0; i < centroidCount; i++) {
+ currentIndex += i == 0 ? centroidWeights[0] / 2.0 : (centroidWeights[i - 1] + centroidWeights[i]) / 2.0;
+ if (value < centroidMeans[i]) {
+ return interpolate(previousValue, previousIndex, centroidMeans[i], currentIndex, value) / totalWeight;
+ }
+ previousValue = centroidMeans[i];
+ previousIndex = currentIndex;
+ }
+ return interpolate(previousValue, previousIndex, max, totalWeight, value) / totalWeight;
+ }
+
+ /**
+ * Returns the smallest sample ever added, tracked exactly.
+ *
+ * @return the minimum
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double min() {
+ requireAnySample();
+ return min;
+ }
+
+ /**
+ * Returns the largest sample ever added, tracked exactly.
+ *
+ * @return the maximum
+ * @throws IllegalStateException if no sample has been added yet
+ */
+ public double max() {
+ requireAnySample();
+ return max;
+ }
+
+ /**
+ * Returns the total weight of everything added, which for unweighted input is the number of
+ * samples.
+ *
+ * @return the accumulated weight
+ */
+ public double totalWeight() {
+ double pending = 0.0;
+ for (int i = 0; i < bufferCount; i++) {
+ pending += bufferWeights[i];
+ }
+ return totalWeight + pending;
+ }
+
+ /**
+ * Returns how many centroids the sketch currently holds, after folding in pending insertions.
+ *
+ * @return the number of centroids, bounded by roughly {@code compression / 2}
+ */
+ public int centroidCount() {
+ flushBuffer();
+ return centroidCount;
+ }
+
+ /**
+ * Returns the compression parameter.
+ *
+ * @return the value given at construction time
+ */
+ public double compression() {
+ return compression;
+ }
+
+ /**
+ * Tells whether the digest holds no samples.
+ *
+ * @return {@code true} if nothing has been added
+ */
+ public boolean isEmpty() {
+ return centroidCount == 0 && bufferCount == 0;
+ }
+
+ /**
+ * Forgets every sample.
+ */
+ public void reset() {
+ centroidCount = 0;
+ bufferCount = 0;
+ totalWeight = 0.0;
+ min = Double.POSITIVE_INFINITY;
+ max = Double.NEGATIVE_INFINITY;
+ }
+
+ @Override
+ public String toString() {
+ return "TDigest{compression=" + compression + ", weight=" + totalWeight() + ", centroids=" + (isEmpty() ? 0 : centroidCount()) + '}';
+ }
+
+ /**
+ * Folds every buffered point into the centroid list.
+ *
+ * Buffer and centroids are concatenated, sorted by mean and then swept once from the smallest
+ * mean upwards. The sweep keeps filling the current output centroid while doing so would not push
+ * it past the weight limit given by the scale function at the quantile the centroid starts at;
+ * otherwise the centroid is closed and a new one is opened.
+ */
+ private void flushBuffer() {
+ if (bufferCount == 0) {
+ return;
+ }
+
+ int size = centroidCount + bufferCount;
+ System.arraycopy(centroidMeans, 0, scratchMeans, 0, centroidCount);
+ System.arraycopy(centroidWeights, 0, scratchWeights, 0, centroidCount);
+ System.arraycopy(bufferMeans, 0, scratchMeans, centroidCount, bufferCount);
+ System.arraycopy(bufferWeights, 0, scratchWeights, centroidCount, bufferCount);
+ bufferCount = 0;
+ sortByMean(scratchMeans, scratchWeights, 0, size - 1);
+
+ double weight = 0.0;
+ for (int i = 0; i < size; i++) {
+ weight += scratchWeights[i];
+ }
+ totalWeight = weight;
+
+ int last = 0;
+ centroidMeans[0] = scratchMeans[0];
+ centroidWeights[0] = scratchWeights[0];
+ double weightOfClosedCentroids = 0.0;
+ double weightLimit = totalWeight * quantileLimit(0.0);
+
+ for (int i = 1; i < size; i++) {
+ double projected = centroidWeights[last] + scratchWeights[i];
+ boolean roomLeft = weightOfClosedCentroids + projected <= weightLimit;
+ if (roomLeft || last == centroidMeans.length - 1) {
+ centroidWeights[last] = projected;
+ centroidMeans[last] += (scratchMeans[i] - centroidMeans[last]) * scratchWeights[i] / projected;
+ } else {
+ weightOfClosedCentroids += centroidWeights[last];
+ weightLimit = totalWeight * quantileLimit(weightOfClosedCentroids / totalWeight);
+ last++;
+ centroidMeans[last] = scratchMeans[i];
+ centroidWeights[last] = scratchWeights[i];
+ }
+ }
+ centroidCount = last + 1;
+ }
+
+ /**
+ * Returns the quantile at which the centroid starting at quantile {@code q} must be closed, that
+ * is {@code kInverse(k(q) + 1)} for the scale function {@code k}.
+ */
+ private double quantileLimit(double q) {
+ // Rounding in the running weight sum could push the ratio a hair outside [0, 1], which would
+ // turn asin into NaN and silently disable the size limit.
+ double bounded = Math.min(1.0, Math.max(0.0, q));
+ double k = compression / TWO_PI * Math.asin(2.0 * bounded - 1.0) + 1.0;
+ double angle = k * TWO_PI / compression;
+ if (angle >= Math.PI / 2.0) {
+ return 1.0;
+ }
+ if (angle <= -Math.PI / 2.0) {
+ return 0.0;
+ }
+ return (Math.sin(angle) + 1.0) / 2.0;
+ }
+
+ private void requireNonEmpty() {
+ if (centroidCount == 0) {
+ throw new IllegalStateException("The digest has not seen any sample yet");
+ }
+ }
+
+ private void requireAnySample() {
+ if (isEmpty()) {
+ throw new IllegalStateException("The digest has not seen any sample yet");
+ }
+ }
+
+ /**
+ * Linear interpolation between two knots, degenerating to the right knot when they coincide.
+ */
+ private static double interpolate(double x0, double y0, double x1, double y1, double x) {
+ if (x1 <= x0) {
+ return y1;
+ }
+ return y0 + (x - x0) / (x1 - x0) * (y1 - y0);
+ }
+
+ /**
+ * Sorts two parallel arrays by the values of the first one, using quicksort with a
+ * median-of-three pivot and an insertion sort for short ranges.
+ */
+ private static void sortByMean(double[] means, double[] weights, int from, int to) {
+ int low = from;
+ int high = to;
+ while (low < high) {
+ if (high - low < INSERTION_SORT_THRESHOLD) {
+ insertionSort(means, weights, low, high);
+ return;
+ }
+ int pivotIndex = partition(means, weights, low, high);
+ // Recurse into the smaller half and loop on the larger one to keep the stack shallow.
+ if (pivotIndex - low < high - pivotIndex) {
+ sortByMean(means, weights, low, pivotIndex - 1);
+ low = pivotIndex + 1;
+ } else {
+ sortByMean(means, weights, pivotIndex + 1, high);
+ high = pivotIndex - 1;
+ }
+ }
+ }
+
+ private static int partition(double[] means, double[] weights, int low, int high) {
+ int middle = low + (high - low) / 2;
+ if (means[middle] < means[low]) {
+ swap(means, weights, low, middle);
+ }
+ if (means[high] < means[low]) {
+ swap(means, weights, low, high);
+ }
+ if (means[high] < means[middle]) {
+ swap(means, weights, middle, high);
+ }
+ swap(means, weights, middle, high - 1);
+ double pivot = means[high - 1];
+
+ int store = low;
+ for (int i = low; i < high - 1; i++) {
+ if (means[i] < pivot) {
+ swap(means, weights, store, i);
+ store++;
+ }
+ }
+ swap(means, weights, store, high - 1);
+ return store;
+ }
+
+ private static void insertionSort(double[] means, double[] weights, int low, int high) {
+ for (int i = low + 1; i <= high; i++) {
+ double mean = means[i];
+ double weight = weights[i];
+ int j = i - 1;
+ while (j >= low && means[j] > mean) {
+ means[j + 1] = means[j];
+ weights[j + 1] = weights[j];
+ j--;
+ }
+ means[j + 1] = mean;
+ weights[j + 1] = weight;
+ }
+ }
+
+ private static void swap(double[] means, double[] weights, int i, int j) {
+ double mean = means[i];
+ means[i] = means[j];
+ means[j] = mean;
+ double weight = weights[i];
+ weights[i] = weights[j];
+ weights[j] = weight;
+ }
+
+ /**
+ * Returns the centroid means, smallest first. Exposed for tests and for inspecting the shape of
+ * the sketch; the returned array is a copy.
+ *
+ * @return a copy of the centroid means
+ */
+ double[] centroidMeansSnapshot() {
+ flushBuffer();
+ return Arrays.copyOf(centroidMeans, centroidCount);
+ }
+
+ /**
+ * Returns the centroid weights, aligned with {@link #centroidMeansSnapshot()}.
+ *
+ * @return a copy of the centroid weights
+ */
+ double[] centroidWeightsSnapshot() {
+ flushBuffer();
+ return Arrays.copyOf(centroidWeights, centroidCount);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/streaming/UnscentedKalmanFilter.java b/src/main/java/com/thealgorithms/streaming/UnscentedKalmanFilter.java
new file mode 100644
index 000000000000..69abdf0eb6c2
--- /dev/null
+++ b/src/main/java/com/thealgorithms/streaming/UnscentedKalmanFilter.java
@@ -0,0 +1,561 @@
+package com.thealgorithms.streaming;
+
+import com.thealgorithms.matrix.InverseOfMatrix;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * The unscented Kalman filter: a Kalman filter for a nonlinear model that samples the model
+ * instead of differentiating it.
+ *
+ * The extended filter pushes the covariance through a Jacobian, which is a straight line drawn at
+ * one point. That needs the model to be differentiable, needs somebody to write the derivative, and
+ * throws away everything the model does beyond first order. The unscented filter takes the opposite
+ * route. It picks a small, deterministic set of sigma points whose mean and covariance are
+ * exactly those of the current estimate, sends every one of them through the real model, and reads
+ * the new mean and covariance off where they land:
+ *
+ * The square root is the Cholesky factor, so a step costs {@code 2n + 1} evaluations of the model
+ * and one factorisation, about what the extended filter costs, with no Jacobian anywhere. The payoff
+ * is accuracy: the transformed mean and covariance are right to second order for any model, where
+ * the extended filter is right to first. For {@code x^2} with {@code x} normally distributed, the
+ * extended filter predicts a mean of {@code mu^2} and misses the {@code sigma^2} that the spread
+ * itself contributes; the unscented one gets the mean exactly, and with the defaults it gets the
+ * variance {@code 4 mu^2 sigma^2 + 2 sigma^4} exactly as well. For a linear model it reduces to the
+ * ordinary Kalman filter, step for step.
+ *
+ * The three parameters are left at {@code alpha = 1}, {@code beta = 2}, {@code kappa = 0}. The
+ * value {@code alpha = 1e-3} often quoted from the literature puts the sigma points almost on top of
+ * the mean and compensates with a central weight of about minus a million, which is exact on paper
+ * and a loss of six digits in floating point. {@code beta = 2} is the optimal choice for a Gaussian
+ * prior and is what makes the variance of the quadratic above come out exact.
+ *
+ * After an update the covariance {@code P - K S K'} is symmetrised explicitly. The subtraction is
+ * symmetric in exact arithmetic but not after rounding, and a covariance that has drifted out of
+ * symmetry eventually fails the Cholesky factorisation of the next step. When that factorisation does
+ * fail, because the covariance has stopped being positive definite, the step is refused with an
+ * {@link ArithmeticException} rather than carried on with a square root that does not exist.
+ *
+ * A step costs O(n^3) for the factorisation plus {@code 2n + 1} calls of the model. The inversion
+ * of the innovation covariance reuses {@link InverseOfMatrix#invert(double[][])}. This class is not
+ * thread-safe.
+ *
+ * @see ExtendedKalmanFilter
+ * @see KalmanFilter
+ * @see Unscented Kalman filter
+ */
+public final class UnscentedKalmanFilter {
+
+ /** Spread of the sigma points used when none is given. */
+ public static final double DEFAULT_ALPHA = 1.0;
+
+ /** Prior knowledge of the distribution used when none is given, optimal for a Gaussian. */
+ public static final double DEFAULT_BETA = 2.0;
+
+ /** Secondary scaling used when none is given. */
+ public static final double DEFAULT_KAPPA = 0.0;
+
+ private final int stateSize;
+ private final double alpha;
+ private final double beta;
+ private final double kappa;
+ private final double lambda;
+ private final double[] meanWeights;
+ private final double[] covarianceWeights;
+
+ private double[] state;
+ private double[][] covariance;
+ private double[] innovation;
+ private long predictions;
+ private long corrections;
+
+ /**
+ * Creates a filter with the default sigma point parameters.
+ *
+ * @param initialState the first guess at the state, copied
+ * @param initialCovariance how uncertain that guess is, copied
+ * @throws IllegalArgumentException if the state is empty, if the covariance is not square and of
+ * the same size, or if either holds a value that is not finite
+ * @throws NullPointerException if an argument is {@code null}
+ */
+ public UnscentedKalmanFilter(double[] initialState, double[][] initialCovariance) {
+ this(initialState, initialCovariance, DEFAULT_ALPHA, DEFAULT_BETA, DEFAULT_KAPPA);
+ }
+
+ /**
+ * Creates a filter.
+ *
+ * @param initialState the first guess at the state, copied
+ * @param initialCovariance how uncertain that guess is, copied
+ * @param alpha how far the sigma points spread from the mean, strictly positive
+ * @param beta what is known about the shape of the distribution, not negative; two is optimal for
+ * a Gaussian
+ * @param kappa a secondary scaling, chosen so that {@code n + kappa} is strictly positive
+ * @throws IllegalArgumentException if a shape is wrong, a value is not finite, or the parameters do
+ * not leave {@code n + lambda} strictly positive
+ * @throws NullPointerException if an argument is {@code null}
+ */
+ public UnscentedKalmanFilter(double[] initialState, double[][] initialCovariance, double alpha, double beta, double kappa) {
+ if (initialState.length == 0) {
+ throw new IllegalArgumentException("The state must not be empty");
+ }
+ if (!(alpha > 0.0) || !Double.isFinite(alpha)) {
+ throw new IllegalArgumentException("The alpha must be finite and strictly positive, but was " + alpha);
+ }
+ if (!(beta >= 0.0) || !Double.isFinite(beta)) {
+ throw new IllegalArgumentException("The beta must be finite and not negative, but was " + beta);
+ }
+ if (!Double.isFinite(kappa)) {
+ throw new IllegalArgumentException("The kappa must be finite, but was " + kappa);
+ }
+
+ this.stateSize = initialState.length;
+ requireVector(initialState, stateSize, "initial state");
+ requireMatrix(initialCovariance, stateSize, stateSize, "initial covariance");
+
+ this.alpha = alpha;
+ this.beta = beta;
+ this.kappa = kappa;
+ this.lambda = alpha * alpha * (stateSize + kappa) - stateSize;
+ double scale = stateSize + lambda;
+ if (!(scale > 0.0)) {
+ throw new IllegalArgumentException("The parameters leave n + lambda at " + scale + ", but it must be strictly positive");
+ }
+
+ int points = 2 * stateSize + 1;
+ this.meanWeights = new double[points];
+ this.covarianceWeights = new double[points];
+ meanWeights[0] = lambda / scale;
+ covarianceWeights[0] = meanWeights[0] + 1.0 - alpha * alpha + beta;
+ for (int i = 1; i < points; i++) {
+ meanWeights[i] = 1.0 / (2.0 * scale);
+ covarianceWeights[i] = meanWeights[i];
+ }
+
+ this.state = initialState.clone();
+ this.covariance = copy(initialCovariance);
+ this.innovation = new double[0];
+ }
+
+ /**
+ * Pushes the estimate forward through the model.
+ *
+ * @param transition the model, which maps a state to the state one step later
+ * @param processNoise how much the model is trusted, a square matrix of the size of the state
+ * @return a copy of the predicted state
+ * @throws IllegalArgumentException if the model returns the wrong shape or a value that is not
+ * finite, or if the process noise has the wrong shape
+ * @throws ArithmeticException if the covariance is no longer positive definite
+ * @throws NullPointerException if an argument is {@code null}
+ */
+ public double[] predict(UnaryOperator The textbook formula {@code Var = (sum(x^2) - n * mean^2) / (n - 1)} is fast but numerically
+ * treacherous: {@code sum(x^2)} and {@code n * mean^2} may be huge and nearly equal, so their
+ * difference loses most of its significant digits and can even come out negative. Welford's
+ * recurrence never forms those large intermediate values. It keeps only the running mean and the sum
+ * of squared deviations from that running mean, {@code M2}:
+ *
+ * Both {@link #add(double)} and {@link #remove(double)} run in O(1) time and the accumulator
+ * occupies O(1) memory no matter how many samples pass through it.
+ *
+ * This class is not thread-safe.
+ *
+ * @see ExponentialMovingAverage
+ * @see Algorithms for calculating variance
+ */
+public final class WelfordAlgorithm {
+
+ private long count;
+ private double mean;
+ private double sumOfSquaredDeviations;
+
+ /**
+ * Creates an empty accumulator.
+ */
+ public WelfordAlgorithm() {
+ clear();
+ }
+
+ /**
+ * Incorporates one sample.
+ *
+ * @param value the sample to add
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public void add(double value) {
+ requireFinite(value);
+ count++;
+ double delta = value - mean;
+ mean += delta / count;
+ sumOfSquaredDeviations += delta * (value - mean);
+ }
+
+ /**
+ * Incorporates every given sample, in order.
+ *
+ * @param values the samples to add
+ * @throws IllegalArgumentException if any value is NaN or infinite
+ * @throws NullPointerException if {@code values} is {@code null}
+ */
+ public void addAll(double... values) {
+ for (double value : values) {
+ add(value);
+ }
+ }
+
+ /**
+ * Removes a previously added sample, reversing {@link #add(double)}. This is what makes the
+ * accumulator usable for a sliding window.
+ *
+ * @param value the sample to remove; it must genuinely have been added before
+ * @throws IllegalStateException if the accumulator is empty
+ * @throws IllegalArgumentException if {@code value} is NaN or infinite
+ */
+ public void remove(double value) {
+ requireFinite(value);
+ if (count == 0) {
+ throw new IllegalStateException("Cannot remove a sample from an empty accumulator");
+ }
+ if (count == 1) {
+ clear();
+ return;
+ }
+ double previousMean = mean;
+ mean = (count * mean - value) / (count - 1);
+ sumOfSquaredDeviations -= (value - previousMean) * (value - mean);
+ count--;
+ if (sumOfSquaredDeviations < 0.0) {
+ sumOfSquaredDeviations = 0.0;
+ }
+ }
+
+ /**
+ * Combines two independently accumulated summaries using Chan's parallel variance update.
+ *
+ * @param left summary of the first batch of samples
+ * @param right summary of the second batch of samples
+ * @return a new summary describing the concatenation of both batches
+ * @throws NullPointerException if either argument is {@code null}
+ */
+ public static WelfordAlgorithm merge(WelfordAlgorithm left, WelfordAlgorithm right) {
+ WelfordAlgorithm merged = new WelfordAlgorithm();
+ merged.count = left.count + right.count;
+ if (merged.count == 0) {
+ return merged;
+ }
+ double delta = right.mean - left.mean;
+ merged.mean = left.mean + delta * right.count / merged.count;
+ merged.sumOfSquaredDeviations = left.sumOfSquaredDeviations + right.sumOfSquaredDeviations + delta * delta * left.count * right.count / merged.count;
+ return merged;
+ }
+
+ /**
+ * Returns the number of samples seen so far.
+ *
+ * @return the sample count
+ */
+ public long count() {
+ return count;
+ }
+
+ /**
+ * Tells whether any sample has been added.
+ *
+ * @return {@code true} if no sample is currently accounted for
+ */
+ public boolean isEmpty() {
+ return count == 0;
+ }
+
+ /**
+ * Returns the arithmetic mean of the samples.
+ *
+ * @return the mean, or {@link Double#NaN} if no sample has been added
+ */
+ public double mean() {
+ return count == 0 ? Double.NaN : mean;
+ }
+
+ /**
+ * Returns the sum of the samples, reconstructed from the mean.
+ *
+ * @return {@code count * mean}, or {@code 0} if no sample has been added
+ */
+ public double sum() {
+ return count == 0 ? 0.0 : mean * count;
+ }
+
+ /**
+ * Returns the sum of squared deviations from the mean, {@code M2}.
+ *
+ * @return the sum of squared deviations, {@code 0} for an empty accumulator
+ */
+ public double sumOfSquaredDeviations() {
+ return sumOfSquaredDeviations;
+ }
+
+ /**
+ * Returns the unbiased sample variance, normalised by {@code count - 1}.
+ *
+ * @return the sample variance, or {@link Double#NaN} if fewer than two samples were added
+ */
+ public double sampleVariance() {
+ return count < 2 ? Double.NaN : sumOfSquaredDeviations / (count - 1);
+ }
+
+ /**
+ * Returns the population variance, normalised by {@code count}.
+ *
+ * @return the population variance, or {@link Double#NaN} if no sample has been added
+ */
+ public double populationVariance() {
+ return count == 0 ? Double.NaN : sumOfSquaredDeviations / count;
+ }
+
+ /**
+ * Returns the square root of {@link #sampleVariance()}.
+ *
+ * @return the sample standard deviation, or {@link Double#NaN} if fewer than two samples were added
+ */
+ public double sampleStandardDeviation() {
+ return Math.sqrt(sampleVariance());
+ }
+
+ /**
+ * Returns the square root of {@link #populationVariance()}.
+ *
+ * @return the population standard deviation, or {@link Double#NaN} if no sample has been added
+ */
+ public double populationStandardDeviation() {
+ return Math.sqrt(populationVariance());
+ }
+
+ /**
+ * Returns the standard error of the mean, {@code sampleStandardDeviation / sqrt(count)}.
+ *
+ * @return the standard error, or {@link Double#NaN} if fewer than two samples were added
+ */
+ public double standardError() {
+ return sampleStandardDeviation() / Math.sqrt(count);
+ }
+
+ /**
+ * Forgets every sample.
+ */
+ public void clear() {
+ count = 0;
+ mean = 0.0;
+ sumOfSquaredDeviations = 0.0;
+ }
+
+ @Override
+ public String toString() {
+ return "WelfordAlgorithm{count=" + count + ", mean=" + mean() + ", sampleStandardDeviation=" + sampleStandardDeviation() + '}';
+ }
+
+ private static void requireFinite(double value) {
+ if (!Double.isFinite(value)) {
+ throw new IllegalArgumentException("Samples must be finite, but was " + value);
+ }
+ }
+}
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:
+ * The algorithm compares candidate rotations without materializing them,
+ * running in O(n) time and O(1) auxiliary space. For example, the smallest rotation of {@code "baca"} is
+ * {@code "abac"}, which starts at index {@code 3}.
+ * 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.
+ * A "word" is defined as a maximal substring consisting of non-space
+ * characters only. Trailing spaces at the end of the string are ignored.
+ *
+ * Example:
+ * This implementation runs in O(n) time complexity, where n is the length
+ * of the input string, and uses O(1) additional space.
+ */
+public class LengthOfLastWord {
+
+ /**
+ * Returns the length of the last word in the specified string.
+ *
+ * The method iterates from the end of the string, skipping trailing
+ * spaces first, and then counts the number of consecutive non-space characters
+ * characters until another space (or the beginning of the string) is reached.
+ *
+ * @param s the input string to analyze
+ * @return the length of the last word in {@code s}; returns 0 if there is no word
+ * @throws NullPointerException if {@code s} is {@code null}
+ */
+ public int lengthOfLastWord(String s) {
+ int sizeOfString = s.length() - 1;
+ int lastWordLength = 0;
+
+ // Skip trailing spaces from the end of the string
+ while (sizeOfString >= 0 && s.charAt(sizeOfString) == ' ') {
+ sizeOfString--;
+ }
+
+ // Count the characters of the last word
+ while (sizeOfString >= 0 && s.charAt(sizeOfString) != ' ') {
+ lastWordLength++;
+ sizeOfString--;
+ }
+
+ return lastWordLength;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java
new file mode 100644
index 000000000000..27aef64ee883
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.strings;
+
+/**
+ * Longest Common Substring finds the longest string that is a
+ * contiguous substring of two input strings.
+ * Example: "abcdef" and "zcdemf" -> "cde"
+ *
+ * @see
+ * Wikipedia: Longest Common Substring
+ *
+ * author: Vraj Prajapati @Rosander0
+ */
+public final class LongestCommonSubstring {
+
+ private LongestCommonSubstring() {
+ // Utility class
+ }
+
+ /**
+ * Finds the longest common substring of two strings.
+ *
+ * @param a First input string
+ * @param b Second input string
+ * @return The longest common substring, or empty string if none exists.
+ * If multiple substrings share the maximum length, the first one found is returned.
+ */
+ public static String longestCommonSubstring(final String a, final String b) {
+ if (a == null || b == null || a.isEmpty() || b.isEmpty()) {
+ return "";
+ }
+
+ int[] dp = new int[b.length() + 1];
+ int maxLength = 0;
+ int endIndex = 0;
+
+ for (int i = 1; i <= a.length(); i++) {
+ for (int j = b.length(); j >= 1; j--) {
+ if (a.charAt(i - 1) == b.charAt(j - 1)) {
+ dp[j] = dp[j - 1] + 1;
+ if (dp[j] > maxLength) {
+ maxLength = dp[j];
+ endIndex = i;
+ }
+ } else {
+ dp[j] = 0;
+ }
+ }
+ }
+
+ if (maxLength == 0) {
+ return "";
+ }
+ return a.substring(endIndex - maxLength, endIndex);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
index 6808cd50602f..51e8dc6b02c3 100644
--- a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
+++ b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java
@@ -13,6 +13,12 @@ private LongestNonRepetitiveSubstring() {
/**
* Finds the length of the longest substring without repeating characters.
*
+ * Uses the sliding window technique with a HashMap to track
+ * the last seen index of each character.
+ *
+ * Time Complexity: O(n), where n is the length of the input string.
+ * Space Complexity: O(min(n, m)), where m is the size of the character set.
+ *
* @param s the input string
* @return the length of the longest non-repetitive substring
*/
diff --git a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java b/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java
deleted file mode 100644
index ca500357ba77..000000000000
--- a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.thealgorithms.strings;
-
-final class LongestPalindromicSubstring {
- private LongestPalindromicSubstring() {
- }
-
- /**
- * Finds the longest palindromic substring in the given string.
- *
- * @param s the input string
- * @return the longest palindromic substring
- */
- public static String longestPalindrome(String s) {
- if (s == null || s.isEmpty()) {
- return "";
- }
- String maxStr = "";
- for (int i = 0; i < s.length(); ++i) {
- for (int j = i; j < s.length(); ++j) {
- if (isValid(s, i, j) && (j - i + 1 > maxStr.length())) {
- maxStr = s.substring(i, j + 1);
- }
- }
- }
- return maxStr;
- }
-
- private static boolean isValid(String s, int lo, int hi) {
- int n = hi - lo + 1;
- for (int i = 0; i < n / 2; ++i) {
- if (s.charAt(lo + i) != s.charAt(hi - i)) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java
new file mode 100644
index 000000000000..87c9278fd4bf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java
@@ -0,0 +1,83 @@
+package com.thealgorithms.strings;
+
+/**
+ * Finds the longest substring that occurs at least twice in a given string.
+ *
+ * Uses the suffix array (via {@link SuffixArray}) and Kasai's algorithm
+ * to build the LCP (Longest Common Prefix) array, then returns the substring
+ * corresponding to the maximum LCP value. Time complexity: O(n logΒ² n) for suffix array construction + O(n) for LCP. LCP[i] is the length of the longest common prefix between the suffixes
+ * at positions suffixArray[i] and suffixArray[i+1] in sorted order. Duval's algorithm runs in O(n) time and uses O(1) auxiliary working
+ * space, excluding the returned list and its strings.
+ *
+ * Reference:
+ * https://cp-algorithms.com/string/lyndon_factorization.html
+ */
+public final class LyndonFactorization {
+ private LyndonFactorization() {
+ }
+
+ /**
+ * Returns the standard Lyndon factorization of {@code text}.
+ *
+ * The returned factors are in non-increasing lexicographic order
+ * according to Java's UTF-16 code-unit ordering.
+ *
+ * @param text input string to factorize
+ * @return Lyndon factors in non-increasing lexicographic order
+ * @throws IllegalArgumentException if {@code text} is null
+ */
+ public static List 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
- * A string is considered valid if:
- *
Space Complexity: O(m Γ n)
+ */
+public class RottingOranges {
+
+ private static final int[] DEL_ROW = {-1, 0, 1, 0};
+ private static final int[] DEL_COL = {0, 1, 0, -1};
+
+ private static final class Cell {
+ private final int row;
+ private final int col;
+ private final int minute;
+
+ Cell(int row, int col, int minute) {
+ this.row = row;
+ this.col = col;
+ this.minute = minute;
+ }
+ }
+
+ /**
+ * Executes the Rotting Oranges algorithm.
+ *
+ * @param grid the input grid
+ * @return minimum minutes required to rot all fresh oranges,
+ * or -1 if impossible
+ */
+ public int run(int[][] grid) {
+
+ if (grid == null || grid.length == 0 || grid[0].length == 0) {
+ return 0;
+ }
+
+ int rows = grid.length;
+ int cols = grid[0].length;
+
+ // Create a copy so original input is not modified
+ int[][] copy = new int[rows][cols];
+
+ for (int i = 0; i < rows; i++) {
+ copy[i] = grid[i].clone();
+ }
+
+ Queue
+ *
+ *
+ * IMPORTANT contracts
+ *
+ *
+ *
+ *
+ *
+ * If you prefer value-based semantics, replace with HashMap
+ *
+ */
+public final class MiddleOfLinkedList {
+
+ private MiddleOfLinkedList() {
+ }
+
+ /**
+ * Returns the middle node of the list.
+ *
+ * @param head the head of the singly linked list; may be {@code null}
+ * @return the middle node (second middle for even-sized lists), or {@code null} if {@code head} is {@code null}
+ */
+ public static SinglyLinkedListNode middleNode(final SinglyLinkedListNode head) {
+ if (head == null) {
+ return null;
+ }
+
+ SinglyLinkedListNode slow = head;
+ SinglyLinkedListNode fast = head;
+
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+
+ return slow;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
similarity index 84%
rename from src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
rename to src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
index c81476eaec32..7bb16921b9ef 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.datastructures.lists;
import java.util.Stack;
@@ -9,6 +9,13 @@
*
* See more:
* https://www.geeksforgeeks.org/function-to-check-if-a-singly-linked-list-is-palindrome/
+ *
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.maths.LowestBasePalindrome
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
*/
@SuppressWarnings("rawtypes")
public final class PalindromeSinglyLinkedList {
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
index f018781ada70..a7c2da913cd5 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
@@ -20,7 +20,7 @@
* List lessThanPivot : 3 -> 1 -> 2 -> 4
* List greaterThanPivot : 5 -> 8 -> 10 -> 7 -> 9 -> 6
*
- * -> reccur for lessThanPivot and greaterThanPivot
+ * -> recur for lessThanPivot and greaterThanPivot
*
* lessThanPivot :
* current pivot : 3
@@ -32,7 +32,7 @@
* lessThanPivot : null
* greaterThanPivot : 8 -> 10 -> 7 -> 9 -> 6
*
- * By following the above pattern, reccuring tree will form like below :
+ * By following the above pattern, the recursion tree will form like below :
*
* List-> 5 -> 3 -> 8 -> 1 -> 10 -> 2 -> 7 -> 4 -> 9 -> 6
*
@@ -66,7 +66,7 @@
* (N) (N) (N) (N)
*
*
- * -> After this the tree will reccur back (or backtrack)
+ * -> After this the tree will recur back (or backtrack)
* and the returning list from left and right subtree will attach
* themselves around pivot.
* i.e. ,
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
new file mode 100644
index 000000000000..200c636ce1ab
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SelfOrganizingLinkedList.java
@@ -0,0 +1,105 @@
+package com.thealgorithms.datastructures.lists;
+
+import java.util.Objects;
+
+/**
+ * A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy.
+ * When an element is searched, it is automatically moved to the head of the list
+ * to optimize subsequent lookups.
+ *
+ * @param Rules:
+ *
+ *
+ *
+ * Example:
+ *
+ *
+ * Input tree:
+ * 1
+ * / \
+ * 2 3
+ * \
+ * 4
+ *
+ * Output string:
+ * "1(2()(4))(3)"
+ *
+ *
+ * > adj;
+ private final int[] parent;
+ private final int[] subtreeSize;
+ private final boolean[] removed;
+ private int root;
+
+ /**
+ * Constructs a centroid tree from an adjacency list.
+ *
+ * @param adj adjacency list representation of the tree (0-indexed)
+ * @throws IllegalArgumentException if tree is empty or null
+ */
+ public CentroidTree(List
> adj) {
+ if (adj == null || adj.isEmpty()) {
+ throw new IllegalArgumentException("Tree cannot be empty or null");
+ }
+
+ this.n = adj.size();
+ this.adj = adj;
+ this.parent = new int[n];
+ this.subtreeSize = new int[n];
+ this.removed = new boolean[n];
+ Arrays.fill(parent, -1);
+
+ // Build centroid tree starting from node 0
+ this.root = decompose(0, -1);
+ }
+
+ /**
+ * Recursively builds the centroid tree.
+ *
+ * @param u current node
+ * @param p parent in centroid tree
+ * @return centroid of current component
+ */
+ private int decompose(int u, int p) {
+ int size = getSubtreeSize(u, -1);
+ int centroid = findCentroid(u, -1, size);
+
+ removed[centroid] = true;
+ parent[centroid] = p;
+
+ // Recursively decompose each subtree
+ for (int v : adj.get(centroid)) {
+ if (!removed[v]) {
+ decompose(v, centroid);
+ }
+ }
+
+ return centroid;
+ }
+
+ /**
+ * Calculates subtree size from node u.
+ *
+ * @param u current node
+ * @param p parent node (-1 for root)
+ * @return size of subtree rooted at u
+ */
+ private int getSubtreeSize(int u, int p) {
+ subtreeSize[u] = 1;
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v]) {
+ subtreeSize[u] += getSubtreeSize(v, u);
+ }
+ }
+ return subtreeSize[u];
+ }
+
+ /**
+ * Finds the centroid of a subtree.
+ * A centroid is a node whose removal creates components with size <= totalSize/2.
+ *
+ * @param u current node
+ * @param p parent node
+ * @param totalSize total size of current component
+ * @return centroid node
+ */
+ private int findCentroid(int u, int p, int totalSize) {
+ for (int v : adj.get(u)) {
+ if (v != p && !removed[v] && subtreeSize[v] > totalSize / 2) {
+ return findCentroid(v, u, totalSize);
+ }
+ }
+ return u;
+ }
+
+ /**
+ * Gets the parent of a node in the centroid tree.
+ *
+ * @param node the node
+ * @return parent node in centroid tree, or -1 if root
+ */
+ public int getParent(int node) {
+ if (node < 0 || node >= n) {
+ throw new IllegalArgumentException("Invalid node: " + node);
+ }
+ return parent[node];
+ }
+
+ /**
+ * Gets the root of the centroid tree.
+ *
+ * @return root node
+ */
+ public int getRoot() {
+ return root;
+ }
+
+ /**
+ * Gets the number of nodes in the tree.
+ *
+ * @return number of nodes
+ */
+ public int size() {
+ return n;
+ }
+
+ /**
+ * Returns the centroid tree structure as a string.
+ * Format: node -> parent (or ROOT for root node)
+ *
+ * @return string representation
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Centroid Tree:\n");
+ for (int i = 0; i < n; i++) {
+ sb.append("Node ").append(i).append(" -> ");
+ if (parent[i] == -1) {
+ sb.append("ROOT");
+ } else {
+ sb.append("Parent ").append(parent[i]);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Creates a centroid tree from an edge list.
+ *
+ * @param n number of nodes (0-indexed: 0 to n-1)
+ * @param edges list of edges where each edge is [u, v]
+ * @return CentroidTree object
+ * @throws IllegalArgumentException if n <= 0 or edges is invalid
+ */
+ public static CentroidTree buildFromEdges(int n, int[][] edges) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("Number of nodes must be positive");
+ }
+ if (edges == null) {
+ throw new IllegalArgumentException("Edges cannot be null");
+ }
+ if (edges.length != n - 1) {
+ throw new IllegalArgumentException("Tree must have exactly n-1 edges");
+ }
+
+ List
> adj = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ adj.add(new ArrayList<>());
+ }
+
+ for (int[] edge : edges) {
+ if (edge.length != 2) {
+ throw new IllegalArgumentException("Each edge must have exactly 2 nodes");
+ }
+ int u = edge[0];
+ int v = edge[1];
+
+ if (u < 0 || u >= n || v < 0 || v >= n) {
+ throw new IllegalArgumentException("Invalid node in edge: [" + u + ", " + v + "]");
+ }
+
+ adj.get(u).add(v);
+ adj.get(v).add(u);
+ }
+
+ return new CentroidTree(adj);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
index 5378a01f6642..e9c355baa4eb 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
@@ -1,29 +1,37 @@
package com.thealgorithms.datastructures.trees;
+/**
+ * Fenwick Tree for point updates and prefix-sum queries in O(log n).
+ *
+ * @see Fenwick Tree
+ */
public class FenwickTree {
- private int n;
- private int[] fenTree;
+ private final int n;
+ private final int[] fenTree;
- /* Constructor which takes the size of the array as a parameter */
+ /** Creates a Fenwick tree with n elements, all initialized to zero. */
public FenwickTree(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Size must be non-negative");
+ }
this.n = n;
this.fenTree = new int[n + 1];
}
- /* A function which will add the element val at index i*/
+ /** Adds val to the element at index i. */
public void update(int i, int val) {
- // As index starts from 0, increment the index by 1
- i += 1;
+ checkIndex(i);
+ i += 1; // Convert to the internal 1-based index
while (i <= n) {
fenTree[i] += val;
i += i & (-i);
}
}
- /* A function which will return the cumulative sum from index 1 to index i*/
+ /** Returns the sum of elements from index 0 to i. */
public int query(int i) {
- // As index starts from 0, increment the index by 1
+ checkIndex(i);
i += 1;
int cumSum = 0;
while (i > 0) {
@@ -32,4 +40,11 @@ public int query(int i) {
}
return cumSum;
}
+
+ // Check that the index is within the valid range.
+ private void checkIndex(int i) {
+ if (i < 0 || i >= n) {
+ throw new IndexOutOfBoundsException("Index " + i + " out of bounds for size " + n);
+ }
+ }
}
diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
similarity index 99%
rename from src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java
rename to src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
index 236a23205180..ed67f9ae3394 100644
--- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.tree;
+package com.thealgorithms.datastructures.trees;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/KDTree.java b/src/main/java/com/thealgorithms/datastructures/trees/KDTree.java
index 5190e82f74ef..f2bab248ff28 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/KDTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/KDTree.java
@@ -406,12 +406,14 @@ private Node delete(Node root, Node node) {
} else if (root.left != null) {
Node min = findMin(root.left, root.getAxis());
root.point = min.point;
- root.left = delete(root.left, min);
+ root.right = delete(root.left, min);
+ root.left = null;
} else {
return null;
}
+ return root;
}
- if (root.getAxisCoordinate() < node.point.getCoordinate(root.getAxis())) {
+ if (node.point.getCoordinate(root.getAxis()) < root.getAxisCoordinate()) {
root.left = delete(root.left, node);
} else {
root.right = delete(root.right, node);
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
index 57b3edc163ca..4fbb578dcf50 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java
@@ -8,13 +8,18 @@ public class SegmentTree {
/* Constructor which takes the size of the array and the array as a parameter*/
public SegmentTree(int n, int[] arr) {
+ if (arr == null) {
+ throw new IllegalArgumentException("Input array must not be null");
+ }
+ if (n <= 0 || n > arr.length) {
+ throw new IllegalArgumentException("Size must be in the range [1, " + arr.length + "], but was " + n);
+ }
this.n = n;
int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
int segSize = 2 * (int) Math.pow(2, x) - 1;
this.segTree = new int[segSize];
this.arr = arr;
- this.n = n;
constructTree(arr, 0, n - 1, 0);
}
@@ -30,7 +35,7 @@ public final int constructTree(int[] arr, int start, int end, int index) {
return this.segTree[index];
}
- /* A function which will update the value at a index i. This will be called by the
+ /* A function which will update the value at an index i. This will be called by the
update function internally*/
private void updateTree(int start, int end, int index, int diff, int segIndex) {
if (index < start || index > end) {
@@ -47,7 +52,8 @@ private void updateTree(int start, int end, int index, int diff, int segIndex) {
/* A function to update the value at a particular index*/
public void update(int index, int value) {
- if (index < 0 || index > n) {
+ // Valid positions are 0..n-1; index == n is out of bounds and must not reach arr[index].
+ if (index < 0 || index >= n) {
return;
}
@@ -73,7 +79,8 @@ private int getSumTree(int start, int end, int qStart, int qEnd, int segIndex) {
/* A function to query the sum of the subarray [start...end]*/
public int getSum(int start, int end) {
- if (start < 0 || end > n || start > end) {
+ // The last queryable position is n-1, so end == n is an out of range query.
+ if (start < 0 || end >= n || start > end) {
return 0;
}
return getSumTree(0, n - 1, start, end, 0);
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java
new file mode 100644
index 000000000000..40b9e8a73533
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java
@@ -0,0 +1,201 @@
+package com.thealgorithms.datastructures.trees;
+
+/**
+ * 2D Segment Tree (Tree of Trees) implementation.
+ * This data structure supports point updates and submatrix sum queries
+ * in a 2D grid. It achieves this by nesting 1D Segment Trees within a 1D Segment Tree.
+ *
+ * Time Complexity:
+ * - Build/Initialization: O(N * M)
+ * - Point Update: O(log N * log M)
+ * - Submatrix Query: O(log N * log M)
+ *
+ * @see 2D Segment Tree
+ */
+public class SegmentTree2D {
+
+ /**
+ * Represents a 1D Segment Tree.
+ * This is equivalent to your 'Sagara' struct. It manages the columns (X-axis).
+ */
+ public static class SegmentTree1D {
+ private int n;
+ private final int[] tree;
+
+ /**
+ * Initializes the 1D Segment Tree with the nearest power of 2.
+ *
+ * @param size The expected number of elements (columns).
+ */
+ public SegmentTree1D(int size) {
+ n = 1;
+ while (n < size) {
+ n *= 2;
+ }
+ tree = new int[n * 2];
+ }
+
+ /**
+ * Recursively updates a point in the 1D tree.
+ */
+ private void update(int index, int val, int node, int lx, int rx) {
+ if (rx - lx == 1) {
+ tree[node] = val;
+ return;
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftChild = node * 2 + 1;
+ int rightChild = node * 2 + 2;
+
+ if (index < mid) {
+ update(index, val, leftChild, lx, mid);
+ } else {
+ update(index, val, rightChild, mid, rx);
+ }
+
+ tree[node] = tree[leftChild] + tree[rightChild];
+ }
+
+ /**
+ * Public wrapper to update a specific index.
+ *
+ * @param index The column index to update.
+ * @param val The new value.
+ */
+ public void update(int index, int val) {
+ update(index, val, 0, 0, n);
+ }
+
+ /**
+ * Retrieves the exact value at a specific leaf node.
+ *
+ * @param index The column index.
+ * @return The value at the given index.
+ */
+ public int get(int index) {
+ return query(index, index + 1, 0, 0, n);
+ }
+
+ /**
+ * Recursively queries the sum in a 1D range.
+ */
+ private int query(int l, int r, int node, int lx, int rx) {
+ if (lx >= r || rx <= l) {
+ return 0; // Out of bounds
+ }
+ if (lx >= l && rx <= r) {
+ return tree[node]; // Fully inside
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftSum = query(l, r, node * 2 + 1, lx, mid);
+ int rightSum = query(l, r, node * 2 + 2, mid, rx);
+
+ return leftSum + rightSum;
+ }
+
+ /**
+ * Public wrapper to query the sum in the range [l, r).
+ *
+ * @param l Left boundary (inclusive).
+ * @param r Right boundary (exclusive).
+ * @return The sum of the range.
+ */
+ public int query(int l, int r) {
+ return query(l, r, 0, 0, n);
+ }
+ }
+
+ // --- Start of 2D Segment Tree (equivalent to 'Sagara2D') ---
+
+ private int n;
+ private final SegmentTree1D[] tree;
+
+ /**
+ * Initializes the 2D Segment Tree.
+ *
+ * @param rows The number of rows in the matrix.
+ * @param cols The number of columns in the matrix.
+ */
+ public SegmentTree2D(int rows, int cols) {
+ n = 1;
+ while (n < rows) {
+ n *= 2;
+ }
+ tree = new SegmentTree1D[n * 2];
+ for (int i = 0; i < n * 2; i++) {
+ // Every node in the outer tree is a full 1D tree!
+ tree[i] = new SegmentTree1D(cols);
+ }
+ }
+
+ /**
+ * Recursively updates a point in the 2D grid.
+ */
+ private void update(int row, int col, int val, int node, int lx, int rx) {
+ if (rx - lx == 1) {
+ tree[node].update(col, val);
+ return;
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftChild = node * 2 + 1;
+ int rightChild = node * 2 + 2;
+
+ if (row < mid) {
+ update(row, col, val, leftChild, lx, mid);
+ } else {
+ update(row, col, val, rightChild, mid, rx);
+ }
+
+ // The value of the current node's column is the sum of its children's column values
+ int leftVal = tree[leftChild].get(col);
+ int rightVal = tree[rightChild].get(col);
+ tree[node].update(col, leftVal + rightVal);
+ }
+
+ /**
+ * Public wrapper to update a specific point (row, col).
+ *
+ * @param row The row index.
+ * @param col The column index.
+ * @param val The new value.
+ */
+ public void update(int row, int col, int val) {
+ update(row, col, val, 0, 0, n);
+ }
+
+ /**
+ * Recursively queries the sum in a submatrix.
+ */
+ private int query(int top, int bottom, int left, int right, int node, int lx, int rx) {
+ if (lx >= bottom || rx <= top) {
+ return 0; // Out of bounds
+ }
+ if (lx >= top && rx <= bottom) {
+ // Fully inside the row range, so delegate the column query to the 1D tree
+ return tree[node].query(left, right);
+ }
+
+ int mid = lx + (rx - lx) / 2;
+ int leftSum = query(top, bottom, left, right, node * 2 + 1, lx, mid);
+ int rightSum = query(top, bottom, left, right, node * 2 + 2, mid, rx);
+
+ return leftSum + rightSum;
+ }
+
+ /**
+ * Public wrapper to query the sum of a submatrix.
+ * Note: boundaries are [top, bottom) and [left, right).
+ *
+ * @param top Top row index (inclusive).
+ * @param bottom Bottom row index (exclusive).
+ * @param left Left column index (inclusive).
+ * @param right Right column index (exclusive).
+ * @return The sum of the submatrix.
+ */
+ public int query(int top, int bottom, int left, int right) {
+ return query(top, bottom, left, right, 0, 0, n);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
new file mode 100644
index 000000000000..fd8876cecb70
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java
@@ -0,0 +1,145 @@
+/*
+ * TheAlgorithms (https://github.com/TheAlgorithms/Java)
+ * Author: Shewale41
+ * This file is licensed under the MIT License.
+ */
+
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Threaded binary tree implementation that supports insertion and
+ * in-order traversal without recursion or stack by using threads.
+ *
+ *
+ *
+ * * @author Varun Upadhyay (https://github.com/varunu28)
*/
public final class Fibonacci {
private Fibonacci() {
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
index 134561766830..0d4c8d501f9f 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java
@@ -3,53 +3,76 @@
import java.util.Arrays;
/**
- * A Dynamic Programming based solution for the 0-1 Knapsack problem.
- * This class provides a method, `knapSack`, that calculates the maximum value that can be
- * obtained from a given set of items with weights and values, while not exceeding a
- * given weight capacity.
+ * 0/1 Knapsack Problem - Dynamic Programming solution.
*
- * @see 0-1 Knapsack Problem
+ * This algorithm solves the classic optimization problem where we have n items,
+ * each with a weight and a value. The goal is to maximize the total value
+ * without exceeding the knapsack's weight capacity.
+ *
+ * Time Complexity: O(n * W)
+ * Space Complexity: O(W)
+ *
+ * Example:
+ * values = {60, 100, 120}
+ * weights = {10, 20, 30}
+ * W = 50
+ * Output: 220
+ *
+ * @author Arpita
+ * @see Knapsack Problem
*/
public final class Knapsack {
private Knapsack() {
}
+ /**
+ * Validates the input to ensure correct constraints.
+ */
private static void throwIfInvalidInput(final int weightCapacity, final int[] weights, final int[] values) {
if (weightCapacity < 0) {
throw new IllegalArgumentException("Weight capacity should not be negative.");
}
if (weights == null || values == null || weights.length != values.length) {
- throw new IllegalArgumentException("Input arrays must not be null and must have the same length.");
+ throw new IllegalArgumentException("Weights and values must be non-null and of the same length.");
}
if (Arrays.stream(weights).anyMatch(w -> w <= 0)) {
- throw new IllegalArgumentException("Input array should not contain non-positive weight(s).");
+ throw new IllegalArgumentException("Weights must be positive.");
}
}
/**
- * Solves the 0-1 Knapsack problem using Dynamic Programming.
+ * Solves the 0/1 Knapsack problem using Dynamic Programming (bottom-up approach).
*
* @param weightCapacity The maximum weight capacity of the knapsack.
- * @param weights An array of item weights.
- * @param values An array of item values.
- * @return The maximum value that can be obtained without exceeding the weight capacity.
- * @throws IllegalArgumentException If the input arrays are null or have different lengths.
+ * @param weights The array of item weights.
+ * @param values The array of item values.
+ * @return The maximum total value achievable without exceeding capacity.
*/
- public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) throws IllegalArgumentException {
+ public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) {
throwIfInvalidInput(weightCapacity, weights, values);
- // DP table to store the state of the maximum possible return for a given weight capacity.
int[] dp = new int[weightCapacity + 1];
+ // Fill dp[] array iteratively
for (int i = 0; i < values.length; i++) {
- for (int w = weightCapacity; w > 0; w--) {
- if (weights[i] <= w) {
- dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
- }
+ for (int w = weightCapacity; w >= weights[i]; w--) {
+ dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[weightCapacity];
}
+
+ /*
+ // Example main method for local testing only.
+ public static void main(String[] args) {
+ int[] values = {60, 100, 120};
+ int[] weights = {10, 20, 30};
+ int weightCapacity = 50;
+
+ int maxValue = knapSack(weightCapacity, weights, values);
+ System.out.println("Maximum value = " + maxValue); // Output: 220
+ }
+ */
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
index 0b40d4559341..5c8a6a953f83 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java
@@ -1,58 +1,42 @@
package com.thealgorithms.dynamicprogramming;
/**
- * Algorithm explanation
- * https://www.educative.io/edpresso/longest-palindromic-subsequence-algorithm
+ * Longest Palindromic Subsequence algorithm.
+ * A palindromic subsequence is a subsequence that reads the same forwards and backwards.
+ * This implementation finds the longest such subsequence by computing the LCS of the
+ * original string and its reverse.
+ *
+ * @see Wikipedia
*/
public final class LongestPalindromicSubsequence {
private LongestPalindromicSubsequence() {
}
- public static void main(String[] args) {
- String a = "BBABCBCAB";
- String b = "BABCBAB";
-
- String aLPS = lps(a);
- String bLPS = lps(b);
-
- System.out.println(a + " => " + aLPS);
- System.out.println(b + " => " + bLPS);
- }
-
- public static String lps(String original) throws IllegalArgumentException {
- StringBuilder reverse = new StringBuilder(original);
- reverse = reverse.reverse();
- return recursiveLPS(original, reverse.toString());
+ /**
+ * Returns the longest palindromic subsequence of the given string.
+ *
+ * @param original the input string
+ * @return the longest palindromic subsequence
+ * @throws IllegalArgumentException if the input string is null
+ */
+ public static String lps(String original) {
+ if (original == null) {
+ throw new IllegalArgumentException("Input string must not be null");
+ }
+ String reverse = new StringBuilder(original).reverse().toString();
+ return recursiveLPS(original, reverse);
}
private static String recursiveLPS(String original, String reverse) {
- String bestResult = "";
-
- // no more chars, then return empty
- if (original.length() == 0 || reverse.length() == 0) {
- bestResult = "";
- } else {
- // if the last chars match, then remove it from both strings and recur
- if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) {
- String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1));
-
- bestResult = reverse.charAt(reverse.length() - 1) + bestSubResult;
- } else {
- // otherwise (1) ignore the last character of reverse, and recur on original and
- // updated reverse again (2) ignore the last character of original and recur on the
- // updated original and reverse again then select the best result from these two
- // subproblems.
-
- String bestSubResult1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1));
- String bestSubResult2 = recursiveLPS(original.substring(0, original.length() - 1), reverse);
- if (bestSubResult1.length() > bestSubResult2.length()) {
- bestResult = bestSubResult1;
- } else {
- bestResult = bestSubResult2;
- }
- }
+ if (original.isEmpty() || reverse.isEmpty()) {
+ return "";
}
-
- return bestResult;
+ if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) {
+ String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1));
+ return reverse.charAt(reverse.length() - 1) + bestSubResult;
+ }
+ String sub1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1));
+ String sub2 = recursiveLPS(original.substring(0, original.length() - 1), reverse);
+ return sub1.length() >= sub2.length() ? sub1 : sub2;
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java
new file mode 100644
index 000000000000..428176ea6c40
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java
@@ -0,0 +1,130 @@
+package com.thealgorithms.dynamicprogramming;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Computes the minimum search cost of an optimal binary search tree.
+ *
+ * > mergeAccounts(List
> accounts) {
+ if (accounts == null || accounts.isEmpty()) {
+ return List.of();
+ }
+
+ UnionFind dsu = new UnionFind(accounts.size());
+ Map
> merged = new ArrayList<>();
+ for (Map.Entry
+ *
+ *
+ * > 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
> adjacencyList;
+ private final List
> adjacencyList, List
+ *
+ */
+ public static long[] extendedGCD(long a, long b) {
+ if (b == 0) {
+ // Base case: gcd(a, 0) = a. The equation is a*1 + 0*0 = a.
+ return new long[] {a, 1, 0};
+ }
+
+ // Recursive call
+ long[] result = extendedGCD(b, a % b);
+ long gcd = result[0];
+ long x1 = result[1];
+ long y1 = result[2];
+
+ // Update coefficients using the results from the recursive call
+ long x = y1;
+ long y = x1 - a / b * y1;
+
+ return new long[] {gcd, x, y};
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/Factorial.java b/src/main/java/com/thealgorithms/maths/Factorial.java
index 511cc1f84f05..8ad219a3066c 100644
--- a/src/main/java/com/thealgorithms/maths/Factorial.java
+++ b/src/main/java/com/thealgorithms/maths/Factorial.java
@@ -1,23 +1,19 @@
package com.thealgorithms.maths;
+import java.math.BigInteger;
+
public final class Factorial {
private Factorial() {
}
- /**
- * Calculate factorial N using iteration
- *
- * @param n the number
- * @return the factorial of {@code n}
- */
- public static long factorial(int n) {
+ public static BigInteger factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("Input number cannot be negative");
}
- long factorial = 1;
- for (int i = 1; i <= n; ++i) {
- factorial *= i;
+ BigInteger result = BigInteger.ONE;
+ for (int i = 1; i <= n; i++) {
+ result = result.multiply(BigInteger.valueOf(i));
}
- return factorial;
+ return result;
}
}
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
index 84390860ccc4..8a93580a4b72 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java
@@ -6,9 +6,23 @@
import java.util.stream.Stream;
/**
- * @author: caos321
- * @date: 14 October 2021 (Thursday)
+ * Calculates Fibonacci numbers using a functional programming paradigm with Java Streams.
+ *
+ *
+ * * @author caos321
+ * @date 14 October 2021 (Thursday)
*/
+
public final class FibonacciJavaStreams {
private FibonacciJavaStreams() {
}
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
index de23a4305c3f..f19e3a6969c5 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java
@@ -1,9 +1,20 @@
package com.thealgorithms.maths;
-
import java.math.BigInteger;
-
/**
* This class provides methods for calculating Fibonacci numbers using BigInteger for large values of 'n'.
+ *
+ *
*/
public final class FibonacciLoop {
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
index 781275d3130d..6bdc2d6ed9fb 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java
@@ -4,6 +4,18 @@
* Fibonacci: 0 1 1 2 3 5 8 13 21 ...
* This code checks Fibonacci Numbers up to 45th number.
* Other checks fail because of 'long'-type overflow.
+ *
+ *
*/
public final class FibonacciNumberCheck {
private FibonacciNumberCheck() {
diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
index 4df37a40f541..eca6379b93bc 100644
--- a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
+++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java
@@ -3,6 +3,18 @@
/**
* This class provides methods for calculating Fibonacci numbers using Binet's formula.
* Binet's formula is based on the golden ratio and allows computing Fibonacci numbers efficiently.
+ *
+ *
*
* @see Binet's formula on Wikipedia
*/
diff --git a/src/main/java/com/thealgorithms/maths/FriendlyNumber.java b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java
new file mode 100644
index 000000000000..900ce89295a4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java
@@ -0,0 +1,49 @@
+package com.thealgorithms.maths;
+
+/**
+ * Two numbers are Friendly if they share the same abundancy index,
+ * which is the ratio of the sum of divisors to the number itself.
+ * Example: 6 and 28 are friendly because sigma(6)/6 = 2 and sigma(28)/28 = 2
+ *
+ * @see
+ * Wikipedia: Friendly Number
+ *
+ * @author Vraj Prajapati @Rosander0
+ */
+public final class FriendlyNumber {
+
+ private FriendlyNumber() {
+ // Utility class
+ }
+
+ private static int sumOfDivisors(final int number) {
+ int sum = 0;
+ final int root = (int) Math.sqrt(number);
+ for (int i = 1; i <= root; i++) {
+ if (number % i == 0) {
+ sum += i;
+ final int other = number / i;
+ if (other != i) {
+ sum += other;
+ }
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * Checks whether two numbers are Friendly Numbers.
+ *
+ * @param a First number (must be positive)
+ * @param b Second number (must be positive)
+ * @return true if a and b are friendly numbers, false otherwise
+ */
+ public static boolean areFriendly(final int a, final int b) {
+ if (a <= 0 || b <= 0) {
+ return false;
+ }
+ final long sigmaA = sumOfDivisors(a);
+ final long sigmaB = sumOfDivisors(b);
+ return sigmaA * b == sigmaB * a;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java
new file mode 100644
index 000000000000..f4f2e23c3932
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.maths;
+// author: Vraj Prajapati @Rosander0
+
+/**
+ * The Jacobsthal Sequence is a sequence of integers defined by the recurrence relation:
+ * J(n) = J(n-1) + 2*J(n-2) with initial values J(0) = 0, J(1) = 1.
+ * Example: 0, 1, 1, 3, 5, 11, 21, 43, 85, 171, 341...
+ *
+ * @see
+ * Wikipedia: Jacobsthal Number
+ */
+public final class JacobsthalNumber {
+
+ private JacobsthalNumber() {
+ // Utility class
+ }
+
+ /**
+ * Calculates the nth term of the Jacobsthal Sequence.
+ *
+ * @param n the index of the sequence (must be non-negative)
+ * @return the nth term of the Jacobsthal Sequence
+ */
+ public static long jacobsthal(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input must be non-negative!");
+ }
+ if (n == 0) {
+ return 0;
+ }
+ if (n == 1) {
+ return 1;
+ }
+ long a = 0;
+ long b = 1;
+ long result = 0;
+ for (int i = 2; i <= n; i++) {
+ result = b + 2 * a;
+ a = b;
+ b = result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
similarity index 94%
rename from src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
rename to src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
index a3ca8d6f6db8..4a79b4298fc4 100644
--- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
+++ b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.maths;
import java.util.ArrayList;
import java.util.List;
@@ -23,6 +23,12 @@
*
* @see OEIS A016026 - Smallest base in which
* n is palindromic
+ * @see com.thealgorithms.strings.Palindrome
+ * @see com.thealgorithms.stacks.PalindromeWithStack
+ * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck
+ * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList
+ * @see com.thealgorithms.maths.PalindromePrime
+ * @see com.thealgorithms.maths.PalindromeNumber
* @author TheAlgorithms Contributors
*/
public final class LowestBasePalindrome {
diff --git a/src/main/java/com/thealgorithms/maths/LuckyNumber.java b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
new file mode 100644
index 000000000000..70308e1e0edd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/LuckyNumber.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.maths;
+
+/**
+ * In number theory, a lucky number is a natural number in a set which is generated by a certain "sieve".
+ * This sieve is similar to the sieve of Eratosthenes that generates the primes,
+ * but it eliminates numbers based on their position in the remaining set,
+ * instead of their value (or position in the initial set of natural numbers).
+ *
+ * Wiki: https://en.wikipedia.org/wiki/Lucky_number
+ */
+public final class LuckyNumber {
+
+ private LuckyNumber() {
+ }
+
+ // Common validation method
+ private static void validatePositiveNumber(int number) {
+ if (number <= 0) {
+ throw new IllegalArgumentException("Number must be positive.");
+ }
+ }
+
+ // Function to check recursively for Lucky Number
+ private static boolean isLuckyRecursiveApproach(int n, int counter) {
+ // Base case: If counter exceeds n, number is lucky
+ if (counter > n) {
+ return true;
+ }
+
+ // If number is eliminated in this step, it's not lucky
+ if (n % counter == 0) {
+ return false;
+ }
+
+ // Calculate new position after removing every counter-th number
+ int newNumber = n - (n / counter);
+
+ // Recursive call for next round
+ return isLuckyRecursiveApproach(newNumber, counter + 1);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using recursive approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLuckyNumber(int number) {
+ validatePositiveNumber(number);
+ int counterStarting = 2;
+ return isLuckyRecursiveApproach(number, counterStarting);
+ }
+
+ /**
+ * Check if {@code number} is a Lucky number or not using iterative approach
+ *
+ * @param number the number
+ * @return {@code true} if {@code number} is a Lucky number, otherwise false
+ */
+ public static boolean isLucky(int number) {
+ validatePositiveNumber(number);
+
+ int counter = 2; // Position starts from 2 (since first elimination happens at 2)
+ int position = number; // The position of the number in the sequence
+
+ while (counter <= position) {
+ if (position % counter == 0) {
+ return false;
+ } // Number is eliminated
+
+ // Update the position of n after removing every counter-th number
+ position = position - (position / counter);
+ counter++;
+ }
+
+ return true; // Survives all eliminations β Lucky Number
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/MathBuilder.java b/src/main/java/com/thealgorithms/maths/MathBuilder.java
index 1cf3d8b7fc9a..08d2ae2c9456 100644
--- a/src/main/java/com/thealgorithms/maths/MathBuilder.java
+++ b/src/main/java/com/thealgorithms/maths/MathBuilder.java
@@ -481,8 +481,8 @@ public Builder closeParenthesisAndDivide() {
}
public Builder format(String format) {
- DecimalFormat formater = new DecimalFormat(format);
- String num = formater.format(number);
+ DecimalFormat formatter = new DecimalFormat(format);
+ String num = formatter.format(number);
number = Double.parseDouble(num);
return this;
}
@@ -490,8 +490,8 @@ public Builder format(String format) {
public Builder format(int decimalPlace) {
String pattern = "."
+ "#".repeat(decimalPlace);
- DecimalFormat formater = new DecimalFormat(pattern);
- String num = formater.format(number);
+ DecimalFormat formatter = new DecimalFormat(pattern);
+ String num = formatter.format(number);
number = Double.parseDouble(num);
return this;
}
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
+ * 1/f = 1/v + 1/u
+ *
+ *
+ * where:
+ *
+ *
+ *
+ * Uses the Cartesian sign convention.
+ *
+ * @see Thin Lens
+ */
+public final class ThinLens {
+
+ private ThinLens() {
+ throw new AssertionError("No instances.");
+ }
+
+ /**
+ * Computes the image distance using the thin lens formula.
+ *
+ * @param focalLength focal length of the lens (f)
+ * @param objectDistance object distance (u)
+ * @return image distance (v)
+ * @throws IllegalArgumentException if focal length or object distance is zero
+ */
+ public static double imageDistance(double focalLength, double objectDistance) {
+
+ if (focalLength == 0 || objectDistance == 0) {
+ throw new IllegalArgumentException("Focal length and object distance must be non-zero.");
+ }
+
+ return 1.0 / ((1.0 / focalLength) - (1.0 / objectDistance));
+ }
+
+ /**
+ * Computes magnification of the image.
+ *
+ *
+ * m = v / u
+ *
+ *
+ * @param imageDistance image distance (v)
+ * @param objectDistance object distance (u)
+ * @return magnification
+ * @throws IllegalArgumentException if object distance is zero
+ */
+ public static double magnification(double imageDistance, double objectDistance) {
+
+ if (objectDistance == 0) {
+ throw new IllegalArgumentException("Object distance must be non-zero.");
+ }
+
+ return imageDistance / objectDistance;
+ }
+
+ /**
+ * Determines whether the image formed is real or virtual.
+ *
+ * @param imageDistance image distance (v)
+ * @return {@code true} if image is real, {@code false} if virtual
+ */
+ public static boolean isRealImage(double imageDistance) {
+ return imageDistance > 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java
new file mode 100644
index 000000000000..1be55039cff0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java
@@ -0,0 +1,87 @@
+package com.thealgorithms.prefixsum;
+
+/**
+ * Implements the Difference Array algorithm.
+ *
+ *
+ *
+ *
+ * prefixSum[right + 1] - prefixSum[left]
+ *
+ *
+ * Space Complexity: O(N)
+ *
+ * prefixSum[i] - prefixSum[j] = k
+ *
+ *
+ * Space Complexity: O(N)
+ *
+ *
+ */
public final class FibonacciSeries {
private FibonacciSeries() {
throw new UnsupportedOperationException("Utility class");
}
+
+ /**
+ * Calculates the nth term in the Fibonacci sequence using recursion.
+ *
+ * @param n the position in the Fibonacci sequence (must be non-negative)
+ * @return the nth Fibonacci number
+ * @throws IllegalArgumentException if n is negative
+ */
public static int fibonacci(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be a non-negative integer");
+ }
if (n <= 1) {
return n;
- } else {
- return fibonacci(n - 1) + fibonacci(n - 2);
}
+ return fibonacci(n - 1) + fibonacci(n - 2);
}
}
diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java
index bedad1667f33..ca873fc6eafa 100644
--- a/src/main/java/com/thealgorithms/searches/BinarySearch.java
+++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java
@@ -3,12 +3,32 @@
import com.thealgorithms.devutils.searches.SearchAlgorithm;
/**
- * Binary search is one of the most popular algorithms The algorithm finds the
- * position of a target value within a sorted array
+ * Binary Search Algorithm Implementation
*
- *
+ * Integer[] numbers = {1, 3, 5, 7, 9, 11};
+ * int result = new BinarySearch().find(numbers, 7);
+ * // result will be 3 (index of element 7)
+ *
+ * int notFound = new BinarySearch().find(numbers, 4);
+ * // notFound will be -1 (element 4 does not exist)
+ *
+ *
+ * @param
+ *
+ *
+ *
+ *
+ *
+ * @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
+ *
*
*
- * 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
+ *
+ *
+ * - 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
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Input array: [5, 3, 8, 1, 9]
+ * Target: 8
+ *
+ * Step 1: Compare 5 with 8 β no match, move on
+ * Step 2: Compare 3 with 8 β no match, move on
+ * Step 3: Compare 8 with 8 β match found at index 2!
+ *
+ * Output: 2
*
- *
+ * Time Complexity:
+ * - Best case: O(1) - target is the first element
+ * - Average case: O(n) - target is somewhere in the middle
+ * - Worst case: O(n) - target is last or not present
*
- * @author Varun Upadhyay (https://github.com/varunu28)
- * @author Podshivalov Nikita (https://github.com/nikitap492)
+ * Space Complexity: O(1)
+ *
+ * @author Varun Upadhyay
+ * @author Podshivalov Nikita
* @see BinarySearch
* @see SearchAlgorithm
*/
public class LinearSearch implements SearchAlgorithm {
/**
- * Generic Linear search method
+ * Generic Linear search method that searches for a value
+ * in the given array by checking each element one by one.
*
- * @param array List to be searched
+ * @param array List to be searched (can be unsorted)
* @param value Key being searched for
- * @return Location of the key
+ * @return the index of the matching element, or -1 if the array is null or empty,
+ * the value is null, or no matching element is found; null array elements are skipped
*/
@Override
public
+ * Best-case performance: O(1)
+ * Average performance: Ξ(log3(N))
+ * Worst-case space complexity: O(log3(N))
*
* @author Podshivalov Nikita (https://github.com/nikitap492)
* @see SearchAlgorithm
* @see IterativeBinarySearch
*/
-public class TernarySearch implements SearchAlgorithm {
+public final class TernarySearch implements SearchAlgorithm {
/**
- * @param arr The **Sorted** array in which we will search the element.
+ * Searches for a value in a sorted array using ternary search.
+ *
+ * @param arr The sorted array in which we will search for the element.
* @param value The value that we want to search for.
- * @return The index of the element if found. Else returns -1.
+ * @return The index of the element if found. Returns -1 if the array is null or empty, if value is null, or if not found.
*/
@Override
public
+ *
+ */
+public final class ConcurrentMergeSort {
+
+ private ConcurrentMergeSort() {
+ }
+
+ /**
+ * Fallback threshold where the algorithm switches to standard sequential
+ * Merge Sort to prevent thread-creation overhead from ruining performance.
+ */
+ private static final int SEQUENTIAL_THRESHOLD = 8192;
+
+ /**
+ * Sorts the specified array of integers concurrently using Merge Sort.
+ *
+ * @param array the array to be sorted
+ */
+ public static void sort(int[] array) {
+ if (array == null || array.length <= 1) {
+ return;
+ }
+
+ int availableProcessors = Runtime.getRuntime().availableProcessors();
+
+ // Calculate a safe maximum depth to prevent creating more tasks than the pool can handle.
+ // This effectively prevents thread starvation deadlock in fixed-size thread pools,
+ // by forcing leaf tasks to run sequentially and eventually complete.
+ int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1;
+
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * m = 1 / (n0 - minLength + 1) + 1 / (n1 - minLength + 1)
+ * d = ln( 2 * ln(width) / delta )
+ * epsilon = sqrt(2 * m * v * d) + 2/3 * d * m
+ * cut when |mean0 - mean1| > epsilon
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * Adwin window = new Adwin(0.002);
+ * for (double sample : stream) {
+ * if (window.accept(sample)) {
+ * alert(window.estimate(), window.width());
+ * }
+ * }
+ * }
+ *
+ *
+ * value <- a * (value + rate * dt) + (1 - a) * reference
+ *
+ *
+ *
+ * tau = a * dt / (1 - a)
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * ComplementaryFilter tilt = ComplementaryFilter.ofTimeConstant(0.5, 0.01);
+ * for (Reading reading : imu) {
+ * double angle = tilt.accept(reading.gyroscopeRate(), reading.accelerometerAngle(), reading.dt());
+ * }
+ * }
+ *
+ *
+ * upper <- max(0, upper + z - allowance)
+ * lower <- max(0, lower - z - allowance)
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * // Watch for a shift of one standard deviation around a target of 20.0.
+ * CusumDetector detector = new CusumDetector(20.0, 0.5, 0.5, 5.0);
+ * for (double sample : stream) {
+ * ShiftSignal signal = detector.accept(sample);
+ * if (signal.isAlarm()) {
+ * alert(signal, detector.count());
+ * }
+ * }
+ * }
+ *
+ *
+ * D[i][j] = |a[i] - b[j]| + min( D[i-1][j], D[i][j-1], D[i-1][j-1] )
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * double distance = DynamicTimeWarping.distance(query, candidate);
+ * double banded = DynamicTimeWarping.distance(query, candidate, 10);
+ * int[][] alignment = DynamicTimeWarping.path(query, candidate);
+ * }
+ *
+ *
+ * z <- z + alpha * (x - target)
+ * limit = width * sigma * sqrt( alpha / (2 - alpha) * (1 - (1 - alpha)^(2n)) )
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * EwmaChangeDetector detector = new EwmaChangeDetector(20.0, 0.5, 0.2, 3.0);
+ * for (double sample : stream) {
+ * if (detector.accept(sample).isAlarm()) {
+ * alert(detector.statistic(), detector.controlLimit());
+ * }
+ * }
+ * }
+ *
+ *
+ * mean <- mean + alpha * (x - mean)
+ * variance <- (1 - alpha) * (variance + alpha * (x - mean_before)^2)
+ *
+ *
+ *
+ *
+ *
+ *
+ * Factory alpha Meaning
+ * {@link #ofAlpha(double)} as given direct control
+ * {@link #ofSpan(double)} {@code 2 / (span + 1)} comparable to a simple moving average of {@code span} samples
+ * {@link #ofHalfLife(double)} {@code 1 - exp(-ln2 / halfLife)} a sample loses half of its weight after {@code halfLife} steps
+ * predict: x <- f(x) P <- F P F' + Q, F = df/dx at x
+ * update: y = z - h(x) S = H P H' + R, H = dh/dx at x
+ * K = P H' S^-1 x <- x + K y
+ * P <- (I - K H) P (I - K H)' + K R K'
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * // a target moving at constant velocity, watched by a sensor that only measures range
+ * ExtendedKalmanFilter filter = new ExtendedKalmanFilter(new double[] {0.0, 1.0}, identity);
+ * filter.predict(new double[][] {{1.0, dt}, {0.0, 1.0}}, processNoise);
+ * filter.update(new double[] {range},
+ * state -> new double[] {Math.hypot(state[0], offset)},
+ * state -> new double[][] {{state[0] / Math.hypot(state[0], offset), 0.0}},
+ * measurementNoise);
+ * }
+ *
+ *
+ * R_i = max |x - mean| / sd over the points that are still in
+ * lambda_i = (n - i) * t / sqrt( (n - i - 1 + t^2)(n - i + 1) )
+ * where t = the 1 - alpha / (2(n - i + 1)) quantile of a t distribution with n - i - 1 degrees of freedom
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * GeneralizedEsd test = new GeneralizedEsd(0.05);
+ * int[] outliers = test.findOutliers(sample, 10);
+ * }
+ *
+ *
+ *
+ *
+ *
+ * |x - median| > threshold * 1.4826 * MAD
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * HampelFilter filter = new HampelFilter(7, 3.0);
+ * for (double sample : sensorReadings) {
+ * double clean = filter.accept(sample);
+ * if (filter.lastWasOutlier()) {
+ * log.warn("spike of {} replaced by {}", sample, clean);
+ * }
+ * }
+ * }
+ *
+ *
+ * predict: x <- x + u p <- p + q
+ * update: k <- p / (p + r) x <- x + k * (z - x) p <- (1 - k) * p
+ *
+ *
+ * Sensor fusion
+ *
+ * {@code
+ * KalmanFilter filter = new KalmanFilter(startingHeight, 1.0, 0.01, 1.0);
+ * for (int t = 0; t < steps; t++) {
+ * filter.predict();
+ * filter.update(barometer[t], barometerVariance);
+ * filter.update(gps[t], gpsVariance); // second sensor, same estimate
+ * double height = filter.estimate();
+ * }
+ * }
+ *
+ * Usage
+ *
+ * {@code
+ * MedianFilter filter = new MedianFilter(5);
+ * for (double sample : signal) {
+ * double clean = filter.accept(sample);
+ * }
+ * }
+ *
+ *
+ *
+ *
+ *
+ * Operation Complexity
+ * {@link #add(double)} O(1)
+ * {@link #quantile()} O(1)
+ * memory O(1) - five markers, independent of the stream length Usage
+ *
+ * {@code
+ * P2QuantileEstimator p99 = new P2QuantileEstimator(0.99);
+ * for (double latency : latencies) {
+ * p99.add(latency);
+ * }
+ * double tail = p99.quantile();
+ * }
+ *
+ *
+ * k(q) = compression / (2 * pi) * asin(2q - 1)
+ *
+ *
+ *
+ *
+ *
+ *
+ * Operation Complexity
+ * {@link #add(double)} O(log d) amortised
+ * {@link #quantile(double)}, {@link #cdf(double)} O(d)
+ * {@link #merge(TDigest)} O(d log d)
+ * memory O(d), independent of the stream length Usage
+ *
+ * {@code
+ * TDigest digest = new TDigest(100);
+ * for (double latency : latencies) {
+ * digest.add(latency);
+ * }
+ * digest.quantile(0.5); // median
+ * digest.quantile(0.999); // deep tail, the case t-digest is built for
+ * digest.cdf(250.0); // share of samples below 250
+ *
+ * // Sketches computed on different shards combine the way partial sums do.
+ * shardA.merge(shardB);
+ * }
+ *
+ *
+ * lambda = alpha^2 (n + kappa) - n
+ * X_0 = x, X_i = x + [sqrt((n + lambda) P)]_i, X_{i+n} = x - [sqrt((n + lambda) P)]_i
+ * Wm_0 = lambda / (n + lambda), Wc_0 = Wm_0 + 1 - alpha^2 + beta, W_i = 1 / (2 (n + lambda))
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * UnscentedKalmanFilter filter = new UnscentedKalmanFilter(new double[] {0.0, 1.0}, identity);
+ * filter.predict(state -> new double[] {state[0] + dt * state[1], state[1]}, processNoise);
+ * filter.update(new double[] {range}, state -> new double[] {Math.hypot(state[0], offset)}, measurementNoise);
+ * }
+ *
+ *
+ * n <- n + 1
+ * delta <- x - mean
+ * mean <- mean + delta / n
+ * M2 <- M2 + delta * (x - mean) // note: the second factor uses the *updated* mean
+ *
+ *
+ * Sliding windows and map-reduce
+ *
+ *
+ *
+ *
+ * Usage
+ *
+ * {@code
+ * WelfordAlgorithm stats = new WelfordAlgorithm();
+ * stats.add(2.0);
+ * stats.add(4.0);
+ * stats.add(4.0);
+ * stats.mean(); // 3.3333...
+ * stats.populationStandardDeviation(); // 0.9428...
+ * }
+ *
+ *
+ *
*
- * @param s the input string
- * @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false}
+ * @param s input string
+ * @return {@code true} if characters are in non-decreasing order, otherwise {@code false}
*/
public static boolean isAlphabetical(String s) {
- s = s.toLowerCase();
- for (int i = 0; i < s.length() - 1; ++i) {
- if (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) {
+ if (s == null || s.isBlank()) {
+ return false;
+ }
+
+ String normalized = s.toLowerCase(Locale.ROOT);
+
+ if (!Character.isLetter(normalized.charAt(0))) {
+ return false;
+ }
+
+ for (int i = 1; i < normalized.length(); i++) {
+ char prev = normalized.charAt(i - 1);
+ char curr = normalized.charAt(i);
+
+ if (!Character.isLetter(curr) || prev > curr) {
return false;
}
}
- return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1));
+ return true;
}
}
diff --git a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
index cf736dbd8cab..016ee2821a17 100644
--- a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
+++ b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java
@@ -21,12 +21,19 @@ private AlternativeStringArrange() {
/**
* Arranges two strings by alternating their characters.
+ * If one string is longer than the other, the remaining characters of the longer string
+ * are appended at the end of the result.
*
- * @param firstString the first input string
- * @param secondString the second input string
+ * @param firstString the first input string, must not be {@code null}
+ * @param secondString the second input string, must not be {@code null}
* @return a new string with characters from both strings arranged alternately
+ * @throws IllegalArgumentException if {@code firstString} or {@code secondString} is {@code null}
*/
public static String arrange(String firstString, String secondString) {
+ if (firstString == null || secondString == null) {
+ throw new IllegalArgumentException("Input strings must not be null");
+ }
+
StringBuilder result = new StringBuilder();
int length1 = firstString.length();
int length2 = secondString.length();
diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java
index 5b97af0758f2..7bd84d47508f 100644
--- a/src/main/java/com/thealgorithms/strings/Anagrams.java
+++ b/src/main/java/com/thealgorithms/strings/Anagrams.java
@@ -5,7 +5,7 @@
/**
* An anagram is a word or phrase formed by rearranging the letters of a different word or phrase,
- * typically using all the original letters exactly once.[1]
+ * typically using all the original letters exactly once.
* For example, the word anagram itself can be rearranged into nag a ram,
* also the word binary into brainy and the word adobe into abode.
* Reference from https://en.wikipedia.org/wiki/Anagram
diff --git a/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java b/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java
new file mode 100644
index 000000000000..01c2f67a8569
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/BoothsAlgorithm.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.strings;
+
+/**
+ * Finds the lexicographically smallest cyclic rotation of a string using
+ * Booth's algorithm.
+ *
+ * {@code
+ * LengthOfLastWord obj = new LengthOfLastWord();
+ * System.out.println(obj.lengthOfLastWord("Hello World")); // Output: 5
+ * System.out.println(obj.lengthOfLastWord(" fly me to the moon ")); // Output: 4
+ * System.out.println(obj.lengthOfLastWord("luffy is still joyboy")); // Output: 6
+ * }
+ *
+ *
- *
- *
- * Allowed characters: '(', ')', '{', '}', '[', ']'
- */
-public final class ValidParentheses {
- private ValidParentheses() {
- }
-
- private static final Map