true
true
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml
index bfc7716730c3..3b77ced1a13e 100644
--- a/spotbugs-exclude.xml
+++ b/spotbugs-exclude.xml
@@ -41,9 +41,6 @@
-
-
-
@@ -86,10 +83,10 @@
-
-
+
+
@@ -120,9 +117,6 @@
-
-
-
diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java
new file mode 100644
index 000000000000..0dd23e937953
--- /dev/null
+++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.audiofilters;
+
+/**
+ * Exponential Moving Average (EMA) Filter for smoothing audio signals.
+ *
+ * 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
+ * Wikipedia link.
+ */
+public class EMAFilter {
+ private final double alpha;
+ private double emaValue;
+ /**
+ * Constructs an EMA filter with a given smoothing factor.
+ *
+ * @param alpha Smoothing factor (0 < alpha <= 1)
+ * @throws IllegalArgumentException if alpha is not in (0, 1]
+ */
+ public EMAFilter(double alpha) {
+ if (alpha <= 0 || alpha > 1) {
+ throw new IllegalArgumentException("Alpha must be between 0 and 1.");
+ }
+ this.alpha = alpha;
+ this.emaValue = 0.0;
+ }
+ /**
+ * Applies the EMA filter to an audio signal array.
+ *
+ * @param audioSignal Array of audio samples to process
+ * @return Array of processed (smoothed) samples
+ */
+ public double[] apply(double[] audioSignal) {
+ if (audioSignal.length == 0) {
+ return new double[0];
+ }
+ double[] emaSignal = new double[audioSignal.length];
+ emaValue = audioSignal[0];
+ emaSignal[0] = emaValue;
+ for (int i = 1; i < audioSignal.length; i++) {
+ emaValue = alpha * audioSignal[i] + (1 - alpha) * emaValue;
+ emaSignal[i] = emaValue;
+ }
+ return emaSignal;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
index a064decc0eb7..f8cd0c40c20e 100644
--- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
+++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java
@@ -4,22 +4,24 @@
import java.util.List;
/**
- * Finds all combinations of 0...n-1 of length k
+ * This class provides methods to find all combinations of integers from 0 to n-1
+ * of a specified length k using backtracking.
*/
public final class ArrayCombination {
private ArrayCombination() {
}
/**
- * Finds all combinations of length k of 0..n-1 using backtracking.
+ * Generates all possible combinations of length k from the integers 0 to n-1.
*
- * @param n Number of the elements.
- * @param k Length of the combination.
- * @return A list of all combinations of length k.
+ * @param n The total number of elements (0 to n-1).
+ * @param k The desired length of each combination.
+ * @return A list containing all combinations of length k.
+ * @throws IllegalArgumentException if n or k are negative, or if k is greater than n.
*/
public static List> combination(int n, int k) {
if (n < 0 || k < 0 || k > n) {
- throw new IllegalArgumentException("Wrong input.");
+ throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n.");
}
List> combinations = new ArrayList<>();
@@ -27,16 +29,26 @@ public static List> combination(int n, int k) {
return combinations;
}
+ /**
+ * A helper method that uses backtracking to find combinations.
+ *
+ * @param combinations The list to store all valid combinations found.
+ * @param current The current combination being built.
+ * @param start The starting index for the current recursion.
+ * @param n The total number of elements (0 to n-1).
+ * @param k The desired length of each combination.
+ */
private static void combine(List> combinations, List current, int start, int n, int k) {
- if (current.size() == k) { // Base case: combination found
- combinations.add(new ArrayList<>(current)); // Copy to avoid modification
+ // Base case: combination found
+ if (current.size() == k) {
+ combinations.add(new ArrayList<>(current));
return;
}
for (int i = start; i < n; i++) {
current.add(i);
combine(combinations, current, i + 1, n, k);
- current.removeLast(); // Backtrack
+ current.remove(current.size() - 1); // Backtrack
}
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/Combination.java b/src/main/java/com/thealgorithms/backtracking/Combination.java
index bf2a672a0ef8..ecaf7428f986 100644
--- a/src/main/java/com/thealgorithms/backtracking/Combination.java
+++ b/src/main/java/com/thealgorithms/backtracking/Combination.java
@@ -1,6 +1,7 @@
package com.thealgorithms.backtracking;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
@@ -13,8 +14,6 @@ public final class Combination {
private Combination() {
}
- private static int length;
-
/**
* Find all combinations of given array using backtracking
* @param arr the array.
@@ -23,39 +22,45 @@ private Combination() {
* @return a list of all combinations of length n. If n == 0, return null.
*/
public static List> combination(T[] arr, int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("The combination length cannot be negative.");
+ }
+
if (n == 0) {
- return null;
+ return Collections.emptyList();
}
- length = n;
T[] array = arr.clone();
Arrays.sort(array);
+
List> result = new LinkedList<>();
- backtracking(array, 0, new TreeSet(), result);
+ backtracking(array, n, 0, new TreeSet(), result);
return result;
}
/**
* Backtrack all possible combinations of a given array
* @param arr the array.
+ * @param n length of the combination
* @param index the starting index.
* @param currSet set that tracks current combination
* @param result the list contains all combination.
* @param the type of elements in the array.
*/
- private static void backtracking(T[] arr, int index, TreeSet currSet, List> result) {
- if (index + length - currSet.size() > arr.length) {
+ private static void backtracking(T[] arr, int n, int index, TreeSet currSet, List> result) {
+ if (index + n - currSet.size() > arr.length) {
return;
}
- if (length - 1 == currSet.size()) {
+ if (currSet.size() == n - 1) {
for (int i = index; i < arr.length; i++) {
currSet.add(arr[i]);
- result.add((TreeSet) currSet.clone());
+ result.add(new TreeSet<>(currSet));
currSet.remove(arr[i]);
}
+ return;
}
for (int i = index; i < arr.length; i++) {
currSet.add(arr[i]);
- backtracking(arr, i + 1, currSet, result);
+ backtracking(arr, n, i + 1, currSet, result);
currSet.remove(arr[i]);
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java
new file mode 100644
index 000000000000..6bfb026c7de9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java
@@ -0,0 +1,125 @@
+package com.thealgorithms.backtracking;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A class to solve a crossword puzzle using backtracking.
+ * Example:
+ * Input:
+ * puzzle = {
+ * {' ', ' ', ' '},
+ * {' ', ' ', ' '},
+ * {' ', ' ', ' '}
+ * }
+ * words = List.of("cat", "dog")
+ *
+ * Output:
+ * {
+ * {'c', 'a', 't'},
+ * {' ', ' ', ' '},
+ * {'d', 'o', 'g'}
+ * }
+ */
+public final class CrosswordSolver {
+ private CrosswordSolver() {
+ }
+
+ /**
+ * Checks if a word can be placed at the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be placed.
+ * @param row The row index where the word might be placed.
+ * @param col The column index where the word might be placed.
+ * @param vertical If true, the word is placed vertically; otherwise, horizontally.
+ * @return true if the word can be placed, false otherwise.
+ */
+ public static boolean isValid(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ if (row + i >= puzzle.length || puzzle[row + i][col] != ' ') {
+ return false;
+ }
+ } else {
+ if (col + i >= puzzle[0].length || puzzle[row][col + i] != ' ') {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Places a word at the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be placed.
+ * @param row The row index where the word will be placed.
+ * @param col The column index where the word will be placed.
+ * @param vertical If true, the word is placed vertically; otherwise, horizontally.
+ */
+ public static void placeWord(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ puzzle[row + i][col] = word.charAt(i);
+ } else {
+ puzzle[row][col + i] = word.charAt(i);
+ }
+ }
+ }
+
+ /**
+ * Removes a word from the specified position in the crossword.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param word The word to be removed.
+ * @param row The row index where the word is placed.
+ * @param col The column index where the word is placed.
+ * @param vertical If true, the word was placed vertically; otherwise, horizontally.
+ */
+ public static void removeWord(char[][] puzzle, String word, int row, int col, boolean vertical) {
+ for (int i = 0; i < word.length(); i++) {
+ if (vertical) {
+ puzzle[row + i][col] = ' ';
+ } else {
+ puzzle[row][col + i] = ' ';
+ }
+ }
+ }
+
+ /**
+ * Solves the crossword puzzle using backtracking.
+ *
+ * @param puzzle The crossword puzzle represented as a 2D char array.
+ * @param words The list of words to be placed.
+ * @return true if the crossword is solved, false otherwise.
+ */
+ public static boolean solveCrossword(char[][] puzzle, Collection words) {
+ // Create a mutable copy of the words list
+ List remainingWords = new ArrayList<>(words);
+
+ for (int row = 0; row < puzzle.length; row++) {
+ for (int col = 0; col < puzzle[0].length; col++) {
+ if (puzzle[row][col] == ' ') {
+ for (String word : new ArrayList<>(remainingWords)) {
+ for (boolean vertical : new boolean[] {true, false}) {
+ if (isValid(puzzle, word, row, col, vertical)) {
+ placeWord(puzzle, word, row, col, vertical);
+ remainingWords.remove(word);
+ if (solveCrossword(puzzle, remainingWords)) {
+ return true;
+ }
+ remainingWords.add(word);
+ removeWord(puzzle, word, row, col, vertical);
+ }
+ }
+ }
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
index f7eae01e449a..8247172e7ee0 100644
--- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
+++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
@@ -1,152 +1,125 @@
package com.thealgorithms.backtracking;
+/**
+ * This class contains methods to solve a maze using recursive backtracking.
+ * The maze is represented as a 2D array where walls, paths, and visited/dead
+ * ends are marked with different integers.
+ *
+ * The goal is to find a path from a starting position to the target position
+ * (map[6][5]) while navigating through the maze.
+ */
public final class MazeRecursion {
+
private MazeRecursion() {
}
- public static void mazeRecursion() {
- // First create a 2 dimensions array to mimic a maze map
- int[][] map = new int[8][7];
- int[][] map2 = new int[8][7];
-
- // We use 1 to indicate wall
- // Set the ceiling and floor to 1
- for (int i = 0; i < 7; i++) {
- map[0][i] = 1;
- map[7][i] = 1;
- }
-
- // Then we set the left and right wall to 1
- for (int i = 0; i < 8; i++) {
- map[i][0] = 1;
- map[i][6] = 1;
- }
-
- // Now we have created a maze with its wall initialized
-
- // Here we set the obstacle
- map[3][1] = 1;
- map[3][2] = 1;
-
- // Print the current map
- System.out.println("The condition of the map: ");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
- }
-
- // clone another map for setWay2 method
- for (int i = 0; i < map.length; i++) {
- System.arraycopy(map[i], 0, map2[i], 0, map[i].length);
- }
-
- // By using recursive backtracking to let your ball(target) find its way in the
- // maze
- // The first parameter is the map
- // Second parameter is x coordinate of your target
- // Third parameter is the y coordinate of your target
- setWay(map, 1, 1);
- setWay2(map2, 1, 1);
-
- // Print out the new map1, with the ball footprint
- System.out.println("After the ball goes through the map1,show the current map1 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "down -> right -> up -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
+ if (setWay(map, 1, 1)) {
+ return map;
}
+ return null;
+ }
- // Print out the new map2, with the ball footprint
- System.out.println("After the ball goes through the map2,show the current map2 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map2[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "up -> right -> down -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingSecondStrategy(int[][] map) {
+ if (setWay2(map, 1, 1)) {
+ return map;
}
+ return null;
}
/**
- * Using recursive path finding to help the ball find its way in the maze
- * Description:
- * 1. map (means the maze)
- * 2. i, j (means the initial coordinate of the ball in the maze)
- * 3. if the ball can reach the end of maze, that is position of map[6][5],
- * means the we have found a path for the ball
- * 4. Additional Information: 0 in the map[i][j] means the ball has not gone
- * through this position, 1 means the wall, 2 means the path is feasible, 3
- * means the ball has gone through the path but this path is dead end
- * 5. We will need strategy for the ball to pass through the maze for example:
- * Down -> Right -> Up -> Left, if the path doesn't work, then backtrack
+ * Attempts to find a path through the maze using a "down -> right -> up -> left"
+ * movement strategy. The path is marked with '2' for valid paths and '3' for dead ends.
*
- * @author OngLipWei
- * @version Jun 23, 2021 11:36:14 AM
- * @param map The maze
- * @param i x coordinate of your ball(target)
- * @param j y coordinate of your ball(target)
- * @return If we did find a path for the ball,return true,else false
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
*/
- public static boolean setWay(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ private static boolean setWay(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : down -> right -> up -> left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay(map, i + 1, j)) { // go down
+
+ // If the current position is unvisited (0), explore it
+ if (map[i][j] == 0) {
+ // Mark the current position as '2'
+ map[i][j] = 2;
+
+ // Move down
+ if (setWay(map, i + 1, j)) {
return true;
- } else if (setWay(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay(map, i, j + 1)) {
return true;
- } else if (setWay(map, i - 1, j)) { // go up
+ }
+ // Move up
+ else if (setWay(map, i - 1, j)) {
return true;
- } else if (setWay(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone though before,
- // and cannot head to deadened.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
- // Here is another move strategy for the ball: up->right->down->left
- public static boolean setWay2(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ /**
+ * Attempts to find a path through the maze using an alternative movement
+ * strategy "up -> right -> down -> left".
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
+ */
+ private static boolean setWay2(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : up->right->down->left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay2(map, i - 1, j)) { // go up
+
+ if (map[i][j] == 0) {
+ map[i][j] = 2;
+
+ // Move up
+ if (setWay2(map, i - 1, j)) {
return true;
- } else if (setWay2(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay2(map, i, j + 1)) {
return true;
- } else if (setWay2(map, i + 1, j)) { // go down
+ }
+ // Move down
+ else if (setWay2(map, i + 1, j)) {
return true;
- } else if (setWay2(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay2(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone through before,
- // and cannot head to deadend.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/PowerSum.java b/src/main/java/com/thealgorithms/backtracking/PowerSum.java
index 6617ea326a1c..b34ba660ebd7 100644
--- a/src/main/java/com/thealgorithms/backtracking/PowerSum.java
+++ b/src/main/java/com/thealgorithms/backtracking/PowerSum.java
@@ -1,45 +1,51 @@
package com.thealgorithms.backtracking;
-/*
- * Problem Statement :
- * Find the number of ways that a given integer, N , can be expressed as the sum of the Xth powers
- * of unique, natural numbers. For example, if N=100 and X=3, we have to find all combinations of
- * unique cubes adding up to 100. The only solution is 1^3+2^3+3^3+4^3. Therefore output will be 1.
+/**
+ * Problem Statement:
+ * Find the number of ways that a given integer, N, can be expressed as the sum of the Xth powers
+ * of unique, natural numbers.
+ * For example, if N=100 and X=3, we have to find all combinations of unique cubes adding up to 100.
+ * The only solution is 1^3 + 2^3 + 3^3 + 4^3. Therefore, the output will be 1.
+ *
+ * N is represented by the parameter 'targetSum' in the code.
+ * X is represented by the parameter 'power' in the code.
*/
public class PowerSum {
- private int count = 0;
- private int sum = 0;
-
- public int powSum(int n, int x) {
- sum(n, x, 1);
- return count;
+ /**
+ * Calculates the number of ways to express the target sum as a sum of Xth powers of unique natural numbers.
+ *
+ * @param targetSum The target sum to achieve (N in the problem statement)
+ * @param power The power to raise natural numbers to (X in the problem statement)
+ * @return The number of ways to express the target sum
+ */
+ public int powSum(int targetSum, int power) {
+ // Special case: when both targetSum and power are zero
+ if (targetSum == 0 && power == 0) {
+ return 1; // by convention, one way to sum to zero: use nothing
+ }
+ return sumRecursive(targetSum, power, 1, 0);
}
- // here i is the natural number which will be raised by X and added in sum.
- public void sum(int n, int x, int i) {
- // if sum is equal to N that is one of our answer and count is increased.
- if (sum == n) {
- count++;
- return;
- } // we will be adding next natural number raised to X only if on adding it in sum the
- // result is less than N.
- else if (sum + power(i, x) <= n) {
- sum += power(i, x);
- sum(n, x, i + 1);
- // backtracking and removing the number added last since no possible combination is
- // there with it.
- sum -= power(i, x);
+ /**
+ * Recursively calculates the number of ways to express the remaining sum as a sum of Xth powers.
+ *
+ * @param remainingSum The remaining sum to achieve
+ * @param power The power to raise natural numbers to (X in the problem statement)
+ * @param currentNumber The current natural number being considered
+ * @param currentSum The current sum of powered numbers
+ * @return The number of valid combinations
+ */
+ private int sumRecursive(int remainingSum, int power, int currentNumber, int currentSum) {
+ int newSum = currentSum + (int) Math.pow(currentNumber, power);
+
+ if (newSum == remainingSum) {
+ return 1;
}
- if (power(i, x) < n) {
- // calling the sum function with next natural number after backtracking if when it is
- // raised to X is still less than X.
- sum(n, x, i + 1);
+ if (newSum > remainingSum) {
+ return 0;
}
- }
- // creating a separate power function so that it can be used again and again when required.
- private int power(int a, int b) {
- return (int) Math.pow(a, b);
+ return sumRecursive(remainingSum, power, currentNumber + 1, newSum) + sumRecursive(remainingSum, power, currentNumber + 1, currentSum);
}
}
diff --git a/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java
new file mode 100644
index 000000000000..1854cab20a7f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java
@@ -0,0 +1,86 @@
+package com.thealgorithms.backtracking;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class to determine if a pattern matches a string using backtracking.
+ *
+ * Example:
+ * Pattern: "abab"
+ * Input String: "JavaPythonJavaPython"
+ * Output: true
+ *
+ * Pattern: "aaaa"
+ * Input String: "JavaJavaJavaJava"
+ * Output: true
+ *
+ * Pattern: "aabb"
+ * Input String: "JavaPythonPythonJava"
+ * Output: false
+ */
+public final class WordPatternMatcher {
+ private WordPatternMatcher() {
+ }
+
+ /**
+ * Determines if the given pattern matches the input string using backtracking.
+ *
+ * @param pattern The pattern to match.
+ * @param inputString The string to match against the pattern.
+ * @return True if the pattern matches the string, False otherwise.
+ */
+ public static boolean matchWordPattern(String pattern, String inputString) {
+ Map patternMap = new HashMap<>();
+ Map strMap = new HashMap<>();
+ return backtrack(pattern, inputString, 0, 0, patternMap, strMap);
+ }
+
+ /**
+ * Backtracking helper function to check if the pattern matches the string.
+ *
+ * @param pattern The pattern string.
+ * @param inputString The string to match against the pattern.
+ * @param patternIndex Current index in the pattern.
+ * @param strIndex Current index in the input string.
+ * @param patternMap Map to store pattern characters to string mappings.
+ * @param strMap Map to store string to pattern character mappings.
+ * @return True if the pattern matches, False otherwise.
+ */
+ private static boolean backtrack(String pattern, String inputString, int patternIndex, int strIndex, Map patternMap, Map strMap) {
+ if (patternIndex == pattern.length() && strIndex == inputString.length()) {
+ return true;
+ }
+ if (patternIndex == pattern.length() || strIndex == inputString.length()) {
+ return false;
+ }
+
+ char currentChar = pattern.charAt(patternIndex);
+ if (patternMap.containsKey(currentChar)) {
+ String mappedStr = patternMap.get(currentChar);
+ if (inputString.startsWith(mappedStr, strIndex)) {
+ return backtrack(pattern, inputString, patternIndex + 1, strIndex + mappedStr.length(), patternMap, strMap);
+ } else {
+ return false;
+ }
+ }
+
+ for (int end = strIndex + 1; end <= inputString.length(); end++) {
+ String substring = inputString.substring(strIndex, end);
+ if (strMap.containsKey(substring)) {
+ continue;
+ }
+
+ patternMap.put(currentChar, substring);
+ strMap.put(substring, currentChar);
+ if (backtrack(pattern, inputString, patternIndex + 1, end, patternMap, strMap)) {
+ return true;
+ }
+
+ patternMap.remove(currentChar);
+ strMap.remove(substring);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java
new file mode 100644
index 000000000000..e6bd35720d9f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java
@@ -0,0 +1,82 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides methods to convert between BCD (Binary-Coded Decimal) and decimal numbers.
+ *
+ * BCD is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of binary digits, usually four or eight.
+ *
+ * For more information, refer to the
+ * Binary-Coded Decimal Wikipedia page.
+ *
+ * Example usage:
+ *
+ * int decimal = BcdConversion.bcdToDecimal(0x1234);
+ * System.out.println("BCD 0x1234 to decimal: " + decimal); // Output: 1234
+ *
+ * int bcd = BcdConversion.decimalToBcd(1234);
+ * System.out.println("Decimal 1234 to BCD: " + Integer.toHexString(bcd)); // Output: 0x1234
+ *
+ */
+public final class BcdConversion {
+ private BcdConversion() {
+ }
+
+ /**
+ * Converts a BCD (Binary-Coded Decimal) number to a decimal number.
+ * Steps:
+ *
1. Validate the BCD number to ensure all digits are between 0 and 9.
+ *
2. Extract the last 4 bits (one BCD digit) from the BCD number.
+ *
3. Multiply the extracted digit by the corresponding power of 10 and add it to the decimal number.
+ *
4. Shift the BCD number right by 4 bits to process the next BCD digit.
+ *
5. Repeat steps 1-4 until the BCD number is zero.
+ *
+ * @param bcd The BCD number.
+ * @return The corresponding decimal number.
+ * @throws IllegalArgumentException if the BCD number contains invalid digits.
+ */
+ public static int bcdToDecimal(int bcd) {
+ int decimal = 0;
+ int multiplier = 1;
+
+ // Validate BCD digits
+ while (bcd > 0) {
+ int digit = bcd & 0xF;
+ if (digit > 9) {
+ throw new IllegalArgumentException("Invalid BCD digit: " + digit);
+ }
+ decimal += digit * multiplier;
+ multiplier *= 10;
+ bcd >>= 4;
+ }
+ return decimal;
+ }
+
+ /**
+ * Converts a decimal number to BCD (Binary-Coded Decimal).
+ *
Steps:
+ *
1. Check if the decimal number is within the valid range for BCD (0 to 9999).
+ *
2. Extract the last decimal digit from the decimal number.
+ *
3. Shift the digit to the correct BCD position and add it to the BCD number.
+ *
4. Remove the last decimal digit from the decimal number.
+ *
5. Repeat steps 2-4 until the decimal number is zero.
+ *
+ * @param decimal The decimal number.
+ * @return The corresponding BCD number.
+ * @throws IllegalArgumentException if the decimal number is greater than 9999.
+ */
+ public static int decimalToBcd(int decimal) {
+ if (decimal < 0 || decimal > 9999) {
+ throw new IllegalArgumentException("Value out of bounds for BCD representation: " + decimal);
+ }
+
+ int bcd = 0;
+ int shift = 0;
+ while (decimal > 0) {
+ int digit = decimal % 10;
+ bcd |= (digit << (shift * 4));
+ decimal /= 10;
+ shift++;
+ }
+ return bcd;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
new file mode 100644
index 000000000000..0d6fd140c720
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java
@@ -0,0 +1,43 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class contains a method to check if the binary representation of a number is a palindrome.
+ *
+ * A binary palindrome is a number whose binary representation is the same when read from left to right and right to left.
+ * For example, the number 9 has a binary representation of 1001, which is a palindrome.
+ * The number 10 has a binary representation of 1010, which is not a palindrome.
+ *
+ *
+ * @author Hardvan
+ */
+public final class BinaryPalindromeCheck {
+ private BinaryPalindromeCheck() {
+ }
+
+ /**
+ * Checks if the binary representation of a number is a palindrome.
+ *
+ * @param x The number to check.
+ * @return True if the binary representation is a palindrome, otherwise false.
+ */
+ public static boolean isBinaryPalindrome(int x) {
+ int reversed = reverseBits(x);
+ return x == reversed;
+ }
+
+ /**
+ * Helper function to reverse all the bits of an integer.
+ *
+ * @param x The number to reverse the bits of.
+ * @return The number with reversed bits.
+ */
+ private static int reverseBits(int x) {
+ int result = 0;
+ while (x > 0) {
+ result <<= 1;
+ result |= (x & 1);
+ x >>= 1;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java
new file mode 100644
index 000000000000..869466320831
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java
@@ -0,0 +1,111 @@
+package com.thealgorithms.bitmanipulation;
+
+import java.util.List;
+
+/**
+ * Implements various Boolean algebra gates (AND, OR, NOT, XOR, NAND, NOR)
+ */
+public final class BooleanAlgebraGates {
+
+ private BooleanAlgebraGates() {
+ // Prevent instantiation
+ }
+
+ /**
+ * Represents a Boolean gate that takes multiple inputs and returns a result.
+ */
+ interface BooleanGate {
+ /**
+ * Evaluates the gate with the given inputs.
+ *
+ * @param inputs The input values for the gate.
+ * @return The result of the evaluation.
+ */
+ boolean evaluate(List inputs);
+ }
+
+ /**
+ * AND Gate implementation.
+ * Returns true if all inputs are true; otherwise, false.
+ */
+ static class ANDGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ for (boolean input : inputs) {
+ if (!input) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * OR Gate implementation.
+ * Returns true if at least one input is true; otherwise, false.
+ */
+ static class ORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ for (boolean input : inputs) {
+ if (input) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * NOT Gate implementation (Unary operation).
+ * Negates a single input value.
+ */
+ static class NOTGate {
+ /**
+ * Evaluates the negation of the input.
+ *
+ * @param input The input value to be negated.
+ * @return The negated value.
+ */
+ public boolean evaluate(boolean input) {
+ return !input;
+ }
+ }
+
+ /**
+ * XOR Gate implementation.
+ * Returns true if an odd number of inputs are true; otherwise, false.
+ */
+ static class XORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ boolean result = false;
+ for (boolean input : inputs) {
+ result ^= input;
+ }
+ return result;
+ }
+ }
+
+ /**
+ * NAND Gate implementation.
+ * Returns true if at least one input is false; otherwise, false.
+ */
+ static class NANDGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ return !new ANDGate().evaluate(inputs); // Equivalent to negation of AND
+ }
+ }
+
+ /**
+ * NOR Gate implementation.
+ * Returns true if all inputs are false; otherwise, false.
+ */
+ static class NORGate implements BooleanGate {
+ @Override
+ public boolean evaluate(List inputs) {
+ return !new ORGate().evaluate(inputs); // Equivalent to negation of OR
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java
new file mode 100644
index 000000000000..3e9a4a21183f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * ClearLeftmostSetBit class contains a method to clear the leftmost set bit of a number.
+ * The leftmost set bit is the leftmost bit that is set to 1 in the binary representation of a number.
+ *
+ * Example:
+ * 26 (11010) -> 10 (01010)
+ * 1 (1) -> 0 (0)
+ * 7 (111) -> 3 (011)
+ * 6 (0110) -> 2 (0010)
+ *
+ * @author Hardvan
+ */
+public final class ClearLeftmostSetBit {
+ private ClearLeftmostSetBit() {
+ }
+
+ /**
+ * Clears the leftmost set bit (1) of a given number.
+ * Step 1: Find the position of the leftmost set bit
+ * Step 2: Create a mask with all bits set except for the leftmost set bit
+ * Step 3: Clear the leftmost set bit using AND with the mask
+ *
+ * @param num The input number.
+ * @return The number after clearing the leftmost set bit.
+ */
+ public static int clearLeftmostSetBit(int num) {
+ int pos = 0;
+ int temp = num;
+ while (temp > 0) {
+ temp >>= 1;
+ pos++;
+ }
+
+ int mask = ~(1 << (pos - 1));
+ return num & mask;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java
new file mode 100644
index 000000000000..318334f0b951
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * CountLeadingZeros class contains a method to count the number of leading zeros in the binary representation of a number.
+ * The number of leading zeros is the number of zeros before the leftmost 1 bit.
+ * For example, the number 5 has 29 leading zeros in its 32-bit binary representation.
+ * The number 0 has 32 leading zeros.
+ * The number 1 has 31 leading zeros.
+ * The number -1 has no leading zeros.
+ *
+ * @author Hardvan
+ */
+public final class CountLeadingZeros {
+ private CountLeadingZeros() {
+ }
+
+ /**
+ * Counts the number of leading zeros in the binary representation of a number.
+ * Method: Keep shifting the mask to the right until the leftmost bit is 1.
+ * The number of shifts is the number of leading zeros.
+ *
+ * @param num The input number.
+ * @return The number of leading zeros.
+ */
+ public static int countLeadingZeros(int num) {
+ if (num == 0) {
+ return 32;
+ }
+
+ int count = 0;
+ int mask = 1 << 31;
+ while ((mask & num) == 0) {
+ count++;
+ mask >>>= 1;
+ }
+
+ return count;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java
new file mode 100644
index 000000000000..7a35fc3feebf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * A utility class to find the Nth bit of a given number.
+ *
+ * This class provides a method to extract the value of the Nth bit (either 0 or 1)
+ * from the binary representation of a given integer.
+ *
+ *
Example:
+ *
{@code
+ * int result = FindNthBit.findNthBit(5, 2); // returns 0 as the 2nd bit of 5 (binary 101) is 0.
+ * }
+ *
+ * Author: Tuhinm2002
+ */
+public final class FindNthBit {
+
+ /**
+ * Private constructor to prevent instantiation.
+ *
+ *
This is a utility class, and it should not be instantiated.
+ * Attempting to instantiate this class will throw an UnsupportedOperationException.
+ */
+ private FindNthBit() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Finds the value of the Nth bit of the given number.
+ *
+ *
This method uses bitwise operations to extract the Nth bit from the
+ * binary representation of the given integer.
+ *
+ * @param num the integer number whose Nth bit is to be found
+ * @param n the bit position (1-based) to retrieve
+ * @return the value of the Nth bit (0 or 1)
+ * @throws IllegalArgumentException if the bit position is less than 1
+ */
+ public static int findNthBit(int num, int n) {
+ if (n < 1) {
+ throw new IllegalArgumentException("Bit position must be greater than or equal to 1.");
+ }
+ // Shifting the number to the right by (n - 1) positions and checking the last bit
+ return (num & (1 << (n - 1))) >> (n - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java
new file mode 100644
index 000000000000..9a761c572e2c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java
@@ -0,0 +1,33 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the first differing bit
+ * between two integers.
+ *
+ * Example:
+ * x = 10 (1010 in binary)
+ * y = 12 (1100 in binary)
+ * The first differing bit is at index 1 (0-based)
+ * So, the output will be 1
+ *
+ * @author Hardvan
+ */
+public final class FirstDifferentBit {
+ private FirstDifferentBit() {
+ }
+
+ /**
+ * Identifies the index of the first differing bit between two integers.
+ * Steps:
+ * 1. XOR the two integers to get the differing bits
+ * 2. Find the index of the first set bit in XOR result
+ *
+ * @param x the first integer
+ * @param y the second integer
+ * @return the index of the first differing bit (0-based)
+ */
+ public static int firstDifferentBit(int x, int y) {
+ int diff = x ^ y;
+ return Integer.numberOfTrailingZeros(diff);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java
new file mode 100644
index 000000000000..f1b812495c1b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.bitmanipulation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides a method to generate all subsets (power set)
+ * of a given set using bit manipulation.
+ *
+ * @author Hardvan
+ */
+public final class GenerateSubsets {
+ private GenerateSubsets() {
+ }
+
+ /**
+ * Generates all subsets of a given set using bit manipulation.
+ * Steps:
+ * 1. Iterate over all numbers from 0 to 2^n - 1.
+ * 2. For each number, iterate over all bits from 0 to n - 1.
+ * 3. If the i-th bit of the number is set, add the i-th element of the set to the current subset.
+ * 4. Add the current subset to the list of subsets.
+ * 5. Return the list of subsets.
+ *
+ * @param set the input set of integers
+ * @return a list of all subsets represented as lists of integers
+ */
+ public static List> generateSubsets(int[] set) {
+ int n = set.length;
+ List> subsets = new ArrayList<>();
+
+ for (int mask = 0; mask < (1 << n); mask++) {
+ List subset = new ArrayList<>();
+ for (int i = 0; i < n; i++) {
+ if ((mask & (1 << i)) != 0) {
+ subset.add(set[i]);
+ }
+ }
+ subsets.add(subset);
+ }
+
+ return subsets;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java
new file mode 100644
index 000000000000..83cd30c7d50a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java
@@ -0,0 +1,44 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Gray code is a binary numeral system where two successive values differ in only one bit.
+ * This is a simple conversion between binary and Gray code.
+ * Example:
+ * 7 -> 0111 -> 0100 -> 4
+ * 4 -> 0100 -> 0111 -> 7
+ * 0 -> 0000 -> 0000 -> 0
+ * 1 -> 0001 -> 0000 -> 0
+ * 2 -> 0010 -> 0011 -> 3
+ * 3 -> 0011 -> 0010 -> 2
+ *
+ * @author Hardvan
+ */
+public final class GrayCodeConversion {
+ private GrayCodeConversion() {
+ }
+
+ /**
+ * Converts a binary number to Gray code.
+ *
+ * @param num The binary number.
+ * @return The corresponding Gray code.
+ */
+ public static int binaryToGray(int num) {
+ return num ^ (num >> 1);
+ }
+
+ /**
+ * Converts a Gray code number back to binary.
+ *
+ * @param gray The Gray code number.
+ * @return The corresponding binary number.
+ */
+ public static int grayToBinary(int gray) {
+ int binary = gray;
+ while (gray > 0) {
+ gray >>= 1;
+ binary ^= gray;
+ }
+ return binary;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java
new file mode 100644
index 000000000000..4c24909ef234
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java
@@ -0,0 +1,29 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * The Hamming distance between two integers is the number of positions at which the corresponding bits are different.
+ * Given two integers x and y, calculate the Hamming distance.
+ * Example:
+ * Input: x = 1, y = 4
+ * Output: 2
+ * Explanation: 1 (0001) and 4 (0100) have 2 differing bits.
+ *
+ * @author Hardvan
+ */
+public final class HammingDistance {
+ private HammingDistance() {
+ }
+
+ /**
+ * Calculates the Hamming distance between two integers.
+ * The Hamming distance is the number of differing bits between the two integers.
+ *
+ * @param x The first integer.
+ * @param y The second integer.
+ * @return The Hamming distance (number of differing bits).
+ */
+ public static int hammingDistance(int x, int y) {
+ int xor = x ^ y;
+ return Integer.bitCount(xor);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java
new file mode 100644
index 000000000000..0fb058b2b8a3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * HigherLowerPowerOfTwo class has two methods to find the next higher and lower power of two.
+ *
+ * nextHigherPowerOfTwo method finds the next higher power of two.
+ * nextLowerPowerOfTwo method finds the next lower power of two.
+ * Both methods take an integer as input and return the next higher or lower power of two.
+ * If the input is less than 1, the next higher power of two is 1.
+ * If the input is less than or equal to 1, the next lower power of two is 0.
+ * nextHigherPowerOfTwo method uses bitwise operations to find the next higher power of two.
+ * nextLowerPowerOfTwo method uses Integer.highestOneBit method to find the next lower power of two.
+ * The time complexity of both methods is O(1).
+ * The space complexity of both methods is O(1).
+ *
+ *
+ * @author Hardvan
+ */
+public final class HigherLowerPowerOfTwo {
+ private HigherLowerPowerOfTwo() {
+ }
+
+ /**
+ * Finds the next higher power of two.
+ *
+ * @param x The given number.
+ * @return The next higher power of two.
+ */
+ public static int nextHigherPowerOfTwo(int x) {
+ if (x < 1) {
+ return 1;
+ }
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x + 1;
+ }
+
+ /**
+ * Finds the next lower power of two.
+ *
+ * @param x The given number.
+ * @return The next lower power of two.
+ */
+ public static int nextLowerPowerOfTwo(int x) {
+ if (x < 1) {
+ return 0;
+ }
+ return Integer.highestOneBit(x);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
index 54d28d4d22cc..4cdf3c6faa3e 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java
@@ -1,13 +1,27 @@
package com.thealgorithms.bitmanipulation;
/**
- * Is number power of 2
+ * Utility class for checking if a number is a power of two.
+ * A power of two is a number that can be expressed as 2^n where n is a non-negative integer.
+ * This class provides a method to determine if a given integer is a power of two using bit manipulation.
+ *
* @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class IsPowerTwo {
private IsPowerTwo() {
}
+
+ /**
+ * Checks if the given integer is a power of two.
+ *
+ * A number is considered a power of two if it is greater than zero and
+ * has exactly one '1' bit in its binary representation. This method
+ * uses the property that for any power of two (n), the expression
+ * (n & (n - 1)) will be zero.
+ *
+ * @param number the integer to check
+ * @return true if the number is a power of two, false otherwise
+ */
public static boolean isPowerTwo(int number) {
if (number <= 0) {
return false;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java
new file mode 100644
index 000000000000..537a046f77e4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to compute the remainder
+ * of a number when divided by a power of two (2^n)
+ * without using division or modulo operations.
+ *
+ * @author Hardvan
+ */
+public final class ModuloPowerOfTwo {
+ private ModuloPowerOfTwo() {
+ }
+
+ /**
+ * Computes the remainder of a given integer when divided by 2^n.
+ *
+ * @param x the input number
+ * @param n the exponent (power of two)
+ * @return the remainder of x divided by 2^n
+ */
+ public static int moduloPowerOfTwo(int x, int n) {
+ if (n <= 0) {
+ throw new IllegalArgumentException("The exponent must be positive");
+ }
+
+ return x & ((1 << n) - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java
new file mode 100644
index 000000000000..6a764d806279
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java
@@ -0,0 +1,30 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the next higher number
+ * with the same number of set bits as the given number.
+ *
+ * @author Hardvan
+ */
+public final class NextHigherSameBitCount {
+ private NextHigherSameBitCount() {
+ }
+
+ /**
+ * Finds the next higher integer with the same number of set bits.
+ * Steps:
+ * 1. Find {@code c}, the rightmost set bit of {@code n}.
+ * 2. Find {@code r}, the rightmost set bit of {@code n + c}.
+ * 3. Swap the bits of {@code r} and {@code n} to the right of {@code c}.
+ * 4. Shift the bits of {@code r} and {@code n} to the right of {@code c} to the rightmost.
+ * 5. Combine the results of steps 3 and 4.
+ *
+ * @param n the input number
+ * @return the next higher integer with the same set bit count
+ */
+ public static int nextHigherSameBitCount(int n) {
+ int c = n & -n;
+ int r = n + c;
+ return (((r ^ n) >> 2) / c) | r;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
index 07476a8b9476..17e1a73ec062 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java
@@ -1,14 +1,30 @@
package com.thealgorithms.bitmanipulation;
/**
- * Find Non Repeating Number
+ * A utility class to find the non-repeating number in an array where every other number repeats.
+ * This class contains a method to identify the single unique number using bit manipulation.
+ *
+ * The solution leverages the properties of the XOR operation, which states that:
+ * - x ^ x = 0 for any integer x (a number XORed with itself is zero)
+ * - x ^ 0 = x for any integer x (a number XORed with zero is the number itself)
+ *
+ * Using these properties, we can find the non-repeating number in linear time with constant space.
+ *
+ * Example:
+ * Given the input array [2, 3, 5, 2, 3], the output will be 5 since it does not repeat.
+ *
* @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class NonRepeatingNumberFinder {
private NonRepeatingNumberFinder() {
}
+ /**
+ * Finds the non-repeating number in the given array.
+ *
+ * @param arr an array of integers where every number except one appears twice
+ * @return the integer that appears only once in the array or 0 if the array is empty
+ */
public static int findNonRepeatingNumber(int[] arr) {
int result = 0;
for (int num : arr) {
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java
new file mode 100644
index 000000000000..bd4868d4dbd5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java
@@ -0,0 +1,41 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to find the element that appears an
+ * odd number of times in an array. All other elements in the array
+ * must appear an even number of times for the logic to work.
+ *
+ * The solution uses the XOR operation, which has the following properties:
+ * - a ^ a = 0 (XOR-ing the same numbers cancels them out)
+ * - a ^ 0 = a
+ * - XOR is commutative and associative.
+ *
+ * Time Complexity: O(n), where n is the size of the array.
+ * Space Complexity: O(1), as no extra space is used.
+ *
+ * Usage Example:
+ * int result = NumberAppearingOddTimes.findOddOccurrence(new int[]{1, 2, 1, 2, 3});
+ * // result will be 3
+ *
+ * @author Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999)
+ */
+
+public final class NumberAppearingOddTimes {
+ private NumberAppearingOddTimes() {
+ }
+
+ /**
+ * Finds the element in the array that appears an odd number of times.
+ *
+ * @param arr the input array containing integers, where all elements
+ * except one appear an even number of times.
+ * @return the integer that appears an odd number of times.
+ */
+ public static int findOddOccurrence(int[] arr) {
+ int result = 0;
+ for (int num : arr) {
+ result ^= num;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
index 8e0946f0eb23..a2da37aa81ee 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java
@@ -1,14 +1,29 @@
package com.thealgorithms.bitmanipulation;
/**
- * Numbers Different Signs
+ * This class provides a method to determine whether two integers have
+ * different signs. It utilizes the XOR operation on the two numbers:
+ *
+ * - If two numbers have different signs, their most significant bits
+ * (sign bits) will differ, resulting in a negative XOR result.
+ * - If two numbers have the same sign, the XOR result will be non-negative.
+ *
+ * Time Complexity: O(1) - Constant time operation.
+ * Space Complexity: O(1) - No extra space used.
+ *
* @author Bama Charan Chhandogi
*/
-
public final class NumbersDifferentSigns {
private NumbersDifferentSigns() {
}
+ /**
+ * Determines if two integers have different signs using bitwise XOR.
+ *
+ * @param num1 the first integer
+ * @param num2 the second integer
+ * @return true if the two numbers have different signs, false otherwise
+ */
public static boolean differentSigns(int num1, int num2) {
return (num1 ^ num2) < 0;
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java
new file mode 100644
index 000000000000..afec0188e299
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java
@@ -0,0 +1,32 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to detect if two integers
+ * differ by exactly one bit flip.
+ *
+ * Example:
+ * 1 (0001) and 2 (0010) differ by exactly one bit flip.
+ * 7 (0111) and 3 (0011) differ by exactly one bit flip.
+ *
+ * @author Hardvan
+ */
+public final class OneBitDifference {
+ private OneBitDifference() {
+ }
+
+ /**
+ * Checks if two integers differ by exactly one bit.
+ *
+ * @param x the first integer
+ * @param y the second integer
+ * @return true if x and y differ by exactly one bit, false otherwise
+ */
+ public static boolean differByOneBit(int x, int y) {
+ if (x == y) {
+ return false;
+ }
+
+ int xor = x ^ y;
+ return (xor & (xor - 1)) == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java
new file mode 100644
index 000000000000..c5c068422113
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java
@@ -0,0 +1,28 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * @author - https://github.com/Monk-AbhinayVerma
+ * @Wikipedia - https://en.wikipedia.org/wiki/Ones%27_complement
+ * The class OnesComplement computes the complement of binary number
+ * and returns
+ * the complemented binary string.
+ * @return the complimented binary string
+ */
+public final class OnesComplement {
+ private OnesComplement() {
+ }
+
+ // Function to get the 1's complement of a binary number
+ public static String onesComplement(String binary) {
+ StringBuilder complement = new StringBuilder();
+ // Invert each bit to get the 1's complement
+ for (int i = 0; i < binary.length(); i++) {
+ if (binary.charAt(i) == '0') {
+ complement.append('1');
+ } else {
+ complement.append('0');
+ }
+ }
+ return complement.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java
new file mode 100644
index 000000000000..5acab4d4a362
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * The ParityCheck class provides a method to check the parity of a given number.
+ *
+ * Parity is a mathematical term that describes the property of an integer's binary representation.
+ * The parity of a binary number is the number of 1s in its binary representation.
+ * If the number of 1s is even, the parity is even; otherwise, it is odd.
+ *
+ * For example, the binary representation of 5 is 101, which has two 1s, so the parity of 5 is even.
+ * The binary representation of 6 is 110, which has two 1s, so the parity of 6 is even.
+ * The binary representation of 7 is 111, which has three 1s, so the parity of 7 is odd.
+ *
+ * @author Hardvan
+ */
+public final class ParityCheck {
+ private ParityCheck() {
+ }
+
+ /**
+ * This method checks the parity of the given number.
+ *
+ * @param n the number to check the parity of
+ * @return true if the number has even parity, false otherwise
+ */
+ public static boolean checkParity(int n) {
+ int count = 0;
+ while (n > 0) {
+ count += n & 1;
+ n >>= 1;
+ }
+ return count % 2 == 0;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
index e8f2930d3afe..12c269d9be48 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java
@@ -1,14 +1,33 @@
package com.thealgorithms.bitmanipulation;
/**
- * Converts any Octal Number to a Binary Number
+ * This class provides a method to reverse the bits of a 32-bit integer.
+ * Reversing the bits means that the least significant bit (LSB) becomes
+ * the most significant bit (MSB) and vice versa.
+ *
+ * Example:
+ * Input (binary): 00000010100101000001111010011100 (43261596)
+ * Output (binary): 00111001011110000010100101000000 (964176192)
+ *
+ * Time Complexity: O(32) - A fixed number of 32 iterations
+ * Space Complexity: O(1) - No extra space used
+ *
+ * Note:
+ * - If the input is negative, Java handles it using two’s complement representation.
+ * - This function works on 32-bit integers by default.
+ *
* @author Bama Charan Chhandogi
*/
-
public final class ReverseBits {
private ReverseBits() {
}
+ /**
+ * Reverses the bits of a 32-bit integer.
+ *
+ * @param n the integer whose bits are to be reversed
+ * @return the integer obtained by reversing the bits of the input
+ */
public static int reverseBits(int n) {
int result = 0;
int bitCount = 32;
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
index b41aeca19af6..624a4e2b858a 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java
@@ -1,34 +1,68 @@
package com.thealgorithms.bitmanipulation;
-/*
+/**
+ * A utility class for performing single-bit operations on integers.
+ * These operations include flipping, setting, clearing, and getting
+ * individual bits at specified positions.
+ *
+ * Bit positions are zero-indexed (i.e., the least significant bit is at position 0).
+ * These methods leverage bitwise operations for optimal performance.
+ *
+ * Examples:
+ * - `flipBit(3, 1)` flips the bit at index 1 in binary `11` (result: `1`).
+ * - `setBit(4, 0)` sets the bit at index 0 in `100` (result: `101` or 5).
+ * - `clearBit(7, 1)` clears the bit at index 1 in `111` (result: `101` or 5).
+ * - `getBit(6, 0)` checks if the least significant bit is set (result: `0`).
+ *
+ * Time Complexity: O(1) for all operations.
+ *
* Author: lukasb1b (https://github.com/lukasb1b)
*/
-
public final class SingleBitOperations {
private SingleBitOperations() {
}
+
/**
- * Flip the bit at position 'bit' in 'num'
+ * Flips (toggles) the bit at the specified position.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to flip (0-indexed)
+ * @return the new number after flipping the specified bit
*/
public static int flipBit(final int num, final int bit) {
return num ^ (1 << bit);
}
+
/**
- * Set the bit at position 'bit' to 1 in the 'num' variable
+ * Sets the bit at the specified position to 1.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to set (0-indexed)
+ * @return the new number after setting the specified bit to 1
*/
public static int setBit(final int num, final int bit) {
return num | (1 << bit);
}
+
/**
- * Clears the bit located at 'bit' from 'num'
+ * Clears the bit at the specified position (sets it to 0).
+ *
+ * @param num the input number
+ * @param bit the position of the bit to clear (0-indexed)
+ * @return the new number after clearing the specified bit
*/
public static int clearBit(final int num, final int bit) {
return num & ~(1 << bit);
}
+
/**
- * Get the bit located at 'bit' from 'num'
+ * Gets the bit value (0 or 1) at the specified position.
+ *
+ * @param num the input number
+ * @param bit the position of the bit to retrieve (0-indexed)
+ * @return 1 if the bit is set, 0 otherwise
*/
public static int getBit(final int num, final int bit) {
- return ((num >> bit) & 1);
+ return (num >> bit) & 1;
}
}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java
new file mode 100644
index 000000000000..85ebdf02db25
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Utility class to find the single non-duplicate element from an array
+ * where all other elements appear twice.
+ *
+ * The algorithm runs in O(n) time complexity and O(1) space complexity
+ * using bitwise XOR.
+ *
+ *
+ * @author Tuhin M
+ */
+public final class SingleElement {
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ * Throws an UnsupportedOperationException if attempted.
+ */
+ private SingleElement() {
+ throw new UnsupportedOperationException("Utility Class");
+ }
+
+ /**
+ * Finds the single non-duplicate element in an array where every other
+ * element appears exactly twice. Uses bitwise XOR to achieve O(n) time
+ * complexity and O(1) space complexity.
+ *
+ * @param arr the input array containing integers where every element
+ * except one appears exactly twice
+ * @return the single non-duplicate element
+ */
+ public static int findSingleElement(int[] arr) {
+ int ele = 0;
+ for (int i = 0; i < arr.length; i++) {
+ ele ^= arr[i];
+ }
+ return ele;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java
new file mode 100644
index 000000000000..98a7de8bdf1a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * A utility class to swap every pair of adjacent bits in a given integer.
+ * This operation shifts the even-positioned bits to odd positions and vice versa.
+ *
+ * Example:
+ * - Input: 2 (binary: `10`) → Output: 1 (binary: `01`)
+ * - Input: 43 (binary: `101011`) → Output: 23 (binary: `010111`)
+ *
+ * **Explanation of the Algorithm:**
+ * 1. Mask even-positioned bits: Using `0xAAAAAAAA` (binary: `101010...`),
+ * which selects bits in even positions.
+ * 2. Mask odd-positioned bits: Using `0x55555555` (binary: `010101...`),
+ * which selects bits in odd positions.
+ * 3. Shift bits:
+ * - Right-shift even-positioned bits by 1 to move them to odd positions.
+ * - Left-shift odd-positioned bits by 1 to move them to even positions.
+ * 4. Combine both shifted results using bitwise OR (`|`) to produce the final result.
+ *
+ * Use Case: This algorithm can be useful in applications involving low-level bit manipulation,
+ * such as encoding, data compression, or cryptographic transformations.
+ *
+ * Time Complexity: O(1) (constant time, since operations are bitwise).
+ *
+ * Author: Lakshyajeet Singh Goyal (https://github.com/DarkMatter-999)
+ */
+public final class SwapAdjacentBits {
+ private SwapAdjacentBits() {
+ }
+
+ /**
+ * Swaps every pair of adjacent bits of a given integer.
+ * Steps:
+ * 1. Mask the even-positioned bits.
+ * 2. Mask the odd-positioned bits.
+ * 3. Shift the even bits to the right and the odd bits to the left.
+ * 4. Combine the shifted bits.
+ *
+ * @param num the integer whose bits are to be swapped
+ * @return the integer after swapping every pair of adjacent bits
+ */
+ public static int swapAdjacentBits(int num) {
+ // mask the even bits (0xAAAAAAAA => 10101010...)
+ int evenBits = num & 0xAAAAAAAA;
+
+ // mask the odd bits (0x55555555 => 01010101...)
+ int oddBits = num & 0x55555555;
+
+ // right shift even bits and left shift odd bits
+ evenBits >>= 1;
+ oddBits <<= 1;
+
+ // combine shifted bits
+ return evenBits | oddBits;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java
new file mode 100644
index 000000000000..9b8cecd791a6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides a method to compute the Two's Complement of a given binary number.
+ *
+ * In two's complement representation, a binary number's negative value is obtained
+ * by taking the one's complement (inverting all bits) and then adding 1 to the result.
+ * This method handles both small and large binary strings and ensures the output is
+ * correct for all binary inputs, including edge cases like all zeroes and all ones.
+ *
+ *
For more information on Two's Complement:
+ * @see Wikipedia - Two's Complement
+ *
+ *
Algorithm originally suggested by Jon von Neumann.
+ *
+ * @author Abhinay Verma (https://github.com/Monk-AbhinayVerma)
+ */
+public final class TwosComplement {
+ private TwosComplement() {
+ }
+
+ /**
+ * Computes the Two's Complement of the given binary string.
+ * Steps:
+ * 1. Compute the One's Complement (invert all bits).
+ * 2. Add 1 to the One's Complement to get the Two's Complement.
+ * 3. Iterate from the rightmost bit to the left, adding 1 and carrying over as needed.
+ * 4. If a carry is still present after the leftmost bit, prepend '1' to handle overflow.
+ *
+ * @param binary The binary number as a string (only '0' and '1' characters allowed).
+ * @return The two's complement of the input binary string as a new binary string.
+ * @throws IllegalArgumentException If the input contains non-binary characters.
+ */
+ public static String twosComplement(String binary) {
+ if (!binary.matches("[01]+")) {
+ throw new IllegalArgumentException("Input must contain only '0' and '1'.");
+ }
+
+ StringBuilder onesComplement = new StringBuilder();
+ for (char bit : binary.toCharArray()) {
+ onesComplement.append(bit == '0' ? '1' : '0');
+ }
+
+ StringBuilder twosComplement = new StringBuilder(onesComplement);
+ boolean carry = true;
+
+ for (int i = onesComplement.length() - 1; i >= 0 && carry; i--) {
+ if (onesComplement.charAt(i) == '1') {
+ twosComplement.setCharAt(i, '0');
+ } else {
+ twosComplement.setCharAt(i, '1');
+ carry = false;
+ }
+ }
+
+ if (carry) {
+ twosComplement.insert(0, '1');
+ }
+
+ return twosComplement.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java
new file mode 100644
index 000000000000..b22abc0c04ff
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * This class provides methods to convert between XS-3 (Excess-3) and binary.
+ *
+ * Excess-3, also called XS-3, is a binary-coded decimal (BCD) code in which each decimal digit is represented by its corresponding 4-bit binary value plus 3.
+ *
+ * For more information, refer to the
+ * Excess-3 Wikipedia page.
+ *
+ * Example usage:
+ *
+ * int binary = Xs3Conversion.xs3ToBinary(0x4567);
+ * System.out.println("XS-3 0x4567 to binary: " + binary); // Output: 1234
+ *
+ * int xs3 = Xs3Conversion.binaryToXs3(1234);
+ * System.out.println("Binary 1234 to XS-3: " + Integer.toHexString(xs3)); // Output: 0x4567
+ *
+ */
+public final class Xs3Conversion {
+ private Xs3Conversion() {
+ }
+ /**
+ * Converts an XS-3 (Excess-3) number to binary.
+ *
+ * @param xs3 The XS-3 number.
+ * @return The corresponding binary number.
+ */
+ public static int xs3ToBinary(int xs3) {
+ int binary = 0;
+ int multiplier = 1;
+ while (xs3 > 0) {
+ int digit = (xs3 & 0xF) - 3; // Extract the last 4 bits (one XS-3 digit) and subtract 3
+ binary += digit * multiplier;
+ multiplier *= 10;
+ xs3 >>= 4; // Shift right by 4 bits to process the next XS-3 digit
+ }
+ return binary;
+ }
+
+ /**
+ * Converts a binary number to XS-3 (Excess-3).
+ *
+ * @param binary The binary number.
+ * @return The corresponding XS-3 number.
+ */
+ public static int binaryToXs3(int binary) {
+ int xs3 = 0;
+ int shift = 0;
+ while (binary > 0) {
+ int digit = (binary % 10) + 3; // Extract the last decimal digit and add 3
+ xs3 |= (digit << (shift * 4)); // Shift the digit to the correct XS-3 position
+ binary /= 10; // Remove the last decimal digit
+ shift++;
+ }
+ return xs3;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java
new file mode 100644
index 000000000000..d915858f9e6f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java
@@ -0,0 +1,167 @@
+package com.thealgorithms.ciphers;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The ADFGVX cipher is a fractionating transposition cipher that was used by
+ * the German Army during World War I. It combines a **Polybius square substitution**
+ * with a **columnar transposition** to enhance encryption strength.
+ *
+ * The name "ADFGVX" refers to the six letters (A, D, F, G, V, X) used as row and
+ * column labels in the Polybius square. This cipher was designed to secure
+ * communication and create complex, hard-to-break ciphertexts.
+ *
+ * Learn more: ADFGVX Cipher - Wikipedia.
+ *
+ * Example usage:
+ *
+ * ADFGVXCipher cipher = new ADFGVXCipher();
+ * String encrypted = cipher.encrypt("attack at 1200am", "PRIVACY");
+ * String decrypted = cipher.decrypt(encrypted, "PRIVACY");
+ *
+ *
+ * @author bennybebo
+ */
+public class ADFGVXCipher {
+
+ // Constants used in the Polybius square
+ private static final char[] POLYBIUS_LETTERS = {'A', 'D', 'F', 'G', 'V', 'X'};
+ private static final char[][] POLYBIUS_SQUARE = {{'N', 'A', '1', 'C', '3', 'H'}, {'8', 'T', 'B', '2', 'O', 'M'}, {'E', '5', 'W', 'R', 'P', 'D'}, {'4', 'F', '6', 'G', '7', 'I'}, {'9', 'J', '0', 'K', 'L', 'Q'}, {'S', 'U', 'V', 'X', 'Y', 'Z'}};
+
+ // Maps for fast substitution lookups
+ private static final Map POLYBIUS_MAP = new HashMap<>();
+ private static final Map REVERSE_POLYBIUS_MAP = new HashMap<>();
+
+ // Static block to initialize the lookup tables from the Polybius square
+ static {
+ for (int i = 0; i < POLYBIUS_SQUARE.length; i++) {
+ for (int j = 0; j < POLYBIUS_SQUARE[i].length; j++) {
+ String key = "" + POLYBIUS_LETTERS[i] + POLYBIUS_LETTERS[j];
+ POLYBIUS_MAP.put(key, POLYBIUS_SQUARE[i][j]);
+ REVERSE_POLYBIUS_MAP.put(POLYBIUS_SQUARE[i][j], key);
+ }
+ }
+ }
+
+ /**
+ * Encrypts a given plaintext using the ADFGVX cipher with the provided keyword.
+ * Steps:
+ * 1. Substitute each letter in the plaintext with a pair of ADFGVX letters.
+ * 2. Perform a columnar transposition on the fractionated text using the keyword.
+ *
+ * @param plaintext The message to be encrypted (can contain letters and digits).
+ * @param key The keyword for columnar transposition.
+ * @return The encrypted message as ciphertext.
+ */
+ public String encrypt(String plaintext, String key) {
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z0-9]", ""); // Sanitize input
+ StringBuilder fractionatedText = new StringBuilder();
+
+ for (char c : plaintext.toCharArray()) {
+ fractionatedText.append(REVERSE_POLYBIUS_MAP.get(c));
+ }
+
+ return columnarTransposition(fractionatedText.toString(), key);
+ }
+
+ /**
+ * Decrypts a given ciphertext using the ADFGVX cipher with the provided keyword.
+ * Steps:
+ * 1. Reverse the columnar transposition performed during encryption.
+ * 2. Substitute each pair of ADFGVX letters with the corresponding plaintext letter.
+ * The resulting text is the decrypted message.
+ *
+ * @param ciphertext The encrypted message.
+ * @param key The keyword used during encryption.
+ * @return The decrypted plaintext message.
+ */
+ public String decrypt(String ciphertext, String key) {
+ String fractionatedText = reverseColumnarTransposition(ciphertext, key);
+
+ StringBuilder plaintext = new StringBuilder();
+ for (int i = 0; i < fractionatedText.length(); i += 2) {
+ String pair = fractionatedText.substring(i, i + 2);
+ plaintext.append(POLYBIUS_MAP.get(pair));
+ }
+
+ return plaintext.toString();
+ }
+
+ /**
+ * Helper method: Performs columnar transposition during encryption
+ *
+ * @param text The fractionated text to be transposed
+ * @param key The keyword for columnar transposition
+ * @return The transposed text
+ */
+ private String columnarTransposition(String text, String key) {
+ int numRows = (int) Math.ceil((double) text.length() / key.length());
+ char[][] table = new char[numRows][key.length()];
+ for (char[] row : table) { // Fill empty cells with underscores
+ Arrays.fill(row, '_');
+ }
+
+ // Populate the table row by row
+ for (int i = 0; i < text.length(); i++) {
+ table[i / key.length()][i % key.length()] = text.charAt(i);
+ }
+
+ // Read columns based on the alphabetical order of the key
+ StringBuilder ciphertext = new StringBuilder();
+ char[] sortedKey = key.toCharArray();
+ Arrays.sort(sortedKey);
+
+ for (char keyChar : sortedKey) {
+ int column = key.indexOf(keyChar);
+ for (char[] row : table) {
+ if (row[column] != '_') {
+ ciphertext.append(row[column]);
+ }
+ }
+ }
+
+ return ciphertext.toString();
+ }
+
+ /**
+ * Helper method: Reverses the columnar transposition during decryption
+ *
+ * @param ciphertext The transposed text to be reversed
+ * @param key The keyword used during encryption
+ * @return The reversed text
+ */
+ private String reverseColumnarTransposition(String ciphertext, String key) {
+ int numRows = (int) Math.ceil((double) ciphertext.length() / key.length());
+ char[][] table = new char[numRows][key.length()];
+
+ char[] sortedKey = key.toCharArray();
+ Arrays.sort(sortedKey);
+
+ int index = 0;
+ // Populate the table column by column according to the sorted key
+ for (char keyChar : sortedKey) {
+ int column = key.indexOf(keyChar);
+ for (int row = 0; row < numRows; row++) {
+ if (index < ciphertext.length()) {
+ table[row][column] = ciphertext.charAt(index++);
+ } else {
+ table[row][column] = '_';
+ }
+ }
+ }
+
+ // Read the table row by row to reconstruct the fractionated text
+ StringBuilder fractionatedText = new StringBuilder();
+ for (char[] row : table) {
+ for (char cell : row) {
+ if (cell != '_') {
+ fractionatedText.append(cell);
+ }
+ }
+ }
+
+ return fractionatedText.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/AES.java b/src/main/java/com/thealgorithms/ciphers/AES.java
index 5d614afbe584..1c283f6b7655 100644
--- a/src/main/java/com/thealgorithms/ciphers/AES.java
+++ b/src/main/java/com/thealgorithms/ciphers/AES.java
@@ -2418,8 +2418,6 @@ public static BigInteger scheduleCore(BigInteger t, int rconCounter) {
rBytes = new StringBuilder(rBytes.substring(0, i * 2) + currentByteBits + rBytes.substring((i + 1) * 2));
}
- // t = new BigInteger(rBytes, 16);
- // return t;
return new BigInteger(rBytes.toString(), 16);
}
diff --git a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
index 636323b63646..b82681372f2b 100644
--- a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
@@ -34,19 +34,19 @@ private AffineCipher() {
*/
static String encryptMessage(char[] msg) {
// Cipher Text initially empty
- String cipher = "";
+ StringBuilder cipher = new StringBuilder();
for (int i = 0; i < msg.length; i++) {
// Avoid space to be encrypted
/* applying encryption formula ( a * x + b ) mod m
{here x is msg[i] and m is 26} and added 'A' to
bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (msg[i] != ' ') {
- cipher += (char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A');
+ cipher.append((char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A'));
} else { // else simply append space character
- cipher += msg[i];
+ cipher.append(msg[i]);
}
}
- return cipher;
+ return cipher.toString();
}
/**
@@ -56,7 +56,7 @@ static String encryptMessage(char[] msg) {
* @return the decrypted plaintext message
*/
static String decryptCipher(String cipher) {
- String msg = "";
+ StringBuilder msg = new StringBuilder();
int aInv = 0;
int flag;
@@ -75,13 +75,13 @@ static String decryptCipher(String cipher) {
{here x is cipher[i] and m is 26} and added 'A'
to bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (cipher.charAt(i) != ' ') {
- msg += (char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A');
+ msg.append((char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A'));
} else { // else simply append space character
- msg += cipher.charAt(i);
+ msg.append(cipher.charAt(i));
}
}
- return msg;
+ return msg.toString();
}
// Driver code
diff --git a/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java
new file mode 100644
index 000000000000..9169aa82bd75
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.ciphers;
+
+/**
+ * The Atbash cipher is a classic substitution cipher that substitutes each letter
+ * with its opposite letter in the alphabet.
+ *
+ * For example:
+ * - 'A' becomes 'Z', 'B' becomes 'Y', 'C' becomes 'X', and so on.
+ * - Similarly, 'a' becomes 'z', 'b' becomes 'y', and so on.
+ *
+ * The cipher works identically for both uppercase and lowercase letters.
+ * Non-alphabetical characters remain unchanged in the output.
+ *
+ * This cipher is symmetric, meaning that applying the cipher twice will return
+ * the original text. Therefore, the same function is used for both encryption and decryption.
+ *
+ * Usage Example:
+ *
+ * AtbashCipher cipher = new AtbashCipher("Hello World!");
+ * String encrypted = cipher.convert(); // Output: "Svool Dliow!"
+ *
+ *
+ * @author Krounosity
+ * @see Atbash Cipher (Wikipedia)
+ */
+public class AtbashCipher {
+
+ private String toConvert;
+
+ public AtbashCipher() {
+ }
+
+ /**
+ * Constructor with a string parameter.
+ *
+ * @param str The string to be converted using the Atbash cipher
+ */
+ public AtbashCipher(String str) {
+ this.toConvert = str;
+ }
+
+ /**
+ * Returns the current string set for conversion.
+ *
+ * @return The string to be converted
+ */
+ public String getString() {
+ return toConvert;
+ }
+
+ /**
+ * Sets the string to be converted using the Atbash cipher.
+ *
+ * @param str The new string to convert
+ */
+ public void setString(String str) {
+ this.toConvert = str;
+ }
+
+ /**
+ * Checks if a character is uppercase.
+ *
+ * @param ch The character to check
+ * @return {@code true} if the character is uppercase, {@code false} otherwise
+ */
+ private boolean isCapital(char ch) {
+ return ch >= 'A' && ch <= 'Z';
+ }
+
+ /**
+ * Checks if a character is lowercase.
+ *
+ * @param ch The character to check
+ * @return {@code true} if the character is lowercase, {@code false} otherwise
+ */
+ private boolean isSmall(char ch) {
+ return ch >= 'a' && ch <= 'z';
+ }
+
+ /**
+ * Converts the input string using the Atbash cipher.
+ * Alphabetic characters are substituted with their opposite in the alphabet,
+ * while non-alphabetic characters remain unchanged.
+ *
+ * @return The converted string after applying the Atbash cipher
+ */
+ public String convert() {
+ StringBuilder convertedString = new StringBuilder();
+
+ for (char ch : toConvert.toCharArray()) {
+ if (isSmall(ch)) {
+ convertedString.append((char) ('z' - (ch - 'a')));
+ } else if (isCapital(ch)) {
+ convertedString.append((char) ('Z' - (ch - 'A')));
+ } else {
+ convertedString.append(ch);
+ }
+ }
+ return convertedString.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java
new file mode 100644
index 000000000000..16dfd6e674af
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java
@@ -0,0 +1,71 @@
+package com.thealgorithms.ciphers;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The Baconian Cipher is a substitution cipher where each letter is represented
+ * by a group of five binary digits (A's and B's). It can also be used to hide
+ * messages within other texts, making it a simple form of steganography.
+ * https://en.wikipedia.org/wiki/Bacon%27s_cipher
+ *
+ * @author Bennybebo
+ */
+public class BaconianCipher {
+
+ private static final Map BACONIAN_MAP = new HashMap<>();
+ private static final Map REVERSE_BACONIAN_MAP = new HashMap<>();
+
+ static {
+ // Initialize the Baconian cipher mappings
+ String[] baconianAlphabet = {"AAAAA", "AAAAB", "AAABA", "AAABB", "AABAA", "AABAB", "AABBA", "AABBB", "ABAAA", "ABAAB", "ABABA", "ABABB", "ABBAA", "ABBAB", "ABBBA", "ABBBB", "BAAAA", "BAAAB", "BAABA", "BAABB", "BABAA", "BABAB", "BABBA", "BABBB", "BBAAA", "BBAAB"};
+ char letter = 'A';
+ for (String code : baconianAlphabet) {
+ BACONIAN_MAP.put(letter, code);
+ REVERSE_BACONIAN_MAP.put(code, letter);
+ letter++;
+ }
+
+ // Handle I/J as the same letter
+ BACONIAN_MAP.put('I', BACONIAN_MAP.get('J'));
+ REVERSE_BACONIAN_MAP.put(BACONIAN_MAP.get('I'), 'I');
+ }
+
+ /**
+ * Encrypts the given plaintext using the Baconian cipher.
+ *
+ * @param plaintext The plaintext message to encrypt.
+ * @return The ciphertext as a binary (A/B) sequence.
+ */
+ public String encrypt(String plaintext) {
+ StringBuilder ciphertext = new StringBuilder();
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Remove non-letter characters
+
+ for (char letter : plaintext.toCharArray()) {
+ ciphertext.append(BACONIAN_MAP.get(letter));
+ }
+
+ return ciphertext.toString();
+ }
+
+ /**
+ * Decrypts the given ciphertext encoded in binary (A/B) format using the Baconian cipher.
+ *
+ * @param ciphertext The ciphertext to decrypt.
+ * @return The decrypted plaintext message.
+ */
+ public String decrypt(String ciphertext) {
+ StringBuilder plaintext = new StringBuilder();
+
+ for (int i = 0; i < ciphertext.length(); i += 5) {
+ String code = ciphertext.substring(i, i + 5);
+ if (REVERSE_BACONIAN_MAP.containsKey(code)) {
+ plaintext.append(REVERSE_BACONIAN_MAP.get(code));
+ } else {
+ throw new IllegalArgumentException("Invalid Baconian code: " + code);
+ }
+ }
+
+ return plaintext.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/Blowfish.java b/src/main/java/com/thealgorithms/ciphers/Blowfish.java
index f6a0a3753e9b..ea1807e62710 100644
--- a/src/main/java/com/thealgorithms/ciphers/Blowfish.java
+++ b/src/main/java/com/thealgorithms/ciphers/Blowfish.java
@@ -1078,7 +1078,7 @@ public class Blowfish {
* @return String object which is a binary representation of the hex number passed as parameter
*/
private String hexToBin(String hex) {
- String binary = "";
+ StringBuilder binary = new StringBuilder();
long num;
String binary4B;
int n = hex.length();
@@ -1089,9 +1089,9 @@ private String hexToBin(String hex) {
binary4B = "0000" + binary4B;
binary4B = binary4B.substring(binary4B.length() - 4);
- binary += binary4B;
+ binary.append(binary4B);
}
- return binary;
+ return binary.toString();
}
/**
@@ -1103,12 +1103,12 @@ private String hexToBin(String hex) {
*/
private String binToHex(String binary) {
long num = Long.parseUnsignedLong(binary, 2);
- String hex = Long.toHexString(num);
+ StringBuilder hex = new StringBuilder(Long.toHexString(num));
while (hex.length() < (binary.length() / 4)) {
- hex = "0" + hex;
+ hex.insert(0, "0");
}
- return hex;
+ return hex.toString();
}
/**
@@ -1121,12 +1121,12 @@ private String binToHex(String binary) {
private String xor(String a, String b) {
a = hexToBin(a);
b = hexToBin(b);
- String ans = "";
+ StringBuilder ans = new StringBuilder();
for (int i = 0; i < a.length(); i++) {
- ans += (char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0');
+ ans.append((char) (((a.charAt(i) - '0') ^ (b.charAt(i) - '0')) + '0'));
}
- ans = binToHex(ans);
- return ans;
+ ans = new StringBuilder(binToHex(ans.toString()));
+ return ans.toString();
}
/**
diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java
index 61c444cf6463..23535bc2b5d2 100644
--- a/src/main/java/com/thealgorithms/ciphers/Caesar.java
+++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java
@@ -9,6 +9,9 @@
* @author khalil2535
*/
public class Caesar {
+ private static char normalizeShift(final int shift) {
+ return (char) (shift % 26);
+ }
/**
* Encrypt text by shifting every Latin char by add number shift for ASCII
@@ -19,7 +22,7 @@ public class Caesar {
public String encode(String message, int shift) {
StringBuilder encoded = new StringBuilder();
- shift %= 26;
+ final char shiftChar = normalizeShift(shift);
final int length = message.length();
for (int i = 0; i < length; i++) {
@@ -29,10 +32,10 @@ public String encode(String message, int shift) {
char current = message.charAt(i); // Java law : char + int = char
if (isCapitalLatinLetter(current)) {
- current += shift;
+ current += shiftChar;
encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters
} else if (isSmallLatinLetter(current)) {
- current += shift;
+ current += shiftChar;
encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters
} else {
encoded.append(current);
@@ -50,16 +53,16 @@ public String encode(String message, int shift) {
public String decode(String encryptedMessage, int shift) {
StringBuilder decoded = new StringBuilder();
- shift %= 26;
+ final char shiftChar = normalizeShift(shift);
final int length = encryptedMessage.length();
for (int i = 0; i < length; i++) {
char current = encryptedMessage.charAt(i);
if (isCapitalLatinLetter(current)) {
- current -= shift;
+ current -= shiftChar;
decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters
} else if (isSmallLatinLetter(current)) {
- current -= shift;
+ current -= shiftChar;
decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters
} else {
decoded.append(current);
diff --git a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
index d7e64a12ebfd..b6b889b079ca 100644
--- a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
@@ -27,13 +27,13 @@ private ColumnarTranspositionCipher() {
* @return a String with the word encrypted by the Columnar Transposition
* Cipher Rule
*/
- public static String encrpyter(String word, String keyword) {
+ public static String encrypt(final String word, final String keyword) {
ColumnarTranspositionCipher.keyword = keyword;
- abecedariumBuilder(500);
+ abecedariumBuilder();
table = tableBuilder(word);
Object[][] sortedTable = sortTable(table);
StringBuilder wordEncrypted = new StringBuilder();
- for (int i = 0; i < sortedTable[i].length; i++) {
+ for (int i = 0; i < sortedTable[0].length; i++) {
for (int j = 1; j < sortedTable.length; j++) {
wordEncrypted.append(sortedTable[j][i]);
}
@@ -51,11 +51,12 @@ public static String encrpyter(String word, String keyword) {
* @return a String with the word encrypted by the Columnar Transposition
* Cipher Rule
*/
- public static String encrpyter(String word, String keyword, String abecedarium) {
+ public static String encrypt(String word, String keyword, String abecedarium) {
ColumnarTranspositionCipher.keyword = keyword;
ColumnarTranspositionCipher.abecedarium = Objects.requireNonNullElse(abecedarium, ABECEDARIUM);
table = tableBuilder(word);
Object[][] sortedTable = sortTable(table);
+
StringBuilder wordEncrypted = new StringBuilder();
for (int i = 0; i < sortedTable[0].length; i++) {
for (int j = 1; j < sortedTable.length; j++) {
@@ -72,7 +73,7 @@ public static String encrpyter(String word, String keyword, String abecedarium)
* @return a String decrypted with the word encrypted by the Columnar
* Transposition Cipher Rule
*/
- public static String decrypter() {
+ public static String decrypt() {
StringBuilder wordDecrypted = new StringBuilder();
for (int i = 1; i < table.length; i++) {
for (Object item : table[i]) {
@@ -91,14 +92,14 @@ public static String decrypter() {
*/
private static Object[][] tableBuilder(String word) {
Object[][] table = new Object[numberOfRows(word) + 1][keyword.length()];
- char[] wordInChards = word.toCharArray();
- // Fils in the respective numbers
+ char[] wordInChars = word.toCharArray();
+ // Fills in the respective numbers for the column
table[0] = findElements();
int charElement = 0;
for (int i = 1; i < table.length; i++) {
for (int j = 0; j < table[i].length; j++) {
- if (charElement < wordInChards.length) {
- table[i][j] = wordInChards[charElement];
+ if (charElement < wordInChars.length) {
+ table[i][j] = wordInChars[charElement];
charElement++;
} else {
table[i][j] = ENCRYPTION_FIELD_CHAR;
@@ -116,7 +117,7 @@ private static Object[][] tableBuilder(String word) {
* order to respect the Columnar Transposition Cipher Rule.
*/
private static int numberOfRows(String word) {
- if (word.length() / keyword.length() > word.length() / keyword.length()) {
+ if (word.length() % keyword.length() != 0) {
return (word.length() / keyword.length()) + 1;
} else {
return word.length() / keyword.length();
@@ -173,13 +174,11 @@ private static void switchColumns(Object[][] table, int firstColumnIndex, int se
}
/**
- * Creates an abecedarium with a specified ascii inded
- *
- * @param value Number of characters being used based on the ASCII Table
+ * Creates an abecedarium with all available ascii values.
*/
- private static void abecedariumBuilder(int value) {
+ private static void abecedariumBuilder() {
StringBuilder t = new StringBuilder();
- for (int i = 0; i < value; i++) {
+ for (int i = 0; i < 256; i++) {
t.append((char) i);
}
abecedarium = t.toString();
diff --git a/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java
new file mode 100644
index 000000000000..7470b40e001a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.ciphers;
+
+import java.math.BigInteger;
+
+public final class DiffieHellman {
+
+ private final BigInteger base;
+ private final BigInteger secret;
+ private final BigInteger prime;
+
+ // Constructor to initialize base, secret, and prime
+ public DiffieHellman(BigInteger base, BigInteger secret, BigInteger prime) {
+ // Check for non-null and positive values
+ if (base == null || secret == null || prime == null || base.signum() <= 0 || secret.signum() <= 0 || prime.signum() <= 0) {
+ throw new IllegalArgumentException("Base, secret, and prime must be non-null and positive values.");
+ }
+ this.base = base;
+ this.secret = secret;
+ this.prime = prime;
+ }
+
+ // Method to calculate public value (g^x mod p)
+ public BigInteger calculatePublicValue() {
+ // Returns g^x mod p
+ return base.modPow(secret, prime);
+ }
+
+ // Method to calculate the shared secret key (otherPublic^secret mod p)
+ public BigInteger calculateSharedSecret(BigInteger otherPublicValue) {
+ if (otherPublicValue == null || otherPublicValue.signum() <= 0) {
+ throw new IllegalArgumentException("Other public value must be non-null and positive.");
+ }
+ // Returns b^x mod p or a^y mod p
+ return otherPublicValue.modPow(secret, prime);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/ECC.java b/src/main/java/com/thealgorithms/ciphers/ECC.java
new file mode 100644
index 000000000000..7b1e37f0e1e1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/ECC.java
@@ -0,0 +1,236 @@
+package com.thealgorithms.ciphers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * ECC - Elliptic Curve Cryptography
+ * Elliptic Curve Cryptography is a public-key cryptography method that uses the algebraic structure of
+ * elliptic curves over finite fields. ECC provides a higher level of security with smaller key sizes compared
+ * to other public-key methods like RSA, making it particularly suitable for environments where computational
+ * resources are limited, such as mobile devices and embedded systems.
+ *
+ * This class implements elliptic curve cryptography, providing encryption and decryption
+ * functionalities based on public and private key pairs.
+ *
+ * @author xuyang
+ */
+public class ECC {
+
+ private BigInteger privateKey; // Private key used for decryption
+ private ECPoint publicKey; // Public key used for encryption
+ private EllipticCurve curve; // Elliptic curve used in cryptography
+ private ECPoint basePoint; // Base point G on the elliptic curve
+
+ public ECC(int bits) {
+ generateKeys(bits); // Generates public-private key pair
+ }
+
+ public EllipticCurve getCurve() {
+ return curve; // Returns the elliptic curve
+ }
+
+ public void setCurve(EllipticCurve curve) {
+ this.curve = curve;
+ }
+
+ // Getter and Setter for private key
+ public BigInteger getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(BigInteger privateKey) {
+ this.privateKey = privateKey;
+ }
+
+ /**
+ * Encrypts the message using the public key.
+ * The message is transformed into an ECPoint and encrypted with elliptic curve operations.
+ *
+ * @param message The plain message to be encrypted
+ * @return The encrypted message as an array of ECPoints (R, S)
+ */
+ public ECPoint[] encrypt(String message) {
+ BigInteger m = new BigInteger(message.getBytes()); // Convert message to BigInteger
+ SecureRandom r = new SecureRandom(); // Generate random value for k
+ BigInteger k = new BigInteger(curve.getFieldSize(), r); // Generate random scalar k
+
+ // Calculate point r = k * G, where G is the base point
+ ECPoint rPoint = basePoint.multiply(k, curve.getP(), curve.getA());
+
+ // Calculate point s = k * publicKey + encodedMessage
+ ECPoint sPoint = publicKey.multiply(k, curve.getP(), curve.getA()).add(curve.encodeMessage(m), curve.getP(), curve.getA());
+
+ return new ECPoint[] {rPoint, sPoint}; // Return encrypted message as two ECPoints
+ }
+
+ /**
+ * Decrypts the encrypted message using the private key.
+ * The decryption process is the reverse of encryption, recovering the original message.
+ *
+ * @param encryptedMessage The encrypted message as an array of ECPoints (R, S)
+ * @return The decrypted plain message as a String
+ */
+ public String decrypt(ECPoint[] encryptedMessage) {
+ ECPoint rPoint = encryptedMessage[0]; // First part of ciphertext
+ ECPoint sPoint = encryptedMessage[1]; // Second part of ciphertext
+
+ // Perform decryption: s - r * privateKey
+ ECPoint decodedMessage = sPoint.subtract(rPoint.multiply(privateKey, curve.getP(), curve.getA()), curve.getP(), curve.getA());
+
+ BigInteger m = curve.decodeMessage(decodedMessage); // Decode the message from ECPoint
+
+ return new String(m.toByteArray()); // Convert BigInteger back to String
+ }
+
+ /**
+ * Generates a new public-private key pair for encryption and decryption.
+ *
+ * @param bits The size (in bits) of the keys to generate
+ */
+ public final void generateKeys(int bits) {
+ SecureRandom r = new SecureRandom();
+ curve = new EllipticCurve(bits); // Initialize a new elliptic curve
+ basePoint = curve.getBasePoint(); // Set the base point G
+
+ // Generate private key as a random BigInteger
+ privateKey = new BigInteger(bits, r);
+
+ // Generate public key as the point publicKey = privateKey * G
+ publicKey = basePoint.multiply(privateKey, curve.getP(), curve.getA());
+ }
+
+ /**
+ * Class representing an elliptic curve with the form y^2 = x^3 + ax + b.
+ */
+ public static class EllipticCurve {
+ private final BigInteger a; // Coefficient a in the curve equation
+ private final BigInteger b; // Coefficient b in the curve equation
+ private final BigInteger p; // Prime number p, defining the finite field
+ private final ECPoint basePoint; // Base point G on the curve
+
+ // Constructor with explicit parameters for a, b, p, and base point
+ public EllipticCurve(BigInteger a, BigInteger b, BigInteger p, ECPoint basePoint) {
+ this.a = a;
+ this.b = b;
+ this.p = p;
+ this.basePoint = basePoint;
+ }
+
+ // Constructor that randomly generates the curve parameters
+ public EllipticCurve(int bits) {
+ SecureRandom r = new SecureRandom();
+ this.p = BigInteger.probablePrime(bits, r); // Random prime p
+ this.a = new BigInteger(bits, r); // Random coefficient a
+ this.b = new BigInteger(bits, r); // Random coefficient b
+ this.basePoint = new ECPoint(BigInteger.valueOf(4), BigInteger.valueOf(8)); // Fixed base point G
+ }
+
+ public ECPoint getBasePoint() {
+ return basePoint;
+ }
+
+ public BigInteger getP() {
+ return p;
+ }
+
+ public BigInteger getA() {
+ return a;
+ }
+
+ public BigInteger getB() {
+ return b;
+ }
+
+ public int getFieldSize() {
+ return p.bitLength();
+ }
+
+ public ECPoint encodeMessage(BigInteger message) {
+ // Simple encoding of a message as an ECPoint (this is a simplified example)
+ return new ECPoint(message, message);
+ }
+
+ public BigInteger decodeMessage(ECPoint point) {
+ return point.getX(); // Decode the message from ECPoint (simplified)
+ }
+ }
+
+ /**
+ * Class representing a point on the elliptic curve.
+ */
+ public static class ECPoint {
+ private final BigInteger x; // X-coordinate of the point
+ private final BigInteger y; // Y-coordinate of the point
+
+ public ECPoint(BigInteger x, BigInteger y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public BigInteger getX() {
+ return x;
+ }
+
+ public BigInteger getY() {
+ return y;
+ }
+
+ @Override
+ public String toString() {
+ return "ECPoint(x=" + x.toString() + ", y=" + y.toString() + ")";
+ }
+
+ /**
+ * Add two points on the elliptic curve.
+ */
+ public ECPoint add(ECPoint other, BigInteger p, BigInteger a) {
+ if (this.x.equals(BigInteger.ZERO) && this.y.equals(BigInteger.ZERO)) {
+ return other; // If this point is the identity, return the other point
+ }
+ if (other.x.equals(BigInteger.ZERO) && other.y.equals(BigInteger.ZERO)) {
+ return this; // If the other point is the identity, return this point
+ }
+
+ BigInteger lambda;
+ if (this.equals(other)) {
+ // Special case: point doubling
+ lambda = this.x.pow(2).multiply(BigInteger.valueOf(3)).add(a).multiply(this.y.multiply(BigInteger.valueOf(2)).modInverse(p)).mod(p);
+ } else {
+ // General case: adding two different points
+ lambda = other.y.subtract(this.y).multiply(other.x.subtract(this.x).modInverse(p)).mod(p);
+ }
+
+ BigInteger xr = lambda.pow(2).subtract(this.x).subtract(other.x).mod(p);
+ BigInteger yr = lambda.multiply(this.x.subtract(xr)).subtract(this.y).mod(p);
+
+ return new ECPoint(xr, yr);
+ }
+
+ /**
+ * Subtract two points on the elliptic curve.
+ */
+ public ECPoint subtract(ECPoint other, BigInteger p, BigInteger a) {
+ ECPoint negOther = new ECPoint(other.x, p.subtract(other.y)); // Negate the Y coordinate
+ return this.add(negOther, p, a); // Add the negated point
+ }
+
+ /**
+ * Multiply a point by a scalar (repeated addition).
+ */
+ public ECPoint multiply(BigInteger k, BigInteger p, BigInteger a) {
+ ECPoint result = new ECPoint(BigInteger.ZERO, BigInteger.ZERO); // Identity point
+ ECPoint addend = this;
+
+ while (k.signum() > 0) {
+ if (k.testBit(0)) {
+ result = result.add(addend, p, a); // Add the current point
+ }
+ addend = addend.add(addend, p, a); // Double the point
+ k = k.shiftRight(1); // Divide k by 2
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java
new file mode 100644
index 000000000000..1d5b7110a6f3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java
@@ -0,0 +1,48 @@
+package com.thealgorithms.ciphers;
+
+public final class MonoAlphabetic {
+
+ private MonoAlphabetic() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ // Encryption method
+ public static String encrypt(String data, String key) {
+ if (!data.matches("[A-Z]+")) {
+ throw new IllegalArgumentException("Input data contains invalid characters. Only uppercase A-Z are allowed.");
+ }
+ StringBuilder sb = new StringBuilder();
+
+ // Encrypt each character
+ for (char c : data.toCharArray()) {
+ int idx = charToPos(c); // Get the index of the character
+ sb.append(key.charAt(idx)); // Map to the corresponding character in the key
+ }
+ return sb.toString();
+ }
+
+ // Decryption method
+ public static String decrypt(String data, String key) {
+ StringBuilder sb = new StringBuilder();
+
+ // Decrypt each character
+ for (char c : data.toCharArray()) {
+ int idx = key.indexOf(c); // Find the index of the character in the key
+ if (idx == -1) {
+ throw new IllegalArgumentException("Input data contains invalid characters.");
+ }
+ sb.append(posToChar(idx)); // Convert the index back to the original character
+ }
+ return sb.toString();
+ }
+
+ // Helper method: Convert a character to its position in the alphabet
+ private static int charToPos(char c) {
+ return c - 'A'; // Subtract 'A' to get position (0 for A, 1 for B, etc.)
+ }
+
+ // Helper method: Convert a position in the alphabet to a character
+ private static char posToChar(int pos) {
+ return (char) (pos + 'A'); // Add 'A' to convert position back to character
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/RSA.java b/src/main/java/com/thealgorithms/ciphers/RSA.java
index f50e501e68c8..28af1a62032a 100644
--- a/src/main/java/com/thealgorithms/ciphers/RSA.java
+++ b/src/main/java/com/thealgorithms/ciphers/RSA.java
@@ -4,7 +4,27 @@
import java.security.SecureRandom;
/**
- * @author Nguyen Duy Tiep on 23-Oct-17.
+ * RSA is an asymmetric cryptographic algorithm used for secure data encryption and decryption.
+ * It relies on a pair of keys: a public key (used for encryption) and a private key
+ * (used for decryption). The algorithm is based on the difficulty of factoring large prime numbers.
+ *
+ * This implementation includes key generation, encryption, and decryption methods that can handle both
+ * text-based messages and BigInteger inputs. For more details on RSA:
+ * RSA Cryptosystem - Wikipedia.
+ *
+ * Example Usage:
+ *
+ * RSA rsa = new RSA(1024);
+ * String encryptedMessage = rsa.encrypt("Hello RSA!");
+ * String decryptedMessage = rsa.decrypt(encryptedMessage);
+ * System.out.println(decryptedMessage); // Output: Hello RSA!
+ *
+ *
+ * Note: The key size directly affects the security and performance of the RSA algorithm.
+ * Larger keys are more secure but slower to compute.
+ *
+ * @author Nguyen Duy Tiep
+ * @version 23-Oct-17
*/
public class RSA {
@@ -12,55 +32,88 @@ public class RSA {
private BigInteger privateKey;
private BigInteger publicKey;
+ /**
+ * Constructor that generates RSA keys with the specified number of bits.
+ *
+ * @param bits The bit length of the keys to be generated. Common sizes include 512, 1024, 2048, etc.
+ */
public RSA(int bits) {
generateKeys(bits);
}
/**
- * @return encrypted message
+ * Encrypts a text message using the RSA public key.
+ *
+ * @param message The plaintext message to be encrypted.
+ * @throws IllegalArgumentException If the message is empty.
+ * @return The encrypted message represented as a String.
*/
public synchronized String encrypt(String message) {
+ if (message.isEmpty()) {
+ throw new IllegalArgumentException("Message is empty");
+ }
return (new BigInteger(message.getBytes())).modPow(publicKey, modulus).toString();
}
/**
- * @return encrypted message as big integer
+ * Encrypts a BigInteger message using the RSA public key.
+ *
+ * @param message The plaintext message as a BigInteger.
+ * @return The encrypted message as a BigInteger.
*/
public synchronized BigInteger encrypt(BigInteger message) {
return message.modPow(publicKey, modulus);
}
/**
- * @return plain message
+ * Decrypts an encrypted message (as String) using the RSA private key.
+ *
+ * @param encryptedMessage The encrypted message to be decrypted, represented as a String.
+ * @throws IllegalArgumentException If the message is empty.
+ * @return The decrypted plaintext message as a String.
*/
public synchronized String decrypt(String encryptedMessage) {
+ if (encryptedMessage.isEmpty()) {
+ throw new IllegalArgumentException("Message is empty");
+ }
return new String((new BigInteger(encryptedMessage)).modPow(privateKey, modulus).toByteArray());
}
/**
- * @return plain message as big integer
+ * Decrypts an encrypted BigInteger message using the RSA private key.
+ *
+ * @param encryptedMessage The encrypted message as a BigInteger.
+ * @return The decrypted plaintext message as a BigInteger.
*/
public synchronized BigInteger decrypt(BigInteger encryptedMessage) {
return encryptedMessage.modPow(privateKey, modulus);
}
/**
- * Generate a new public and private key set.
+ * Generates a new RSA key pair (public and private keys) with the specified bit length.
+ * Steps:
+ * 1. Generate two large prime numbers p and q.
+ * 2. Compute the modulus n = p * q.
+ * 3. Compute Euler's totient function: φ(n) = (p-1) * (q-1).
+ * 4. Choose a public key e (starting from 3) that is coprime with φ(n).
+ * 5. Compute the private key d as the modular inverse of e mod φ(n).
+ * The public key is (e, n) and the private key is (d, n).
+ *
+ * @param bits The bit length of the keys to be generated.
*/
public final synchronized void generateKeys(int bits) {
- SecureRandom r = new SecureRandom();
- BigInteger p = new BigInteger(bits / 2, 100, r);
- BigInteger q = new BigInteger(bits / 2, 100, r);
+ SecureRandom random = new SecureRandom();
+ BigInteger p = new BigInteger(bits / 2, 100, random);
+ BigInteger q = new BigInteger(bits / 2, 100, random);
modulus = p.multiply(q);
- BigInteger m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
+ BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
publicKey = BigInteger.valueOf(3L);
-
- while (m.gcd(publicKey).intValue() > 1) {
+ while (phi.gcd(publicKey).intValue() > 1) {
publicKey = publicKey.add(BigInteger.TWO);
}
- privateKey = publicKey.modInverse(m);
+ privateKey = publicKey.modInverse(phi);
}
}
diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
new file mode 100644
index 000000000000..f81252980468
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java
@@ -0,0 +1,147 @@
+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.
+ * https://en.wikipedia.org/wiki/Rail_fence_cipher
+ * @author https://github.com/Krounosity
+ */
+
+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) {
+
+ // 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 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').
+ for (int i = 0; i < rails; i++) {
+ Arrays.fill(strRail[i], '\n');
+ }
+
+ int row = 0; // Start at the first row
+ int col = 0; // Start at the first column
+
+ int i = 0;
+
+ // Fill the rail matrix with characters from the string based on the rail pattern.
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ 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.
+ // 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);
+ }
+ }
+ }
+ 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) {
+
+ // 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 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()];
+
+ int row = 0; // Start at the first row
+ int col = 0; // Start at the first column
+
+ // Mark the pattern on the rail matrix using '*'.
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ else if (row == rails - 1) {
+ down = false;
+ }
+
+ // Mark the current position in the rail matrix.
+ strRail[row][col] = '*';
+ col++; // Move to the next column.
+ // Move to the next row based on the direction.
+ if (down) {
+ row++;
+ } else {
+ row--;
+ }
+ }
+
+ int index = 0; // Index to track characters from the input string.
+ // Fill the rail matrix with characters from the input string based on the marked pattern.
+ for (int i = 0; i < rails; i++) {
+ for (int j = 0; j < str.length(); j++) {
+ if (strRail[i][j] == '*') {
+ strRail[i][j] = str.charAt(index++);
+ }
+ }
+ }
+
+ // Construct the decrypted string by following the zigzag pattern.
+ StringBuilder decryptedString = new StringBuilder();
+ row = 0; // Reset to the first row
+ col = 0; // Reset to the first column
+
+ while (col < str.length()) {
+ // Change direction to down when at the first row.
+ if (row == 0) {
+ down = true;
+ }
+ // Change direction to up when at the last row.
+ else if (row == rails - 1) {
+ down = false;
+ }
+ // Append the character from the rail matrix to the decrypted string.
+ decryptedString.append(strRail[row][col]);
+ col++; // Move to the next column.
+ // Move to the next row based on the direction.
+ if (down) {
+ row++;
+ } else {
+ row--;
+ }
+ }
+
+ return decryptedString.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/ciphers/Vigenere.java b/src/main/java/com/thealgorithms/ciphers/Vigenere.java
index 1702f1abb94c..0f117853bb85 100644
--- a/src/main/java/com/thealgorithms/ciphers/Vigenere.java
+++ b/src/main/java/com/thealgorithms/ciphers/Vigenere.java
@@ -1,16 +1,54 @@
package com.thealgorithms.ciphers;
/**
- * A Java implementation of Vigenere Cipher.
+ * A Java implementation of the Vigenère Cipher.
+ *
+ * The Vigenère Cipher is a polyalphabetic substitution cipher that uses a
+ * keyword to shift letters in the plaintext by different amounts, depending
+ * on the corresponding character in the keyword. It wraps around the alphabet,
+ * ensuring the shifts are within 'A'-'Z' or 'a'-'z'.
+ *
+ * Non-alphabetic characters (like spaces, punctuation) are kept unchanged.
+ *
+ * Encryption Example:
+ * - Plaintext: "Hello World!"
+ * - Key: "suchsecret"
+ * - Encrypted Text: "Zynsg Yfvev!"
+ *
+ * Decryption Example:
+ * - Ciphertext: "Zynsg Yfvev!"
+ * - Key: "suchsecret"
+ * - Decrypted Text: "Hello World!"
+ *
+ * Wikipedia Reference:
+ * Vigenère Cipher - Wikipedia
*
* @author straiffix
* @author beingmartinbmc
*/
public class Vigenere {
+ /**
+ * Encrypts a given message using the Vigenère Cipher with the specified key.
+ * Steps:
+ * 1. Iterate over each character in the message.
+ * 2. If the character is a letter, shift it by the corresponding character in the key.
+ * 3. Preserve the case of the letter.
+ * 4. Preserve non-alphabetic characters.
+ * 5. Move to the next character in the key (cyclic).
+ * 6. Return the encrypted message.
+ *
+ * @param message The plaintext message to encrypt.
+ * @param key The keyword used for encryption.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The encrypted message.
+ */
public String encrypt(final String message, final String key) {
- StringBuilder result = new StringBuilder();
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be empty.");
+ }
+ StringBuilder result = new StringBuilder();
int j = 0;
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
@@ -20,17 +58,35 @@ public String encrypt(final String message, final String key) {
} else {
result.append((char) ((c + key.toLowerCase().charAt(j) - 2 * 'a') % 26 + 'a'));
}
+ j = ++j % key.length();
} else {
result.append(c);
}
- j = ++j % key.length();
}
return result.toString();
}
+ /**
+ * Decrypts a given message encrypted with the Vigenère Cipher using the specified key.
+ * Steps:
+ * 1. Iterate over each character in the message.
+ * 2. If the character is a letter, shift it back by the corresponding character in the key.
+ * 3. Preserve the case of the letter.
+ * 4. Preserve non-alphabetic characters.
+ * 5. Move to the next character in the key (cyclic).
+ * 6. Return the decrypted message.
+ *
+ * @param message The encrypted message to decrypt.
+ * @param key The keyword used for decryption.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The decrypted plaintext message.
+ */
public String decrypt(final String message, final String key) {
- StringBuilder result = new StringBuilder();
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be empty.");
+ }
+ StringBuilder result = new StringBuilder();
int j = 0;
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
@@ -40,11 +96,10 @@ public String decrypt(final String message, final String key) {
} else {
result.append((char) ('z' - (25 - (c - key.toLowerCase().charAt(j))) % 26));
}
+ j = ++j % key.length();
} else {
result.append(c);
}
-
- j = ++j % key.length();
}
return result.toString();
}
diff --git a/src/main/java/com/thealgorithms/ciphers/XORCipher.java b/src/main/java/com/thealgorithms/ciphers/XORCipher.java
index c4410d8c77ba..a612ccfbcdef 100644
--- a/src/main/java/com/thealgorithms/ciphers/XORCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/XORCipher.java
@@ -5,18 +5,46 @@
import java.util.HexFormat;
/**
- * A simple implementation of XOR cipher that, given a key, allows to encrypt and decrypt a plaintext.
+ * A simple implementation of the XOR cipher that allows both encryption and decryption
+ * using a given key. This cipher works by applying the XOR bitwise operation between
+ * the bytes of the input text and the corresponding bytes of the key (repeating the key
+ * if necessary).
*
- * @author lcsjunior
+ * Usage:
+ * - Encryption: Converts plaintext into a hexadecimal-encoded ciphertext.
+ * - Decryption: Converts the hexadecimal ciphertext back into plaintext.
+ *
+ * Characteristics:
+ * - Symmetric: The same key is used for both encryption and decryption.
+ * - Simple but vulnerable: XOR encryption is insecure for real-world cryptography,
+ * especially when the same key is reused.
+ *
+ * Example:
+ * Plaintext: "Hello!"
+ * Key: "key"
+ * Encrypted: "27090c03120b"
+ * Decrypted: "Hello!"
*
+ * Reference: XOR Cipher - Wikipedia
+ *
+ * @author lcsjunior
*/
public final class XORCipher {
+ // Default character encoding for string conversion
private static final Charset CS_DEFAULT = StandardCharsets.UTF_8;
private XORCipher() {
}
+ /**
+ * Applies the XOR operation between the input bytes and the key bytes.
+ * If the key is shorter than the input, it wraps around (cyclically).
+ *
+ * @param inputBytes The input byte array (plaintext or ciphertext).
+ * @param keyBytes The key byte array used for XOR operation.
+ * @return A new byte array containing the XOR result.
+ */
public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) {
byte[] outputBytes = new byte[inputBytes.length];
for (int i = 0; i < inputBytes.length; ++i) {
@@ -25,14 +53,40 @@ public static byte[] xor(final byte[] inputBytes, final byte[] keyBytes) {
return outputBytes;
}
+ /**
+ * Encrypts the given plaintext using the XOR cipher with the specified key.
+ * The result is a hexadecimal-encoded string representing the ciphertext.
+ *
+ * @param plainText The input plaintext to encrypt.
+ * @param key The encryption key.
+ * @throws IllegalArgumentException if the key is empty.
+ * @return A hexadecimal string representing the encrypted text.
+ */
public static String encrypt(final String plainText, final String key) {
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+
byte[] plainTextBytes = plainText.getBytes(CS_DEFAULT);
byte[] keyBytes = key.getBytes(CS_DEFAULT);
byte[] xorResult = xor(plainTextBytes, keyBytes);
return HexFormat.of().formatHex(xorResult);
}
+ /**
+ * Decrypts the given ciphertext (in hexadecimal format) using the XOR cipher
+ * with the specified key. The result is the original plaintext.
+ *
+ * @param cipherText The hexadecimal string representing the encrypted text.
+ * @param key The decryption key (must be the same as the encryption key).
+ * @throws IllegalArgumentException if the key is empty.
+ * @return The decrypted plaintext.
+ */
public static String decrypt(final String cipherText, final String key) {
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("Key must not be empty");
+ }
+
byte[] cipherBytes = HexFormat.of().parseHex(cipherText);
byte[] keyBytes = key.getBytes(CS_DEFAULT);
byte[] xorResult = xor(cipherBytes, keyBytes);
diff --git a/src/main/java/com/thealgorithms/conversions/AffineConverter.java b/src/main/java/com/thealgorithms/conversions/AffineConverter.java
index a580b23f90f9..199a6dd517d5 100644
--- a/src/main/java/com/thealgorithms/conversions/AffineConverter.java
+++ b/src/main/java/com/thealgorithms/conversions/AffineConverter.java
@@ -1,23 +1,64 @@
package com.thealgorithms.conversions;
+/**
+ * A utility class to perform affine transformations of the form:
+ * y = slope * x + intercept.
+ *
+ * This class supports inversion and composition of affine transformations.
+ * It is immutable, meaning each instance represents a fixed transformation.
+ */
public final class AffineConverter {
private final double slope;
private final double intercept;
+
+ /**
+ * Constructs an AffineConverter with the given slope and intercept.
+ *
+ * @param inSlope The slope of the affine transformation.
+ * @param inIntercept The intercept (constant term) of the affine transformation.
+ * @throws IllegalArgumentException if either parameter is NaN.
+ */
public AffineConverter(final double inSlope, final double inIntercept) {
+ if (Double.isNaN(inSlope) || Double.isNaN(inIntercept)) {
+ throw new IllegalArgumentException("Slope and intercept must be valid numbers.");
+ }
slope = inSlope;
intercept = inIntercept;
}
+ /**
+ * Converts the given input value using the affine transformation:
+ * result = slope * inValue + intercept.
+ *
+ * @param inValue The input value to convert.
+ * @return The transformed value.
+ */
public double convert(final double inValue) {
return slope * inValue + intercept;
}
+ /**
+ * Returns a new AffineConverter representing the inverse of the current transformation.
+ * The inverse of y = slope * x + intercept is x = (y - intercept) / slope.
+ *
+ * @return A new AffineConverter representing the inverse transformation.
+ * @throws AssertionError if the slope is zero, as the inverse would be undefined.
+ */
public AffineConverter invert() {
- assert slope != 0.0;
+ assert slope != 0.0 : "Slope cannot be zero for inversion.";
return new AffineConverter(1.0 / slope, -intercept / slope);
}
+ /**
+ * Composes this affine transformation with another, returning a new AffineConverter.
+ * If this transformation is f(x) and the other is g(x), the result is f(g(x)).
+ *
+ * @param other Another AffineConverter to compose with.
+ * @return A new AffineConverter representing the composition of the two transformations.
+ */
public AffineConverter compose(final AffineConverter other) {
- return new AffineConverter(slope * other.slope, slope * other.intercept + intercept);
+ double newSlope = slope * other.slope;
+ double newIntercept = slope * other.intercept + intercept;
+ return new AffineConverter(newSlope, newIntercept);
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
index 4bd9c74a1751..7a9448fd8fe7 100644
--- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
+++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java
@@ -136,7 +136,7 @@ public static String base2base(String n, int b1, int b2) {
int decimalValue = 0;
int charB2;
char charB1;
- String output = "";
+ StringBuilder output = new StringBuilder();
// Go through every character of n
for (int i = 0; i < n.length(); i++) {
// store the character in charB1
@@ -167,15 +167,15 @@ public static String base2base(String n, int b1, int b2) {
// If the remainder is a digit < 10, simply add it to
// the left side of the new number.
if (decimalValue % b2 < 10) {
- output = decimalValue % b2 + output;
+ output.insert(0, decimalValue % b2);
} // If the remainder is >= 10, add a character with the
// corresponding value to the new number. (A = 10, B = 11, C = 12, ...)
else {
- output = (char) ((decimalValue % b2) + 55) + output;
+ output.insert(0, (char) ((decimalValue % b2) + 55));
}
// Divide by the new base again
decimalValue /= b2;
}
- return output;
+ return output.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/AnytoAny.java b/src/main/java/com/thealgorithms/conversions/AnytoAny.java
index 801e493032e0..e7bdbc2b79c4 100644
--- a/src/main/java/com/thealgorithms/conversions/AnytoAny.java
+++ b/src/main/java/com/thealgorithms/conversions/AnytoAny.java
@@ -1,35 +1,68 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
-// given a source number , source base, destination base, this code can give you the destination
-// number.
-// sn ,sb,db ---> ()dn . this is what we have to do .
-
+/**
+ * A utility class for converting numbers from any base to any other base.
+ *
+ * This class provides a method to convert a source number from a given base
+ * to a destination number in another base. Valid bases range from 2 to 10.
+ */
public final class AnytoAny {
private AnytoAny() {
}
- public static void main(String[] args) {
- Scanner scn = new Scanner(System.in);
- int sn = scn.nextInt();
- int sb = scn.nextInt();
- int db = scn.nextInt();
- int m = 1;
- int dec = 0;
- int dn = 0;
- while (sn != 0) {
- dec = dec + (sn % 10) * m;
- m *= sb;
- sn /= 10;
+ /**
+ * Converts a number from a source base to a destination base.
+ *
+ * @param sourceNumber The number in the source base (as an integer).
+ * @param sourceBase The base of the source number (between 2 and 10).
+ * @param destBase The base to which the number should be converted (between 2 and 10).
+ * @throws IllegalArgumentException if the bases are not between 2 and 10.
+ * @return The converted number in the destination base (as an integer).
+ */
+ public static int convertBase(int sourceNumber, int sourceBase, int destBase) {
+ if (sourceBase < 2 || sourceBase > 10 || destBase < 2 || destBase > 10) {
+ throw new IllegalArgumentException("Bases must be between 2 and 10.");
+ }
+
+ int decimalValue = toDecimal(sourceNumber, sourceBase);
+ return fromDecimal(decimalValue, destBase);
+ }
+
+ /**
+ * Converts a number from a given base to its decimal representation (base 10).
+ *
+ * @param number The number in the original base.
+ * @param base The base of the given number.
+ * @return The decimal representation of the number.
+ */
+ private static int toDecimal(int number, int base) {
+ int decimalValue = 0;
+ int multiplier = 1;
+
+ while (number != 0) {
+ decimalValue += (number % 10) * multiplier;
+ multiplier *= base;
+ number /= 10;
}
- m = 1;
- while (dec != 0) {
- dn = dn + (dec % db) * m;
- m *= 10;
- dec /= db;
+ return decimalValue;
+ }
+
+ /**
+ * Converts a decimal (base 10) number to a specified base.
+ *
+ * @param decimal The decimal number to convert.
+ * @param base The destination base for conversion.
+ * @return The number in the specified base.
+ */
+ private static int fromDecimal(int decimal, int base) {
+ int result = 0;
+ int multiplier = 1;
+
+ while (decimal != 0) {
+ result += (decimal % base) * multiplier;
+ multiplier *= 10;
+ decimal /= base;
}
- System.out.println(dn);
- scn.close();
+ return result;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/EndianConverter.java b/src/main/java/com/thealgorithms/conversions/EndianConverter.java
new file mode 100644
index 000000000000..0d69098e8255
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/EndianConverter.java
@@ -0,0 +1,47 @@
+package com.thealgorithms.conversions;
+
+/**
+ * Utility class for converting integers between big-endian and little-endian formats.
+ *
+ * Endianness defines how byte sequences represent multi-byte data types:
+ *
+ * - Big-endian: The most significant byte (MSB) comes first.
+ * - Little-endian: The least significant byte (LSB) comes first.
+ *
+ *
+ * Example conversion:
+ *
+ * - Big-endian to little-endian: {@code 0x12345678} → {@code 0x78563412}
+ * - Little-endian to big-endian: {@code 0x78563412} → {@code 0x12345678}
+ *
+ *
+ * Note: Both conversions in this utility are equivalent since reversing the bytes is symmetric.
+ *
+ * This class only supports 32-bit integers.
+ *
+ * @author Hardvan
+ */
+public final class EndianConverter {
+ private EndianConverter() {
+ }
+
+ /**
+ * Converts a 32-bit integer from big-endian to little-endian.
+ *
+ * @param value the integer in big-endian format
+ * @return the integer in little-endian format
+ */
+ public static int bigToLittleEndian(int value) {
+ return Integer.reverseBytes(value);
+ }
+
+ /**
+ * Converts a 32-bit integer from little-endian to big-endian.
+ *
+ * @param value the integer in little-endian format
+ * @return the integer in big-endian format
+ */
+ public static int littleToBigEndian(int value) {
+ return Integer.reverseBytes(value);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
index b6228488dc76..c0eb9a01ba17 100644
--- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
+++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java
@@ -1,43 +1,62 @@
package com.thealgorithms.conversions;
-// Hex [0-9],[A-F] -> Binary [0,1]
+/**
+ * Utility class for converting hexadecimal numbers to binary representation.
+ *
+ * A hexadecimal number consists of digits from {@code [0-9]} and {@code [A-F]} (case-insensitive),
+ * while binary representation uses only {@code [0, 1]}.
+ *
+ * This class provides methods to:
+ *
+ * - Convert a hexadecimal string to its binary string equivalent.
+ * - Ensure the binary output is padded to 8 bits (1 byte).
+ *
+ *
+ * Example:
+ *
+ * - {@code "A1"} → {@code "10100001"}
+ * - {@code "1"} → {@code "00000001"}
+ *
+ *
+ * This class assumes that the input hexadecimal string is valid.
+ */
public class HexaDecimalToBinary {
+
+ /**
+ * Converts a hexadecimal string to its binary string equivalent.
+ * The binary output is padded to a minimum of 8 bits (1 byte).
+ * Steps:
+ *
+ * - Convert the hexadecimal string to an integer.
+ * - Convert the integer to a binary string.
+ * - Pad the binary string to ensure it is at least 8 bits long.
+ * - Return the padded binary string.
+ *
+ *
+ * @param numHex the hexadecimal string (e.g., "A1", "7F")
+ * @throws NumberFormatException if the input string is not a valid hexadecimal number
+ * @return the binary string representation, padded to 8 bits (e.g., "10100001")
+ */
public String convert(String numHex) {
- // String a HexaDecimal:
int conHex = Integer.parseInt(numHex, 16);
- // Hex a Binary:
String binary = Integer.toBinaryString(conHex);
- // Output:
return completeDigits(binary);
}
+ /**
+ * Pads the binary string to ensure it is at least 8 bits long.
+ * If the binary string is shorter than 8 bits, it adds leading zeros.
+ *
+ * @param binNum the binary string to pad
+ * @return the padded binary string with a minimum length of 8
+ */
public String completeDigits(String binNum) {
- final int longBits = 8;
- for (int i = binNum.length(); i < longBits; i++) {
- binNum = "0" + binNum;
+ final int byteSize = 8;
+ StringBuilder binNumBuilder = new StringBuilder(binNum);
+ while (binNumBuilder.length() < byteSize) {
+ binNumBuilder.insert(0, "0");
}
+ binNum = binNumBuilder.toString();
return binNum;
}
-
- public static void main(String[] args) {
- // Testing Numbers:
- String[] hexNums = {
- "1",
- "A1",
- "ef",
- "BA",
- "AA",
- "BB",
- "19",
- "01",
- "02",
- "03",
- "04",
- };
- HexaDecimalToBinary objConvert = new HexaDecimalToBinary();
-
- for (String num : hexNums) {
- System.out.println(num + " = " + objConvert.convert(num));
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
index 003781da9d5e..2cf6024d90a3 100644
--- a/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
+++ b/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java
@@ -1,39 +1,45 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
+/**
+ * Utility class for converting a hexadecimal string to its decimal representation.
+ *
+ * A hexadecimal number uses the base-16 numeral system, with the following characters:
+ *
+ * - Digits: 0-9
+ * - Letters: A-F (case-insensitive)
+ *
+ * Each character represents a power of 16. For example:
+ *
+ * Hexadecimal "A1" = 10*16^1 + 1*16^0 = 161 (decimal)
+ *
+ *
+ * This class provides a method to perform the conversion without using built-in Java utilities.
+ */
public final class HexaDecimalToDecimal {
private HexaDecimalToDecimal() {
}
- // convert hexadecimal to decimal
+ /**
+ * Converts a hexadecimal string to its decimal integer equivalent.
+ * The input string is case-insensitive, and must contain valid hexadecimal characters [0-9, A-F].
+ *
+ * @param hex the hexadecimal string to convert
+ * @return the decimal integer representation of the input hexadecimal string
+ * @throws IllegalArgumentException if the input string contains invalid characters
+ */
public static int getHexaToDec(String hex) {
String digits = "0123456789ABCDEF";
hex = hex.toUpperCase();
int val = 0;
+
for (int i = 0; i < hex.length(); i++) {
int d = digits.indexOf(hex.charAt(i));
+ if (d == -1) {
+ throw new IllegalArgumentException("Invalid hexadecimal character: " + hex.charAt(i));
+ }
val = 16 * val + d;
}
- return val;
- }
- // Main method gets the hexadecimal input from user and converts it into Decimal output.
- public static void main(String[] args) {
- String hexaInput;
- int decOutput;
- Scanner scan = new Scanner(System.in);
-
- System.out.print("Enter Hexadecimal Number : ");
- hexaInput = scan.nextLine();
-
- // convert hexadecimal to decimal
- decOutput = getHexaToDec(hexaInput);
- /*
- Pass the string to the getHexaToDec function
- and it returns the decimal form in the variable decOutput.
- */
- System.out.println("Number in Decimal: " + decOutput);
- scan.close();
+ return val;
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/IPConverter.java b/src/main/java/com/thealgorithms/conversions/IPConverter.java
new file mode 100644
index 000000000000..765cb0201dd5
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/IPConverter.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.conversions;
+
+/**
+ * Converts an IPv4 address to its binary equivalent and vice-versa.
+ * IP to Binary: Converts an IPv4 address to its binary equivalent.
+ * Example: 127.3.4.5 -> 01111111.00000011.00000100.00000101
+ *
+ * Binary to IP: Converts a binary equivalent to an IPv4 address.
+ * Example: 01111111.00000011.00000100.00000101 -> 127.3.4.5
+ *
+ * @author Hardvan
+ */
+public final class IPConverter {
+ private IPConverter() {
+ }
+
+ /**
+ * Converts an IPv4 address to its binary equivalent.
+ * @param ip The IPv4 address to convert.
+ * @return The binary equivalent of the IPv4 address.
+ */
+ public static String ipToBinary(String ip) {
+ StringBuilder binary = new StringBuilder();
+ for (String octet : ip.split("\\.")) {
+ binary.append(octetToBinary(Integer.parseInt(octet))).append(".");
+ }
+ return binary.substring(0, binary.length() - 1);
+ }
+
+ /**
+ * Converts a single octet to its 8-bit binary representation.
+ * @param octet The octet to convert (0-255).
+ * @return The 8-bit binary representation as a String.
+ */
+ private static String octetToBinary(int octet) {
+ char[] binary = {'0', '0', '0', '0', '0', '0', '0', '0'};
+ for (int i = 7; i >= 0; i--) {
+ if ((octet & 1) == 1) {
+ binary[i] = '1';
+ }
+ octet >>>= 1;
+ }
+ return new String(binary);
+ }
+
+ /**
+ * Converts a binary equivalent to an IPv4 address.
+ * @param binary The binary equivalent to convert.
+ * @return The IPv4 address of the binary equivalent.
+ */
+ public static String binaryToIP(String binary) {
+ StringBuilder ip = new StringBuilder();
+ for (String octet : binary.split("\\.")) {
+ ip.append(Integer.parseInt(octet, 2)).append(".");
+ }
+ return ip.substring(0, ip.length() - 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/IPv6Converter.java b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java
new file mode 100644
index 000000000000..d42ffd027514
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/IPv6Converter.java
@@ -0,0 +1,98 @@
+package com.thealgorithms.conversions;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * A utility class for converting between IPv6 and IPv4 addresses.
+ *
+ * - Converts IPv4 to IPv6-mapped IPv6 address.
+ * - Extracts IPv4 address from IPv6-mapped IPv6.
+ * - Handles exceptions for invalid inputs.
+ *
+ * @author Hardvan
+ */
+public final class IPv6Converter {
+ private IPv6Converter() {
+ }
+
+ /**
+ * Converts an IPv4 address (e.g., "192.0.2.128") to an IPv6-mapped IPv6 address.
+ * Example: IPv4 "192.0.2.128" -> IPv6 "::ffff:192.0.2.128"
+ *
+ * @param ipv4Address The IPv4 address in string format.
+ * @return The corresponding IPv6-mapped IPv6 address.
+ * @throws UnknownHostException If the IPv4 address is invalid.
+ * @throws IllegalArgumentException If the IPv6 address is not a mapped IPv4 address.
+ */
+ public static String ipv4ToIpv6(String ipv4Address) throws UnknownHostException {
+ if (ipv4Address == null || ipv4Address.isEmpty()) {
+ throw new UnknownHostException("IPv4 address is empty.");
+ }
+
+ InetAddress ipv4 = InetAddress.getByName(ipv4Address);
+ byte[] ipv4Bytes = ipv4.getAddress();
+
+ // Create IPv6-mapped IPv6 address (starts with ::ffff:)
+ byte[] ipv6Bytes = new byte[16];
+ ipv6Bytes[10] = (byte) 0xff;
+ ipv6Bytes[11] = (byte) 0xff;
+ System.arraycopy(ipv4Bytes, 0, ipv6Bytes, 12, 4);
+
+ // Manually format to "::ffff:x.x.x.x" format
+ StringBuilder ipv6String = new StringBuilder("::ffff:");
+ for (int i = 12; i < 16; i++) {
+ ipv6String.append(ipv6Bytes[i] & 0xFF);
+ if (i < 15) {
+ ipv6String.append('.');
+ }
+ }
+ return ipv6String.toString();
+ }
+
+ /**
+ * Extracts the IPv4 address from an IPv6-mapped IPv6 address.
+ * Example: IPv6 "::ffff:192.0.2.128" -> IPv4 "192.0.2.128"
+ *
+ * @param ipv6Address The IPv6 address in string format.
+ * @return The extracted IPv4 address.
+ * @throws UnknownHostException If the IPv6 address is invalid or not a mapped IPv4 address.
+ */
+ public static String ipv6ToIpv4(String ipv6Address) throws UnknownHostException {
+ InetAddress ipv6 = InetAddress.getByName(ipv6Address);
+ byte[] ipv6Bytes = ipv6.getAddress();
+
+ // Check if the address is an IPv6-mapped IPv4 address
+ if (isValidIpv6MappedIpv4(ipv6Bytes)) {
+ byte[] ipv4Bytes = Arrays.copyOfRange(ipv6Bytes, 12, 16);
+ InetAddress ipv4 = InetAddress.getByAddress(ipv4Bytes);
+ return ipv4.getHostAddress();
+ } else {
+ throw new IllegalArgumentException("Not a valid IPv6-mapped IPv4 address.");
+ }
+ }
+
+ /**
+ * Helper function to check if the given byte array represents
+ * an IPv6-mapped IPv4 address (prefix 0:0:0:0:0:ffff).
+ *
+ * @param ipv6Bytes Byte array representation of the IPv6 address.
+ * @return True if the address is IPv6-mapped IPv4, otherwise false.
+ */
+ private static boolean isValidIpv6MappedIpv4(byte[] ipv6Bytes) {
+ // IPv6-mapped IPv4 addresses are 16 bytes long, with the first 10 bytes set to 0,
+ // followed by 0xff, 0xff, and the last 4 bytes representing the IPv4 address.
+ if (ipv6Bytes.length != 16) {
+ return false;
+ }
+
+ for (int i = 0; i < 10; i++) {
+ if (ipv6Bytes[i] != 0) {
+ return false;
+ }
+ }
+
+ return ipv6Bytes[10] == (byte) 0xff && ipv6Bytes[11] == (byte) 0xff;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java
index d3b938bf492d..e85c608af5d0 100644
--- a/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java
+++ b/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java
@@ -2,7 +2,28 @@
import java.util.Map;
+/**
+ * A utility class to convert integers to their English word representation.
+ *
+ * The class supports conversion of numbers from 0 to 2,147,483,647
+ * (the maximum value of a 32-bit signed integer). It divides the number
+ * into groups of three digits (thousands, millions, billions, etc.) and
+ * translates each group into words.
+ *
+ * Example Usage
+ *
+ * IntegerToEnglish.integerToEnglishWords(12345);
+ * // Output: "Twelve Thousand Three Hundred Forty Five"
+ *
+ *
+ * This class uses two maps:
+ *
+ * - BASE_NUMBERS_MAP: Holds English words for numbers 0-20, multiples of 10 up to 90, and 100.
+ * - THOUSAND_POWER_MAP: Maps powers of 1000 (e.g., Thousand, Million, Billion).
+ *
+ */
public final class IntegerToEnglish {
+
private static final Map BASE_NUMBERS_MAP = Map.ofEntries(Map.entry(0, ""), Map.entry(1, "One"), Map.entry(2, "Two"), Map.entry(3, "Three"), Map.entry(4, "Four"), Map.entry(5, "Five"), Map.entry(6, "Six"), Map.entry(7, "Seven"), Map.entry(8, "Eight"), Map.entry(9, "Nine"),
Map.entry(10, "Ten"), Map.entry(11, "Eleven"), Map.entry(12, "Twelve"), Map.entry(13, "Thirteen"), Map.entry(14, "Fourteen"), Map.entry(15, "Fifteen"), Map.entry(16, "Sixteen"), Map.entry(17, "Seventeen"), Map.entry(18, "Eighteen"), Map.entry(19, "Nineteen"), Map.entry(20, "Twenty"),
Map.entry(30, "Thirty"), Map.entry(40, "Forty"), Map.entry(50, "Fifty"), Map.entry(60, "Sixty"), Map.entry(70, "Seventy"), Map.entry(80, "Eighty"), Map.entry(90, "Ninety"), Map.entry(100, "Hundred"));
@@ -13,35 +34,46 @@ private IntegerToEnglish() {
}
/**
- converts numbers < 1000 to english words
+ * Converts numbers less than 1000 into English words.
+ *
+ * @param number the integer value (0-999) to convert
+ * @return the English word representation of the input number
*/
private static String convertToWords(int number) {
int remainder = number % 100;
-
- String result;
+ StringBuilder result = new StringBuilder();
if (remainder <= 20) {
- result = BASE_NUMBERS_MAP.get(remainder);
+ result.append(BASE_NUMBERS_MAP.get(remainder));
} else if (BASE_NUMBERS_MAP.containsKey(remainder)) {
- result = BASE_NUMBERS_MAP.get(remainder);
+ result.append(BASE_NUMBERS_MAP.get(remainder));
} else {
int tensDigit = remainder / 10;
int onesDigit = remainder % 10;
-
- result = String.format("%s %s", BASE_NUMBERS_MAP.get(tensDigit * 10), BASE_NUMBERS_MAP.get(onesDigit));
+ String tens = BASE_NUMBERS_MAP.getOrDefault(tensDigit * 10, "");
+ String ones = BASE_NUMBERS_MAP.getOrDefault(onesDigit, "");
+ result.append(tens);
+ if (ones != null && !ones.isEmpty()) {
+ result.append(" ").append(ones);
+ }
}
int hundredsDigit = number / 100;
-
if (hundredsDigit > 0) {
- result = String.format("%s %s%s", BASE_NUMBERS_MAP.get(hundredsDigit), BASE_NUMBERS_MAP.get(100), result.isEmpty() ? "" : " " + result);
+ if (result.length() > 0) {
+ result.insert(0, " ");
+ }
+ result.insert(0, String.format("%s Hundred", BASE_NUMBERS_MAP.get(hundredsDigit)));
}
- return result;
+ return result.toString().trim();
}
/**
- Only convert groups of three digit if they are non-zero
+ * Converts a non-negative integer to its English word representation.
+ *
+ * @param number the integer to convert (0-2,147,483,647)
+ * @return the English word representation of the input number
*/
public static String integerToEnglishWords(int number) {
if (number == 0) {
@@ -49,7 +81,6 @@ public static String integerToEnglishWords(int number) {
}
StringBuilder result = new StringBuilder();
-
int index = 0;
while (number > 0) {
@@ -58,23 +89,20 @@ public static String integerToEnglishWords(int number) {
if (remainder > 0) {
String subResult = convertToWords(remainder);
-
if (!subResult.isEmpty()) {
- if (!result.isEmpty()) {
- result.insert(0, subResult + " " + THOUSAND_POWER_MAP.get(index) + " ");
- } else {
- if (index > 0) {
- result = new StringBuilder(subResult + " " + THOUSAND_POWER_MAP.get(index));
- } else {
- result = new StringBuilder(subResult);
- }
+ if (index > 0) {
+ subResult += " " + THOUSAND_POWER_MAP.get(index);
+ }
+ if (result.length() > 0) {
+ result.insert(0, " ");
}
+ result.insert(0, subResult);
}
}
index++;
}
- return result.toString();
+ return result.toString().trim();
}
}
diff --git a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
index 9c031df9504d..fec437668fe6 100644
--- a/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
+++ b/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java
@@ -1,68 +1,68 @@
package com.thealgorithms.conversions;
/**
- * Converting Integers into Roman Numerals
+ * A utility class to convert integers into Roman numerals.
*
- *
- * ('I', 1); ('IV',4); ('V', 5); ('IX',9); ('X', 10); ('XL',40); ('L', 50);
- * ('XC',90); ('C', 100); ('D', 500); ('M', 1000);
+ *
Roman numerals follow these rules:
+ *
+ * - I = 1
+ * - IV = 4
+ * - V = 5
+ * - IX = 9
+ * - X = 10
+ * - XL = 40
+ * - L = 50
+ * - XC = 90
+ * - C = 100
+ * - D = 500
+ * - M = 1000
+ *
+ *
+ * Conversion is based on repeatedly subtracting the largest possible Roman numeral value
+ * from the input number until it reaches zero. For example, 1994 is converted as:
+ *
+ * 1994 -> MCMXCIV (1000 + 900 + 90 + 4)
+ *
*/
public final class IntegerToRoman {
+
+ // Array of Roman numeral values in descending order
+ private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
+
+ // Corresponding Roman numeral symbols
+ private static final String[] ALL_ROMAN_NUMBERS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
+
private IntegerToRoman() {
}
- private static final int[] ALL_ROMAN_NUMBERS_IN_ARABIC = new int[] {
- 1000,
- 900,
- 500,
- 400,
- 100,
- 90,
- 50,
- 40,
- 10,
- 9,
- 5,
- 4,
- 1,
- };
- private static final String[] ALL_ROMAN_NUMBERS = new String[] {
- "M",
- "CM",
- "D",
- "CD",
- "C",
- "XC",
- "L",
- "XL",
- "X",
- "IX",
- "V",
- "IV",
- "I",
- };
-
- // Value must be > 0
+ /**
+ * Converts an integer to its Roman numeral representation.
+ * Steps:
+ *
+ * - Iterate over the Roman numeral values in descending order
+ * - Calculate how many times a numeral fits
+ * - Append the corresponding symbol
+ * - Subtract the value from the number
+ * - Repeat until the number is zero
+ * - Return the Roman numeral representation
+ *
+ *
+ * @param num the integer value to convert (must be greater than 0)
+ * @return the Roman numeral representation of the input integer
+ * or an empty string if the input is non-positive
+ */
public static String integerToRoman(int num) {
if (num <= 0) {
return "";
}
StringBuilder builder = new StringBuilder();
-
- for (int a = 0; a < ALL_ROMAN_NUMBERS_IN_ARABIC.length; a++) {
- int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[a];
- for (int b = 0; b < times; b++) {
- builder.append(ALL_ROMAN_NUMBERS[a]);
- }
-
- num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[a];
+ for (int i = 0; i < ALL_ROMAN_NUMBERS_IN_ARABIC.length; i++) {
+ int times = num / ALL_ROMAN_NUMBERS_IN_ARABIC[i];
+ builder.append(ALL_ROMAN_NUMBERS[i].repeat(Math.max(0, times)));
+ num -= times * ALL_ROMAN_NUMBERS_IN_ARABIC[i];
}
return builder.toString();
}
-
- public static void main(String[] args) {
- System.out.println(IntegerToRoman.integerToRoman(2131));
- }
}
diff --git a/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java
new file mode 100644
index 000000000000..a3973da0c586
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java
@@ -0,0 +1,98 @@
+package com.thealgorithms.conversions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts text to Morse code and vice-versa.
+ * Text to Morse code: Each letter is separated by a space and each word is separated by a pipe (|).
+ * Example: "HELLO WORLD" -> ".... . .-.. .-.. --- | .-- --- .-. .-.. -.."
+ *
+ * Morse code to text: Each letter is separated by a space and each word is separated by a pipe (|).
+ * Example: ".... . .-.. .-.. --- | .-- --- .-. .-.. -.." -> "HELLO WORLD"
+ *
+ * Applications: Used in radio communications and algorithmic challenges.
+ *
+ * @author Hardvan
+ */
+public final class MorseCodeConverter {
+ private MorseCodeConverter() {
+ }
+
+ private static final Map MORSE_MAP = new HashMap<>();
+ private static final Map REVERSE_MAP = new HashMap<>();
+
+ static {
+ MORSE_MAP.put('A', ".-");
+ MORSE_MAP.put('B', "-...");
+ MORSE_MAP.put('C', "-.-.");
+ MORSE_MAP.put('D', "-..");
+ MORSE_MAP.put('E', ".");
+ MORSE_MAP.put('F', "..-.");
+ MORSE_MAP.put('G', "--.");
+ MORSE_MAP.put('H', "....");
+ MORSE_MAP.put('I', "..");
+ MORSE_MAP.put('J', ".---");
+ MORSE_MAP.put('K', "-.-");
+ MORSE_MAP.put('L', ".-..");
+ MORSE_MAP.put('M', "--");
+ MORSE_MAP.put('N', "-.");
+ MORSE_MAP.put('O', "---");
+ MORSE_MAP.put('P', ".--.");
+ MORSE_MAP.put('Q', "--.-");
+ MORSE_MAP.put('R', ".-.");
+ MORSE_MAP.put('S', "...");
+ MORSE_MAP.put('T', "-");
+ MORSE_MAP.put('U', "..-");
+ MORSE_MAP.put('V', "...-");
+ MORSE_MAP.put('W', ".--");
+ MORSE_MAP.put('X', "-..-");
+ MORSE_MAP.put('Y', "-.--");
+ MORSE_MAP.put('Z', "--..");
+
+ // Build reverse map for decoding
+ MORSE_MAP.forEach((k, v) -> REVERSE_MAP.put(v, k));
+ }
+
+ /**
+ * Converts text to Morse code.
+ * Each letter is separated by a space and each word is separated by a pipe (|).
+ *
+ * @param text The text to convert to Morse code.
+ * @return The Morse code representation of the text.
+ */
+ public static String textToMorse(String text) {
+ StringBuilder morse = new StringBuilder();
+ String[] words = text.toUpperCase().split(" ");
+ for (int i = 0; i < words.length; i++) {
+ for (char c : words[i].toCharArray()) {
+ morse.append(MORSE_MAP.getOrDefault(c, "")).append(" ");
+ }
+ if (i < words.length - 1) {
+ morse.append("| ");
+ }
+ }
+ return morse.toString().trim();
+ }
+
+ /**
+ * Converts Morse code to text.
+ * Each letter is separated by a space and each word is separated by a pipe (|).
+ *
+ * @param morse The Morse code to convert to text.
+ * @return The text representation of the Morse code.
+ */
+ public static String morseToText(String morse) {
+ StringBuilder text = new StringBuilder();
+ String[] words = morse.split(" \\| ");
+ for (int i = 0; i < words.length; i++) {
+ for (String code : words[i].split(" ")) {
+ text.append(REVERSE_MAP.getOrDefault(code, '?'));
+ }
+ if (i < words.length - 1) {
+ text.append(" ");
+ }
+ }
+ return text.toString();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/NumberToWords.java b/src/main/java/com/thealgorithms/conversions/NumberToWords.java
new file mode 100644
index 000000000000..e39c5b2dea86
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/NumberToWords.java
@@ -0,0 +1,100 @@
+package com.thealgorithms.conversions;
+
+import java.math.BigDecimal;
+
+/**
+ A Java-based utility for converting numeric values into their English word
+ representations. Whether you need to convert a small number, a large number
+ with millions and billions, or even a number with decimal places, this utility
+ has you covered.
+ *
+ */
+public final class NumberToWords {
+
+ private NumberToWords() {
+ }
+
+ private static final String[] UNITS = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
+
+ private static final String[] TENS = {"", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
+
+ private static final String[] POWERS = {"", "Thousand", "Million", "Billion", "Trillion"};
+
+ private static final String ZERO = "Zero";
+ private static final String POINT = " Point";
+ private static final String NEGATIVE = "Negative ";
+
+ public static String convert(BigDecimal number) {
+ if (number == null) {
+ return "Invalid Input";
+ }
+
+ // Check for negative sign
+ boolean isNegative = number.signum() < 0;
+
+ // Split the number into whole and fractional parts
+ BigDecimal[] parts = number.abs().divideAndRemainder(BigDecimal.ONE);
+ BigDecimal wholePart = parts[0]; // Keep whole part as BigDecimal
+ String fractionalPartStr = parts[1].compareTo(BigDecimal.ZERO) > 0 ? parts[1].toPlainString().substring(2) : ""; // Get fractional part only if it exists
+
+ // Convert whole part to words
+ StringBuilder result = new StringBuilder();
+ if (isNegative) {
+ result.append(NEGATIVE);
+ }
+ result.append(convertWholeNumberToWords(wholePart));
+
+ // Convert fractional part to words
+ if (!fractionalPartStr.isEmpty()) {
+ result.append(POINT);
+ for (char digit : fractionalPartStr.toCharArray()) {
+ int digitValue = Character.getNumericValue(digit);
+ result.append(" ").append(digitValue == 0 ? ZERO : UNITS[digitValue]);
+ }
+ }
+
+ return result.toString().trim();
+ }
+
+ private static String convertWholeNumberToWords(BigDecimal number) {
+ if (number.compareTo(BigDecimal.ZERO) == 0) {
+ return ZERO;
+ }
+
+ StringBuilder words = new StringBuilder();
+ int power = 0;
+
+ while (number.compareTo(BigDecimal.ZERO) > 0) {
+ // Get the last three digits
+ BigDecimal[] divisionResult = number.divideAndRemainder(BigDecimal.valueOf(1000));
+ int chunk = divisionResult[1].intValue();
+
+ if (chunk > 0) {
+ String chunkWords = convertChunk(chunk);
+ if (power > 0) {
+ words.insert(0, POWERS[power] + " ");
+ }
+ words.insert(0, chunkWords + " ");
+ }
+
+ number = divisionResult[0]; // Continue with the remaining part
+ power++;
+ }
+
+ return words.toString().trim();
+ }
+
+ private static String convertChunk(int number) {
+ String chunkWords;
+
+ if (number < 20) {
+ chunkWords = UNITS[number];
+ } else if (number < 100) {
+ chunkWords = TENS[number / 10] + (number % 10 > 0 ? " " + UNITS[number % 10] : "");
+ } else {
+ chunkWords = UNITS[number / 100] + " Hundred" + (number % 100 > 0 ? " " + convertChunk(number % 100) : "");
+ }
+
+ return chunkWords;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
index 6b01c2f65cfe..a66db97633b4 100644
--- a/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
+++ b/src/main/java/com/thealgorithms/conversions/OctalToBinary.java
@@ -1,14 +1,40 @@
package com.thealgorithms.conversions;
/**
- * Converts any Octal Number to a Binary Number
+ * A utility class to convert an octal (base-8) number into its binary (base-2) representation.
+ *
+ * This class provides methods to:
+ *
+ * - Convert an octal number to its binary equivalent
+ * - Convert individual octal digits to binary
+ *
+ *
+ * Octal to Binary Conversion:
+ * An octal number is converted to binary by converting each octal digit to its 3-bit binary equivalent.
+ * The result is a long representing the full binary equivalent of the octal number.
+ *
+ * Example Usage
+ *
+ * long binary = OctalToBinary.convertOctalToBinary(52); // Output: 101010 (52 in octal is 101010 in binary)
+ *
*
* @author Bama Charan Chhandogi
+ * @see Octal Number System
+ * @see Binary Number System
*/
-
public final class OctalToBinary {
private OctalToBinary() {
}
+
+ /**
+ * Converts an octal number to its binary representation.
+ *
+ * Each octal digit is individually converted to its 3-bit binary equivalent, and the binary
+ * digits are concatenated to form the final binary number.
+ *
+ * @param octalNumber the octal number to convert (non-negative integer)
+ * @return the binary equivalent as a long
+ */
public static long convertOctalToBinary(int octalNumber) {
long binaryNumber = 0;
int digitPosition = 1;
@@ -20,12 +46,25 @@ public static long convertOctalToBinary(int octalNumber) {
binaryNumber += binaryDigit * digitPosition;
octalNumber /= 10;
- digitPosition *= 1000; // Move to the next group of 3 binary digits
+ digitPosition *= 1000;
}
return binaryNumber;
}
+ /**
+ * Converts a single octal digit (0-7) to its binary equivalent.
+ *
+ * For example:
+ *
+ * - Octal digit 7 is converted to binary 111
+ * - Octal digit 3 is converted to binary 011
+ *
+ *
+ *
+ * @param octalDigit a single octal digit (0-7)
+ * @return the binary equivalent as a long
+ */
public static long convertOctalDigitToBinary(int octalDigit) {
long binaryDigit = 0;
int binaryMultiplier = 1;
diff --git a/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java
new file mode 100644
index 000000000000..730ce2214e2d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java
@@ -0,0 +1,84 @@
+package com.thealgorithms.conversions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts text to the NATO phonetic alphabet.
+ * Examples:
+ * "ABC" -> "Alpha Bravo Charlie"
+ * "Hello" -> "Hotel Echo Lima Lima Oscar"
+ * "123" -> "One Two Three"
+ * "A1B2C3" -> "Alpha One Bravo Two Charlie Three"
+ *
+ * @author Hardvan
+ */
+public final class PhoneticAlphabetConverter {
+ private PhoneticAlphabetConverter() {
+ }
+
+ private static final Map PHONETIC_MAP = new HashMap<>();
+
+ static {
+ PHONETIC_MAP.put('A', "Alpha");
+ PHONETIC_MAP.put('B', "Bravo");
+ PHONETIC_MAP.put('C', "Charlie");
+ PHONETIC_MAP.put('D', "Delta");
+ PHONETIC_MAP.put('E', "Echo");
+ PHONETIC_MAP.put('F', "Foxtrot");
+ PHONETIC_MAP.put('G', "Golf");
+ PHONETIC_MAP.put('H', "Hotel");
+ PHONETIC_MAP.put('I', "India");
+ PHONETIC_MAP.put('J', "Juliett");
+ PHONETIC_MAP.put('K', "Kilo");
+ PHONETIC_MAP.put('L', "Lima");
+ PHONETIC_MAP.put('M', "Mike");
+ PHONETIC_MAP.put('N', "November");
+ PHONETIC_MAP.put('O', "Oscar");
+ PHONETIC_MAP.put('P', "Papa");
+ PHONETIC_MAP.put('Q', "Quebec");
+ PHONETIC_MAP.put('R', "Romeo");
+ PHONETIC_MAP.put('S', "Sierra");
+ PHONETIC_MAP.put('T', "Tango");
+ PHONETIC_MAP.put('U', "Uniform");
+ PHONETIC_MAP.put('V', "Victor");
+ PHONETIC_MAP.put('W', "Whiskey");
+ PHONETIC_MAP.put('X', "X-ray");
+ PHONETIC_MAP.put('Y', "Yankee");
+ PHONETIC_MAP.put('Z', "Zulu");
+ PHONETIC_MAP.put('0', "Zero");
+ PHONETIC_MAP.put('1', "One");
+ PHONETIC_MAP.put('2', "Two");
+ PHONETIC_MAP.put('3', "Three");
+ PHONETIC_MAP.put('4', "Four");
+ PHONETIC_MAP.put('5', "Five");
+ PHONETIC_MAP.put('6', "Six");
+ PHONETIC_MAP.put('7', "Seven");
+ PHONETIC_MAP.put('8', "Eight");
+ PHONETIC_MAP.put('9', "Nine");
+ }
+
+ /**
+ * Converts text to the NATO phonetic alphabet.
+ * Steps:
+ * 1. Convert the text to uppercase.
+ * 2. Iterate over each character in the text.
+ * 3. Get the phonetic equivalent of the character from the map.
+ * 4. Append the phonetic equivalent to the result.
+ * 5. Append a space to separate the phonetic equivalents.
+ * 6. Return the result.
+ *
+ * @param text the text to convert
+ * @return the NATO phonetic alphabet
+ */
+ public static String textToPhonetic(String text) {
+ StringBuilder phonetic = new StringBuilder();
+ for (char c : text.toUpperCase().toCharArray()) {
+ if (Character.isWhitespace(c)) {
+ continue;
+ }
+ phonetic.append(PHONETIC_MAP.getOrDefault(c, String.valueOf(c))).append(" ");
+ }
+ return phonetic.toString().trim();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
index 1934e9b264c9..a634c720326f 100644
--- a/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
+++ b/src/main/java/com/thealgorithms/conversions/RomanToInteger.java
@@ -3,9 +3,27 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * A utility class to convert Roman numerals into integers.
+ *
+ * Roman numerals are based on seven symbols given below:
+ *
+ * - I = 1
+ * - V = 5
+ * - X = 10
+ * - L = 50
+ * - C = 100
+ * - D = 500
+ * - M = 1000
+ *
+ *
+ * If a smaller numeral appears before a larger numeral, it is subtracted.
+ * Otherwise, it is added. For example:
+ *
+ * MCMXCIV = 1000 + (1000 - 100) + (100 - 10) + (5 - 1) = 1994
+ *
+ */
public final class RomanToInteger {
- private RomanToInteger() {
- }
private static final Map ROMAN_TO_INT = new HashMap<>() {
{
@@ -19,44 +37,53 @@ private RomanToInteger() {
}
};
+ private RomanToInteger() {
+ }
+
+ /**
+ * Converts a single Roman numeral character to its integer value.
+ *
+ * @param symbol the Roman numeral character
+ * @return the corresponding integer value
+ * @throws IllegalArgumentException if the symbol is not a valid Roman numeral
+ */
private static int romanSymbolToInt(final char symbol) {
return ROMAN_TO_INT.computeIfAbsent(symbol, c -> { throw new IllegalArgumentException("Unknown Roman symbol: " + c); });
}
- // Roman Number = Roman Numerals
-
/**
- * This function convert Roman number into Integer
+ * Converts a Roman numeral string to its integer equivalent.
+ * Steps:
+ *
+ * - Iterate over the string from right to left.
+ * - For each character, convert it to an integer value.
+ * - If the current value is greater than or equal to the max previous value, add it.
+ * - Otherwise, subtract it from the sum.
+ * - Update the max previous value.
+ * - Return the sum.
+ *
*
- * @param a Roman number string
- * @return integer
+ * @param roman the Roman numeral string
+ * @return the integer value of the Roman numeral
+ * @throws IllegalArgumentException if the input contains invalid Roman characters
+ * @throws NullPointerException if the input is {@code null}
*/
- public static int romanToInt(String a) {
- a = a.toUpperCase();
- char prev = ' ';
+ public static int romanToInt(String roman) {
+ if (roman == null) {
+ throw new NullPointerException("Input cannot be null");
+ }
+ roman = roman.toUpperCase();
int sum = 0;
-
- int newPrev = 0;
- for (int i = a.length() - 1; i >= 0; i--) {
- char c = a.charAt(i);
-
- if (prev != ' ') {
- // checking current Number greater than previous or not
- newPrev = romanSymbolToInt(prev) > newPrev ? romanSymbolToInt(prev) : newPrev;
- }
-
- int currentNum = romanSymbolToInt(c);
-
- // if current number greater than prev max previous then add
- if (currentNum >= newPrev) {
- sum += currentNum;
+ int maxPrevValue = 0;
+ for (int i = roman.length() - 1; i >= 0; i--) {
+ int currentValue = romanSymbolToInt(roman.charAt(i));
+ if (currentValue >= maxPrevValue) {
+ sum += currentValue;
+ maxPrevValue = currentValue;
} else {
- // subtract upcoming number until upcoming number not greater than prev max
- sum -= currentNum;
+ sum -= currentValue;
}
-
- prev = c;
}
return sum;
diff --git a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
index 4d13b8b7fd55..30030de6c1bd 100644
--- a/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
+++ b/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java
@@ -1,7 +1,5 @@
package com.thealgorithms.conversions;
-import java.util.Scanner;
-
/**
* Converts turkish character to latin character
*
@@ -11,21 +9,12 @@ public final class TurkishToLatinConversion {
private TurkishToLatinConversion() {
}
- /**
- * Main method
- *
- * @param args Command line arguments
- */
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- System.out.println("Input the string: ");
- String b = sc.next();
- System.out.println("Converted: " + convertTurkishToLatin(b));
- sc.close();
- }
-
/**
* This method converts a turkish character to latin character.
+ * Steps:
+ * 1. Define turkish characters and their corresponding latin characters
+ * 2. Replace all turkish characters with their corresponding latin characters
+ * 3. Return the converted string
*
* @param param String paramter
* @return String
diff --git a/src/main/java/com/thealgorithms/conversions/UnitConversions.java b/src/main/java/com/thealgorithms/conversions/UnitConversions.java
index abc06a0f8863..15f74a21a17e 100644
--- a/src/main/java/com/thealgorithms/conversions/UnitConversions.java
+++ b/src/main/java/com/thealgorithms/conversions/UnitConversions.java
@@ -5,10 +5,47 @@
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
+/**
+ * A utility class to perform unit conversions between different measurement systems.
+ *
+ * Currently, the class supports temperature conversions between several scales:
+ * Celsius, Fahrenheit, Kelvin, Réaumur, Delisle, and Rankine.
+ *
+ *
Example Usage
+ *
+ * double result = UnitConversions.TEMPERATURE.convert("Celsius", "Fahrenheit", 100.0);
+ * // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ *
+ *
+ * This class makes use of an {@link UnitsConverter} that handles the conversion logic
+ * based on predefined affine transformations. These transformations include scaling factors
+ * and offsets for temperature conversions.
+ *
+ *
Temperature Scales Supported
+ *
+ * - Celsius
+ * - Fahrenheit
+ * - Kelvin
+ * - Réaumur
+ * - Delisle
+ * - Rankine
+ *
+ */
public final class UnitConversions {
private UnitConversions() {
}
+ /**
+ * A preconfigured instance of {@link UnitsConverter} for temperature conversions.
+ * The converter handles conversions between the following temperature units:
+ *
+ * - Kelvin to Celsius
+ * - Celsius to Fahrenheit
+ * - Réaumur to Celsius
+ * - Delisle to Celsius
+ * - Rankine to Kelvin
+ *
+ */
public static final UnitsConverter TEMPERATURE = new UnitsConverter(Map.ofEntries(entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15)), entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
entry(Pair.of("Réaumur", "Celsius"), new AffineConverter(5.0 / 4.0, 0.0)), entry(Pair.of("Delisle", "Celsius"), new AffineConverter(-2.0 / 3.0, 100.0)), entry(Pair.of("Rankine", "Kelvin"), new AffineConverter(5.0 / 9.0, 0.0))));
}
diff --git a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java
index 81c4d4562070..00690b2c0f9b 100644
--- a/src/main/java/com/thealgorithms/conversions/UnitsConverter.java
+++ b/src/main/java/com/thealgorithms/conversions/UnitsConverter.java
@@ -7,6 +7,43 @@
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
+/**
+ * A class that handles unit conversions using affine transformations.
+ *
+ * The {@code UnitsConverter} allows converting values between different units using
+ * pre-defined affine conversion formulas. Each conversion is represented by an
+ * {@link AffineConverter} that defines the scaling and offset for the conversion.
+ *
+ *
For each unit, both direct conversions (e.g., Celsius to Fahrenheit) and inverse
+ * conversions (e.g., Fahrenheit to Celsius) are generated automatically. It also computes
+ * transitive conversions (e.g., Celsius to Kelvin via Fahrenheit if both conversions exist).
+ *
+ *
Key features include:
+ *
+ * - Automatic handling of inverse conversions (e.g., Fahrenheit to Celsius).
+ * - Compositional conversions, meaning if conversions between A -> B and B -> C exist,
+ * it can automatically generate A -> C conversion.
+ * - Supports multiple unit systems as long as conversions are provided in pairs.
+ *
+ *
+ * Example Usage
+ *
+ * Map<Pair<String, String>, AffineConverter> basicConversions = Map.ofEntries(
+ * entry(Pair.of("Celsius", "Fahrenheit"), new AffineConverter(9.0 / 5.0, 32.0)),
+ * entry(Pair.of("Kelvin", "Celsius"), new AffineConverter(1.0, -273.15))
+ * );
+ *
+ * UnitsConverter converter = new UnitsConverter(basicConversions);
+ * double result = converter.convert("Celsius", "Fahrenheit", 100.0);
+ * // Output: 212.0 (Celsius to Fahrenheit conversion of 100°C)
+ *
+ *
+ * Exception Handling
+ *
+ * - If the input unit and output unit are the same, an {@link IllegalArgumentException} is thrown.
+ * - If a conversion between the requested units does not exist, a {@link NoSuchElementException} is thrown.
+ *
+ */
public final class UnitsConverter {
private final Map, AffineConverter> conversions;
private final Set units;
@@ -68,11 +105,29 @@ private static Set extractUnits(final Map, AffineCo
return res;
}
+ /**
+ * Constructor for {@code UnitsConverter}.
+ *
+ * Accepts a map of basic conversions and automatically generates inverse and
+ * transitive conversions.
+ *
+ * @param basicConversions the initial set of unit conversions to add.
+ */
public UnitsConverter(final Map, AffineConverter> basicConversions) {
conversions = computeAllConversions(basicConversions);
units = extractUnits(conversions);
}
+ /**
+ * Converts a value from one unit to another.
+ *
+ * @param inputUnit the unit of the input value.
+ * @param outputUnit the unit to convert the value into.
+ * @param value the value to convert.
+ * @return the converted value in the target unit.
+ * @throws IllegalArgumentException if inputUnit equals outputUnit.
+ * @throws NoSuchElementException if no conversion exists between the units.
+ */
public double convert(final String inputUnit, final String outputUnit, final double value) {
if (inputUnit.equals(outputUnit)) {
throw new IllegalArgumentException("inputUnit must be different from outputUnit.");
@@ -81,6 +136,11 @@ public double convert(final String inputUnit, final String outputUnit, final dou
return conversions.computeIfAbsent(conversionKey, k -> { throw new NoSuchElementException("No converter for: " + k); }).convert(value);
}
+ /**
+ * Retrieves the set of all units supported by this converter.
+ *
+ * @return a set of available units.
+ */
public Set availableUnits() {
return units;
}
diff --git a/src/main/java/com/thealgorithms/conversions/WordsToNumber.java b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
new file mode 100644
index 000000000000..e2b81a0f4b47
--- /dev/null
+++ b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java
@@ -0,0 +1,343 @@
+package com.thealgorithms.conversions;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ A Java-based utility for converting English word representations of numbers
+ into their numeric form. This utility supports whole numbers, decimals,
+ large values up to trillions, and even scientific notation where applicable.
+ It ensures accurate parsing while handling edge cases like negative numbers,
+ improper word placements, and ambiguous inputs.
+ *
+ */
+
+public final class WordsToNumber {
+
+ private WordsToNumber() {
+ }
+
+ private enum NumberWord {
+ ZERO("zero", 0),
+ ONE("one", 1),
+ TWO("two", 2),
+ THREE("three", 3),
+ FOUR("four", 4),
+ FIVE("five", 5),
+ SIX("six", 6),
+ SEVEN("seven", 7),
+ EIGHT("eight", 8),
+ NINE("nine", 9),
+ TEN("ten", 10),
+ ELEVEN("eleven", 11),
+ TWELVE("twelve", 12),
+ THIRTEEN("thirteen", 13),
+ FOURTEEN("fourteen", 14),
+ FIFTEEN("fifteen", 15),
+ SIXTEEN("sixteen", 16),
+ SEVENTEEN("seventeen", 17),
+ EIGHTEEN("eighteen", 18),
+ NINETEEN("nineteen", 19),
+ TWENTY("twenty", 20),
+ THIRTY("thirty", 30),
+ FORTY("forty", 40),
+ FIFTY("fifty", 50),
+ SIXTY("sixty", 60),
+ SEVENTY("seventy", 70),
+ EIGHTY("eighty", 80),
+ NINETY("ninety", 90);
+
+ private final String word;
+ private final int value;
+
+ NumberWord(String word, int value) {
+ this.word = word;
+ this.value = value;
+ }
+
+ public static Integer getValue(String word) {
+ for (NumberWord num : values()) {
+ if (word.equals(num.word)) {
+ return num.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ private enum PowerOfTen {
+ THOUSAND("thousand", new BigDecimal("1000")),
+ MILLION("million", new BigDecimal("1000000")),
+ BILLION("billion", new BigDecimal("1000000000")),
+ TRILLION("trillion", new BigDecimal("1000000000000"));
+
+ private final String word;
+ private final BigDecimal value;
+
+ PowerOfTen(String word, BigDecimal value) {
+ this.word = word;
+ this.value = value;
+ }
+
+ public static BigDecimal getValue(String word) {
+ for (PowerOfTen power : values()) {
+ if (word.equals(power.word)) {
+ return power.value;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static String convert(String numberInWords) {
+ if (numberInWords == null) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+ }
+
+ ArrayDeque wordDeque = preprocessWords(numberInWords);
+ BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque);
+
+ return completeNumber.toString();
+ }
+
+ public static BigDecimal convertToBigDecimal(String numberInWords) {
+ String conversionResult = convert(numberInWords);
+ return new BigDecimal(conversionResult);
+ }
+
+ private static ArrayDeque preprocessWords(String numberInWords) {
+ String[] wordSplitArray = numberInWords.trim().split("[ ,-]");
+ ArrayDeque wordDeque = new ArrayDeque<>();
+ for (String word : wordSplitArray) {
+ if (word.isEmpty()) {
+ continue;
+ }
+ wordDeque.add(word.toLowerCase());
+ }
+ if (wordDeque.isEmpty()) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
+ }
+ return wordDeque;
+ }
+
+ private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque wordDeque) {
+ if (wordDeque.isEmpty()) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+ }
+
+ String nextWord = wordDeque.pollFirst();
+ String afterNextWord = wordDeque.peekFirst();
+
+ wordDeque.addFirst(nextWord);
+
+ Integer number = NumberWord.getValue(nextWord);
+
+ boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen;
+ boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord));
+
+ if (!isPrevWordValid || !isNextWordValid) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
+ }
+ }
+
+ private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ if (currentChunkIsZero) {
+ currentChunk = currentChunk.add(BigDecimal.ONE);
+ }
+ return currentChunk.multiply(BigDecimal.valueOf(100));
+ }
+
+ private static void handlePowerOfTen(List chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (currentChunkIsZero || prevNumWasPowerOfTen) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ BigDecimal nextChunk = currentChunk.multiply(powerOfTen);
+
+ if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ chunks.add(nextChunk);
+ }
+
+ private static BigDecimal handleNumber(Collection chunks, BigDecimal currentChunk, String word, Integer number) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ BigDecimal bigDecimalNumber = BigDecimal.valueOf(number);
+
+ if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
+ }
+ return currentChunk.add(bigDecimalNumber);
+ }
+
+ private static void handlePoint(Collection chunks, BigDecimal currentChunk, ArrayDeque wordDeque) {
+ boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
+ if (!currentChunkIsZero) {
+ chunks.add(currentChunk);
+ }
+
+ String decimalPart = convertDecimalPart(wordDeque);
+ chunks.add(new BigDecimal(decimalPart));
+ }
+
+ private static void handleNegative(boolean isNegative) {
+ if (isNegative) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, "");
+ }
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, "");
+ }
+
+ private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque wordDeque) {
+ BigDecimal currentChunk = BigDecimal.ZERO;
+ List chunks = new ArrayList<>();
+
+ boolean isNegative = "negative".equals(wordDeque.peek());
+ if (isNegative) {
+ wordDeque.poll();
+ }
+
+ boolean prevNumWasHundred = false;
+ boolean prevNumWasPowerOfTen = false;
+
+ while (!wordDeque.isEmpty()) {
+ String word = wordDeque.poll();
+
+ switch (word) {
+ case "and" -> {
+ handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque);
+ continue;
+ }
+ case "hundred" -> {
+ currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen);
+ prevNumWasHundred = true;
+ continue;
+ }
+ default -> {
+
+ }
+ }
+ prevNumWasHundred = false;
+
+ BigDecimal powerOfTen = PowerOfTen.getValue(word);
+ if (powerOfTen != null) {
+ handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen);
+ currentChunk = BigDecimal.ZERO;
+ prevNumWasPowerOfTen = true;
+ continue;
+ }
+ prevNumWasPowerOfTen = false;
+
+ Integer number = NumberWord.getValue(word);
+ if (number != null) {
+ currentChunk = handleNumber(chunks, currentChunk, word, number);
+ continue;
+ }
+
+ switch (word) {
+ case "point" -> {
+ handlePoint(chunks, currentChunk, wordDeque);
+ currentChunk = BigDecimal.ZERO;
+ continue;
+ }
+ case "negative" -> {
+ handleNegative(isNegative);
+ }
+ default -> {
+
+ }
+ }
+
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word);
+ }
+
+ if (currentChunk.compareTo(BigDecimal.ZERO) != 0) {
+ chunks.add(currentChunk);
+ }
+
+ BigDecimal completeNumber = combineChunks(chunks);
+ return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1))
+ :
+ completeNumber;
+ }
+
+ private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) {
+ int chunkDigitCount = currentChunk.toString().length();
+ int numberDigitCount = number.toString().length();
+ return chunkDigitCount > numberDigitCount;
+ }
+
+ private static String convertDecimalPart(ArrayDeque wordDeque) {
+ StringBuilder decimalPart = new StringBuilder(".");
+
+ while (!wordDeque.isEmpty()) {
+ String word = wordDeque.poll();
+ Integer number = NumberWord.getValue(word);
+ if (number == null) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word);
+ }
+ decimalPart.append(number);
+ }
+
+ boolean missingNumbers = decimalPart.length() == 1;
+ if (missingNumbers) {
+ throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, "");
+ }
+ return decimalPart.toString();
+ }
+
+ private static BigDecimal combineChunks(List chunks) {
+ BigDecimal completeNumber = BigDecimal.ZERO;
+ for (BigDecimal chunk : chunks) {
+ completeNumber = completeNumber.add(chunk);
+ }
+ return completeNumber;
+ }
+ }
+
+ class WordsToNumberException extends RuntimeException {
+
+ @Serial private static final long serialVersionUID = 1L;
+
+ enum ErrorType {
+ NULL_INPUT("'null' or empty input provided"),
+ UNKNOWN_WORD("Unknown Word: "),
+ UNEXPECTED_WORD("Unexpected Word: "),
+ UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "),
+ MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."),
+ MULTIPLE_NEGATIVES("Multiple 'Negative's detected."),
+ INVALID_NEGATIVE("Incorrect 'negative' placement"),
+ INVALID_CONJUNCTION("Incorrect 'and' placement");
+
+ private final String message;
+
+ ErrorType(String message) {
+ this.message = message;
+ }
+
+ public String formatMessage(String details) {
+ return "Invalid Input. " + message + (details.isEmpty() ? "" : details);
+ }
+ }
+
+ public final ErrorType errorType;
+
+ WordsToNumberException(ErrorType errorType, String details) {
+ super(errorType.formatMessage(details));
+ this.errorType = errorType;
+ }
+
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+ }
diff --git a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
index 1bb143fabda1..afc3bbe40cce 100644
--- a/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
+++ b/src/main/java/com/thealgorithms/datastructures/bags/Bag.java
@@ -4,14 +4,18 @@
import java.util.NoSuchElementException;
/**
- * A collection that allows adding and iterating over elements but does not support element removal.
+ * A generic collection that allows adding and iterating over elements but does not support
+ * element removal. This class implements a simple bag data structure, which can hold duplicate
+ * elements and provides operations to check for membership and the size of the collection.
+ *
+ * Bag is not thread-safe and should not be accessed by multiple threads concurrently.
*
* @param the type of elements in this bag
*/
public class Bag implements Iterable {
- private Node firstElement; // First element in the bag
- private int size; // Number of elements in the bag
+ private Node firstElement; // Reference to the first element in the bag
+ private int size; // Count of elements in the bag
// Node class representing each element in the bag
private static final class Node {
@@ -21,6 +25,7 @@ private static final class Node {
/**
* Constructs an empty bag.
+ * This initializes the bag with zero elements.
*/
public Bag() {
firstElement = null;
@@ -30,7 +35,7 @@ public Bag() {
/**
* Checks if the bag is empty.
*
- * @return true if the bag is empty, false otherwise
+ * @return {@code true} if the bag contains no elements; {@code false} otherwise
*/
public boolean isEmpty() {
return size == 0;
@@ -39,7 +44,7 @@ public boolean isEmpty() {
/**
* Returns the number of elements in the bag.
*
- * @return the number of elements
+ * @return the number of elements currently in the bag
*/
public int size() {
return size;
@@ -48,7 +53,10 @@ public int size() {
/**
* Adds an element to the bag.
*
- * @param element the element to add
+ *
This method adds the specified element to the bag. Duplicates are allowed, and the
+ * bag will maintain the order in which elements are added.
+ *
+ * @param element the element to add; must not be {@code null}
*/
public void add(E element) {
Node newNode = new Node<>();
@@ -61,8 +69,10 @@ public void add(E element) {
/**
* Checks if the bag contains a specific element.
*
- * @param element the element to check for
- * @return true if the bag contains the element, false otherwise
+ * This method uses the {@code equals} method of the element to determine membership.
+ *
+ * @param element the element to check for; must not be {@code null}
+ * @return {@code true} if the bag contains the specified element; {@code false} otherwise
*/
public boolean contains(E element) {
for (E value : this) {
@@ -76,6 +86,8 @@ public boolean contains(E element) {
/**
* Returns an iterator over the elements in this bag.
*
+ *
The iterator provides a way to traverse the elements in the order they were added.
+ *
* @return an iterator that iterates over the elements in the bag
*/
@Override
@@ -88,19 +100,35 @@ private static class ListIterator implements Iterator {
private Node currentElement;
+ /**
+ * Constructs a ListIterator starting from the given first element.
+ *
+ * @param firstElement the first element of the bag to iterate over
+ */
ListIterator(Node firstElement) {
this.currentElement = firstElement;
}
+ /**
+ * Checks if there are more elements to iterate over.
+ *
+ * @return {@code true} if there are more elements; {@code false} otherwise
+ */
@Override
public boolean hasNext() {
return currentElement != null;
}
+ /**
+ * Returns the next element in the iteration.
+ *
+ * @return the next element in the bag
+ * @throws NoSuchElementException if there are no more elements to return
+ */
@Override
public E next() {
if (!hasNext()) {
- throw new NoSuchElementException();
+ throw new NoSuchElementException("No more elements in the bag.");
}
E element = currentElement.content;
currentElement = currentElement.nextElement;
diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
index 33ea22c3d271..a2edd3db2d8e 100644
--- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
+++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java
@@ -4,6 +4,11 @@
/**
* A generic BloomFilter implementation for probabilistic membership checking.
+ *
+ * Bloom filters are space-efficient data structures that provide a fast way to test whether an
+ * element is a member of a set. They may produce false positives, indicating an element is
+ * in the set when it is not, but they will never produce false negatives.
+ *
*
* @param The type of elements to be stored in the Bloom filter.
*/
@@ -17,10 +22,14 @@ public class BloomFilter {
* Constructs a BloomFilter with a specified number of hash functions and bit array size.
*
* @param numberOfHashFunctions the number of hash functions to use
- * @param bitArraySize the size of the bit array
+ * @param bitArraySize the size of the bit array, which determines the capacity of the filter
+ * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is less than 1
*/
@SuppressWarnings("unchecked")
public BloomFilter(int numberOfHashFunctions, int bitArraySize) {
+ if (numberOfHashFunctions < 1 || bitArraySize < 1) {
+ throw new IllegalArgumentException("Number of hash functions and bit array size must be greater than 0");
+ }
this.numberOfHashFunctions = numberOfHashFunctions;
this.bitArray = new BitSet(bitArraySize);
this.hashFunctions = new Hash[numberOfHashFunctions];
@@ -28,7 +37,7 @@ public BloomFilter(int numberOfHashFunctions, int bitArraySize) {
}
/**
- * Initializes the hash functions with unique indices.
+ * Initializes the hash functions with unique indices to ensure different hashing.
*/
private void initializeHashFunctions() {
for (int i = 0; i < numberOfHashFunctions; i++) {
@@ -38,8 +47,12 @@ private void initializeHashFunctions() {
/**
* Inserts an element into the Bloom filter.
+ *
+ * This method hashes the element using all defined hash functions and sets the corresponding
+ * bits in the bit array.
+ *
*
- * @param key the element to insert
+ * @param key the element to insert into the Bloom filter
*/
public void insert(T key) {
for (Hash hash : hashFunctions) {
@@ -50,8 +63,13 @@ public void insert(T key) {
/**
* Checks if an element might be in the Bloom filter.
+ *
+ * This method checks the bits at the positions computed by each hash function. If any of these
+ * bits are not set, the element is definitely not in the filter. If all bits are set, the element
+ * might be in the filter.
+ *
*
- * @param key the element to check
+ * @param key the element to check for membership in the Bloom filter
* @return {@code true} if the element might be in the Bloom filter, {@code false} if it is definitely not
*/
public boolean contains(T key) {
@@ -66,6 +84,9 @@ public boolean contains(T key) {
/**
* Inner class representing a hash function used by the Bloom filter.
+ *
+ * Each instance of this class represents a different hash function based on its index.
+ *
*
* @param The type of elements to be hashed.
*/
@@ -76,7 +97,7 @@ private static class Hash {
/**
* Constructs a Hash function with a specified index.
*
- * @param index the index of this hash function
+ * @param index the index of this hash function, used to create a unique hash
*/
Hash(int index) {
this.index = index;
@@ -84,9 +105,13 @@ private static class Hash {
/**
* Computes the hash of the given key.
+ *
+ * The hash value is calculated by multiplying the index of the hash function
+ * with the ASCII sum of the string representation of the key.
+ *
*
* @param key the element to hash
- * @return the hash value
+ * @return the computed hash value
*/
public int compute(T key) {
return index * asciiString(String.valueOf(key));
@@ -94,9 +119,13 @@ public int compute(T key) {
/**
* Computes the ASCII value sum of the characters in a string.
+ *
+ * This method iterates through each character of the string and accumulates
+ * their ASCII values to produce a single integer value.
+ *
*
* @param word the string to compute
- * @return the sum of ASCII values of the characters
+ * @return the sum of ASCII values of the characters in the string
*/
private int asciiString(String word) {
int sum = 0;
diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
index 15e9a0956226..b709e16fd1f6 100644
--- a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
+++ b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java
@@ -2,27 +2,62 @@
import java.util.concurrent.atomic.AtomicInteger;
+/**
+ * The {@code CircularBuffer} class implements a generic circular (or ring) buffer.
+ * A circular buffer is a fixed-size data structure that operates in a FIFO (First In, First Out) manner.
+ * The buffer allows you to overwrite old data when the buffer is full and efficiently use limited memory.
+ * When the buffer is full, adding a new item will overwrite the oldest data.
+ *
+ * @param - The type of elements stored in the circular buffer.
+ */
public class CircularBuffer
- {
private final Item[] buffer;
private final CircularPointer putPointer;
private final CircularPointer getPointer;
private final AtomicInteger size = new AtomicInteger(0);
+ /**
+ * Constructor to initialize the circular buffer with a specified size.
+ *
+ * @param size The size of the circular buffer.
+ * @throws IllegalArgumentException if the size is zero or negative.
+ */
public CircularBuffer(int size) {
+ if (size <= 0) {
+ throw new IllegalArgumentException("Buffer size must be positive");
+ }
// noinspection unchecked
this.buffer = (Item[]) new Object[size];
this.putPointer = new CircularPointer(0, size);
this.getPointer = new CircularPointer(0, size);
}
+ /**
+ * Checks if the circular buffer is empty.
+ * This method is based on the current size of the buffer.
+ *
+ * @return {@code true} if the buffer is empty, {@code false} otherwise.
+ */
public boolean isEmpty() {
return size.get() == 0;
}
+ /**
+ * Checks if the circular buffer is full.
+ * The buffer is considered full when its size equals its capacity.
+ *
+ * @return {@code true} if the buffer is full, {@code false} otherwise.
+ */
public boolean isFull() {
return size.get() == buffer.length;
}
+ /**
+ * Retrieves and removes the item at the front of the buffer (FIFO).
+ * This operation will move the {@code getPointer} forward.
+ *
+ * @return The item at the front of the buffer, or {@code null} if the buffer is empty.
+ */
public Item get() {
if (isEmpty()) {
return null;
@@ -33,31 +68,64 @@ public Item get() {
return item;
}
+ /**
+ * Adds an item to the end of the buffer (FIFO).
+ * If the buffer is full, this operation will overwrite the oldest data.
+ *
+ * @param item The item to be added.
+ * @throws IllegalArgumentException if the item is null.
+ * @return {@code true} if the item was successfully added, {@code false} if the buffer was full and the item overwrote existing data.
+ */
public boolean put(Item item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Null items are not allowed");
+ }
+
+ boolean wasEmpty = isEmpty();
if (isFull()) {
- return false;
+ getPointer.getAndIncrement(); // Move get pointer to discard oldest item
+ } else {
+ size.incrementAndGet();
}
buffer[putPointer.getAndIncrement()] = item;
- size.incrementAndGet();
- return true;
+ return wasEmpty;
}
+ /**
+ * The {@code CircularPointer} class is a helper class used to track the current index (pointer)
+ * in the circular buffer.
+ * The max value represents the capacity of the buffer.
+ * The `CircularPointer` class ensures that the pointer automatically wraps around to 0
+ * when it reaches the maximum index.
+ * This is achieved in the `getAndIncrement` method, where the pointer
+ * is incremented and then taken modulo the maximum value (`max`).
+ * This operation ensures that the pointer always stays within the bounds of the buffer.
+ */
private static class CircularPointer {
private int pointer;
private final int max;
+ /**
+ * Constructor to initialize the circular pointer.
+ *
+ * @param pointer The initial position of the pointer.
+ * @param max The maximum size (capacity) of the circular buffer.
+ */
CircularPointer(int pointer, int max) {
this.pointer = pointer;
this.max = max;
}
+ /**
+ * Increments the pointer by 1 and wraps it around to 0 if it reaches the maximum value.
+ * This ensures the pointer always stays within the buffer's bounds.
+ *
+ * @return The current pointer value before incrementing.
+ */
public int getAndIncrement() {
- if (pointer == max) {
- pointer = 0;
- }
int tmp = pointer;
- pointer++;
+ pointer = (pointer + 1) % max;
return tmp;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
index 4e233224e367..f0d8ea8f7ff3 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
@@ -6,16 +6,21 @@
/**
* The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
* An LFU cache evicts the least frequently used item when the cache reaches its capacity.
- * It keeps track of how many times each item is used and maintains a doubly linked list
- * for efficient addition and removal of items based on their frequency of use.
+ * It maintains a mapping of keys to nodes, where each node contains the key, its associated value,
+ * and a frequency count that tracks how many times the item has been accessed. A doubly linked list
+ * is used to efficiently manage the ordering of items based on their usage frequency.
*
- * @param The type of keys maintained by this cache.
- * @param The type of mapped values.
+ *
This implementation is designed to provide O(1) time complexity for both the {@code get} and
+ * {@code put} operations, which is achieved through the use of a hashmap for quick access and a
+ * doubly linked list for maintaining the order of item frequencies.
*
*
* Reference: LFU Cache - Wikipedia
*
*
+ * @param The type of keys maintained by this cache.
+ * @param The type of mapped values.
+ *
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
*/
public class LFUCache {
@@ -75,7 +80,7 @@ public LFUCache(int capacity) {
/**
* Retrieves the value associated with the given key from the cache.
- * If the key exists, the node's frequency is increased and the node is repositioned
+ * If the key exists, the node's frequency is incremented, and the node is repositioned
* in the linked list based on its updated frequency.
*
* @param key The key whose associated value is to be returned.
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
index 97818ff83351..ec39d2a6ed28 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java
@@ -4,15 +4,40 @@
import java.util.Map;
/**
- * Least recently used (LRU)
- *
- * Discards the least recently used items first. This algorithm requires keeping
- * track of what was used when, which is expensive if one wants to make sure the
- * algorithm always discards the least recently used item.
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
+ * A Least Recently Used (LRU) Cache implementation.
*
- * @param key type
- * @param value type
+ * An LRU cache is a fixed-size cache that maintains items in order of use. When the cache reaches
+ * its capacity and a new item needs to be added, it removes the least recently used item first.
+ * This implementation provides O(1) time complexity for both get and put operations.
+ *
+ * Features:
+ *
+ * - Fixed-size cache with configurable capacity
+ * - Constant time O(1) operations for get and put
+ * - Thread-unsafe - should be externally synchronized if used in concurrent environments
+ * - Supports null values but not null keys
+ *
+ *
+ * Implementation Details:
+ *
+ * - Uses a HashMap for O(1) key-value lookups
+ * - Maintains a doubly-linked list for tracking access order
+ * - The head of the list contains the least recently used item
+ * - The tail of the list contains the most recently used item
+ *
+ *
+ * Example usage:
+ *
+ * LRUCache cache = new LRUCache<>(3); // Create cache with capacity 3
+ * cache.put("A", 1); // Cache: A=1
+ * cache.put("B", 2); // Cache: A=1, B=2
+ * cache.put("C", 3); // Cache: A=1, B=2, C=3
+ * cache.get("A"); // Cache: B=2, C=3, A=1 (A moved to end)
+ * cache.put("D", 4); // Cache: C=3, A=1, D=4 (B evicted)
+ *
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of mapped values
*/
public class LRUCache {
@@ -30,6 +55,11 @@ public LRUCache(int cap) {
setCapacity(cap);
}
+ /**
+ * Returns the current capacity of the cache.
+ *
+ * @param newCapacity the new capacity of the cache
+ */
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
for (int i = data.size(); i > newCapacity; i--) {
@@ -39,6 +69,11 @@ private void setCapacity(int newCapacity) {
this.cap = newCapacity;
}
+ /**
+ * Evicts the least recently used item from the cache.
+ *
+ * @return the evicted entry
+ */
private Entry evict() {
if (head == null) {
throw new RuntimeException("cache cannot be empty!");
@@ -50,12 +85,25 @@ private Entry evict() {
return evicted;
}
+ /**
+ * Checks if the capacity is valid.
+ *
+ * @param capacity the capacity to check
+ */
private void checkCapacity(int capacity) {
if (capacity <= 0) {
throw new RuntimeException("capacity must greater than 0!");
}
}
+ /**
+ * Returns the value to which the specified key is mapped, or null if this cache contains no
+ * mapping for the key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or null if this cache contains no
+ * mapping for the key
+ */
public V get(K key) {
if (!data.containsKey(key)) {
return null;
@@ -65,6 +113,11 @@ public V get(K key) {
return entry.getValue();
}
+ /**
+ * Moves the specified entry to the end of the list.
+ *
+ * @param entry the entry to move
+ */
private void moveNodeToLast(Entry entry) {
if (tail == entry) {
return;
@@ -86,6 +139,12 @@ private void moveNodeToLast(Entry entry) {
tail = entry;
}
+ /**
+ * Associates the specified value with the specified key in this cache.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
if (data.containsKey(key)) {
final Entry existingEntry = data.get(key);
@@ -107,6 +166,11 @@ public void put(K key, V value) {
data.put(key, newEntry);
}
+ /**
+ * Adds a new entry to the end of the list.
+ *
+ * @param newEntry the entry to add
+ */
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
index 9c155be8b195..93b13e6ad654 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java
@@ -4,14 +4,17 @@
import java.util.Map;
/**
- * Most recently used (MRU)
+ * Represents a Most Recently Used (MRU) Cache.
*
- * In contrast to Least Recently Used (LRU), MRU discards the most recently used
- * items first.
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)
+ * In contrast to the Least Recently Used (LRU) strategy, the MRU caching policy
+ * evicts the most recently accessed items first. This class provides methods to
+ * store key-value pairs and manage cache eviction based on this policy.
*
- * @param key type
- * @param value type
+ * For more information, refer to:
+ * MRU on Wikipedia.
+ *
+ * @param the type of keys maintained by this cache
+ * @param the type of values associated with the keys
*/
public class MRUCache {
@@ -21,40 +24,74 @@ public class MRUCache {
private int cap;
private static final int DEFAULT_CAP = 100;
+ /**
+ * Creates an MRUCache with the default capacity.
+ */
public MRUCache() {
setCapacity(DEFAULT_CAP);
}
+ /**
+ * Creates an MRUCache with a specified capacity.
+ *
+ * @param cap the maximum number of items the cache can hold
+ */
+ public MRUCache(int cap) {
+ setCapacity(cap);
+ }
+
+ /**
+ * Sets the capacity of the cache and evicts items if the new capacity
+ * is less than the current number of items.
+ *
+ * @param newCapacity the new capacity to set
+ */
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
- for (int i = data.size(); i > newCapacity; i--) {
+ while (data.size() > newCapacity) {
Entry evicted = evict();
data.remove(evicted.getKey());
}
this.cap = newCapacity;
}
+ /**
+ * Checks if the specified capacity is valid.
+ *
+ * @param capacity the capacity to check
+ * @throws IllegalArgumentException if the capacity is less than or equal to zero
+ */
private void checkCapacity(int capacity) {
if (capacity <= 0) {
- throw new RuntimeException("capacity must greater than 0!");
+ throw new IllegalArgumentException("Capacity must be greater than 0!");
}
}
+ /**
+ * Evicts the most recently used entry from the cache.
+ *
+ * @return the evicted entry
+ * @throws RuntimeException if the cache is empty
+ */
private Entry evict() {
if (head == null) {
- throw new RuntimeException("cache cannot be empty!");
+ throw new RuntimeException("Cache cannot be empty!");
}
final Entry evicted = this.tail;
tail = evicted.getPreEntry();
- tail.setNextEntry(null);
+ if (tail != null) {
+ tail.setNextEntry(null);
+ }
evicted.setNextEntry(null);
return evicted;
}
- public MRUCache(int cap) {
- setCapacity(cap);
- }
-
+ /**
+ * Retrieves the value associated with the specified key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the specified key, or null if the key does not exist
+ */
public V get(K key) {
if (!data.containsKey(key)) {
return null;
@@ -64,11 +101,19 @@ public V get(K key) {
return entry.getValue();
}
+ /**
+ * Associates the specified value with the specified key in the cache.
+ * If the key already exists, its value is updated and the entry is moved to the most recently used position.
+ * If the cache is full, the most recently used entry is evicted before adding the new entry.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
if (data.containsKey(key)) {
- final Entry exitingEntry = data.get(key);
- exitingEntry.setValue(value);
- moveEntryToLast(exitingEntry);
+ final Entry existingEntry = data.get(key);
+ existingEntry.setValue(value);
+ moveEntryToLast(existingEntry);
return;
}
Entry newEntry;
@@ -84,6 +129,11 @@ public void put(K key, V value) {
data.put(key, newEntry);
}
+ /**
+ * Adds a new entry to the cache and updates the head and tail pointers accordingly.
+ *
+ * @param newEntry the new entry to be added
+ */
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
@@ -96,6 +146,11 @@ private void addNewEntry(Entry newEntry) {
tail = newEntry;
}
+ /**
+ * Moves the specified entry to the most recently used position in the cache.
+ *
+ * @param entry the entry to be moved
+ */
private void moveEntryToLast(Entry entry) {
if (tail == entry) {
return;
@@ -117,8 +172,14 @@ private void moveEntryToLast(Entry entry) {
tail = entry;
}
+ /**
+ * A nested class representing an entry in the cache, which holds a key-value pair
+ * and references to the previous and next entries in the linked list structure.
+ *
+ * @param the type of the key
+ * @param the type of the value
+ */
static final class Entry {
-
private Entry preEntry;
private Entry nextEntry;
private I key;
diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
index 2c6ce8a427d1..d33bd3ee84d9 100644
--- a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
+++ b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java
@@ -1,53 +1,33 @@
package com.thealgorithms.datastructures.crdt;
+import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
/**
- * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type)
- * designed for managing sets in a distributed and concurrent environment. It supports the addition and removal
- * of elements, using timestamps to determine the order of operations. The set is split into two subsets:
- * the add set for elements to be added and the remove set for elements to be removed.
+ * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data
+ * Type) designed for managing sets in a distributed and concurrent environment. It supports the
+ * addition and removal of elements, using timestamps to determine the order of operations. The set
+ * is split into two subsets: the add set for elements to be added and the remove set for elements
+ * to be removed. The LWWElementSet ensures that the most recent operation (based on the timestamp)
+ * wins in the case of concurrent operations.
*
- * @author itakurah (Niklas Hoefflin) (https://github.com/itakurah)
- * @see Conflict-free_replicated_data_type
- * @see itakurah (Niklas Hoefflin)
+ * @param The type of the elements in the LWWElementSet.
+ * @author itakurah (GitHub), Niklas Hoefflin (LinkedIn)
+ * @see Conflict free
+ * replicated data type (Wikipedia)
+ * @see A comprehensive study of
+ * Convergent and Commutative Replicated Data Types
*/
-
-class Element {
- String key;
- int timestamp;
- Bias bias;
+class LWWElementSet {
+ final Map> addSet;
+ final Map> removeSet;
/**
- * Constructs a new Element with the specified key, timestamp and bias.
- *
- * @param key The key of the element.
- * @param timestamp The timestamp associated with the element.
- * @param bias The bias of the element (ADDS or REMOVALS).
- */
- Element(String key, int timestamp, Bias bias) {
- this.key = key;
- this.timestamp = timestamp;
- this.bias = bias;
- }
-}
-
-enum Bias {
- /**
- * ADDS bias for the add set.
- * REMOVALS bias for the remove set.
- */
- ADDS,
- REMOVALS
-}
-
-class LWWElementSet {
- private final Map addSet;
- private final Map removeSet;
-
- /**
- * Constructs an empty LWWElementSet.
+ * Constructs an empty LWWElementSet. This constructor initializes the addSet and removeSet as
+ * empty HashMaps. The addSet stores elements that are added, and the removeSet stores elements
+ * that are removed.
*/
LWWElementSet() {
this.addSet = new HashMap<>();
@@ -55,84 +35,92 @@ class LWWElementSet {
}
/**
- * Adds an element to the addSet.
+ * Adds an element to the addSet with the current timestamp. This method stores the element in the
+ * addSet, ensuring that the element is added to the set with an associated timestamp that
+ * represents the time of the addition.
*
- * @param e The element to be added.
+ * @param key The key of the element to be added.
*/
- public void add(Element e) {
- addSet.put(e.key, e);
+ public void add(T key) {
+ addSet.put(key, new Element<>(key, Instant.now()));
}
/**
- * Removes an element from the removeSet.
+ * Removes an element by adding it to the removeSet with the current timestamp. This method adds
+ * the element to the removeSet, marking it as removed with the current timestamp.
*
- * @param e The element to be removed.
+ * @param key The key of the element to be removed.
*/
- public void remove(Element e) {
- if (lookup(e)) {
- removeSet.put(e.key, e);
- }
+ public void remove(T key) {
+ removeSet.put(key, new Element<>(key, Instant.now()));
}
/**
- * Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet.
+ * Checks if an element is in the LWWElementSet. An element is considered present if it exists in
+ * the addSet and either does not exist in the removeSet, or its add timestamp is later than any
+ * corresponding remove timestamp.
*
- * @param e The element to be checked.
- * @return True if the element is present, false otherwise.
+ * @param key The key of the element to be checked.
+ * @return {@code true} if the element is present in the set (i.e., its add timestamp is later
+ * than its remove timestamp, or it is not in the remove set), {@code false} otherwise (i.e.,
+ * the element has been removed or its remove timestamp is later than its add timestamp).
*/
- public boolean lookup(Element e) {
- Element inAddSet = addSet.get(e.key);
- Element inRemoveSet = removeSet.get(e.key);
+ public boolean lookup(T key) {
+ Element inAddSet = addSet.get(key);
+ Element inRemoveSet = removeSet.get(key);
- return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp));
+ return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp));
}
/**
- * Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset.
+ * Merges another LWWElementSet into this set. This method takes the union of both the add-sets
+ * and remove-sets from the two sets, resolving conflicts by keeping the element with the latest
+ * timestamp. If an element appears in both the add-set and remove-set of both sets, the one with
+ * the later timestamp will be retained.
*
- * @param other The LWWElementSet to compare.
- * @return True if the set is subset, false otherwise.
+ * @param other The LWWElementSet to merge with the current set.
*/
- public boolean compare(LWWElementSet other) {
- return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet());
+ public void merge(LWWElementSet other) {
+ for (Map.Entry> entry : other.addSet.entrySet()) {
+ addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
+ }
+ for (Map.Entry> entry : other.removeSet.entrySet()) {
+ removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict);
+ }
}
/**
- * Merges another LWWElementSet into this set by resolving conflicts based on timestamps.
+ * Resolves conflicts between two elements by selecting the one with the later timestamp. This
+ * method is used when merging two LWWElementSets to ensure that the most recent operation (based
+ * on timestamps) is kept.
*
- * @param other The LWWElementSet to merge.
+ * @param e1 The first element.
+ * @param e2 The second element.
+ * @return The element with the later timestamp.
*/
- public void merge(LWWElementSet other) {
- for (Element e : other.addSet.values()) {
- if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) {
- addSet.put(e.key, e);
- }
- }
-
- for (Element e : other.removeSet.values()) {
- if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) {
- removeSet.put(e.key, e);
- }
- }
+ private Element resolveConflict(Element e1, Element e2) {
+ return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2;
}
+}
+
+/**
+ * Represents an element in the LWWElementSet, consisting of a key and a timestamp. This class is
+ * used to store the elements in both the add and remove sets with their respective timestamps.
+ *
+ * @param The type of the key associated with the element.
+ */
+class Element {
+ T key;
+ Instant timestamp;
/**
- * Compares timestamps of two elements based on their bias (ADDS or REMOVALS).
+ * Constructs a new Element with the specified key and timestamp.
*
- * @param e The first element.
- * @param other The second element.
- * @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal.
+ * @param key The key of the element.
+ * @param timestamp The timestamp associated with the element.
*/
- public boolean compareTimestamps(Element e, Element other) {
- if (e.bias != other.bias) {
- throw new IllegalArgumentException("Invalid bias value");
- }
- Bias bias = e.bias;
- int timestampComparison = Integer.compare(e.timestamp, other.timestamp);
-
- if (timestampComparison == 0) {
- return bias != Bias.ADDS;
- }
- return timestampComparison < 0;
+ Element(T key, Instant timestamp) {
+ this.key = key;
+ this.timestamp = timestamp;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
index a5fa9cbe94e7..cd5dc580b694 100644
--- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
+++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java
@@ -10,21 +10,24 @@
import java.util.stream.StreamSupport;
/**
- * This class implements a dynamic array.
+ * This class implements a dynamic array, which can grow or shrink in size
+ * as elements are added or removed. It provides an array-like interface
+ * with methods to add, remove, and access elements, along with iterators
+ * to traverse the elements.
*
- * @param the type that each index of the array will hold
+ * @param the type of elements that this array can hold
*/
public class DynamicArray implements Iterable {
private static final int DEFAULT_CAPACITY = 16;
private int size;
- private int modCount; // Tracks structural modifications for the iterator
+ private int modCount; // Tracks structural modifications for iterator integrity
private Object[] elements;
/**
- * Constructor with initial capacity.
+ * Constructs a new DynamicArray with the specified initial capacity.
*
- * @param capacity the starting length of the desired array
+ * @param capacity the initial capacity of the array
* @throws IllegalArgumentException if the specified capacity is negative
*/
public DynamicArray(final int capacity) {
@@ -37,14 +40,15 @@ public DynamicArray(final int capacity) {
}
/**
- * No-args constructor with default capacity.
+ * Constructs a new DynamicArray with a default initial capacity.
*/
public DynamicArray() {
this(DEFAULT_CAPACITY);
}
/**
- * Adds an element to the array. If full, creates a new array with double the size.
+ * Adds an element to the end of the array. If the array is full, it
+ * creates a new array with double the size to accommodate the new element.
*
* @param element the element to be added to the array
*/
@@ -55,11 +59,11 @@ public void add(final E element) {
}
/**
- * Places an element at the desired index, expanding capacity if necessary.
+ * Places an element at the specified index, expanding capacity if necessary.
*
- * @param index the index for the element to be placed
- * @param element the element to be inserted
- * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array
+ * @param index the index at which the element is to be placed
+ * @param element the element to be inserted at the specified index
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements
*/
public void put(final int index, E element) {
if (index < 0) {
@@ -74,11 +78,11 @@ public void put(final int index, E element) {
}
/**
- * Gets the element at a given index.
+ * Retrieves the element at the specified index.
*
- * @param index the desired index of the element
+ * @param index the index of the element to retrieve
* @return the element at the specified index
- * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
*/
@SuppressWarnings("unchecked")
public E get(final int index) {
@@ -89,11 +93,11 @@ public E get(final int index) {
}
/**
- * Removes an element from the array.
+ * Removes and returns the element at the specified index.
*
* @param index the index of the element to be removed
- * @return the element removed
- * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to the number of elements in the array
+ * @return the element that was removed from the array
+ * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size
*/
public E remove(final int index) {
if (index < 0 || index >= size) {
@@ -106,16 +110,16 @@ public E remove(final int index) {
}
/**
- * Gets the size of the array.
+ * Returns the current number of elements in the array.
*
- * @return the size
+ * @return the number of elements in the array
*/
public int getSize() {
return size;
}
/**
- * Checks if the array is empty.
+ * Checks whether the array is empty.
*
* @return true if the array contains no elements, false otherwise
*/
@@ -123,10 +127,20 @@ public boolean isEmpty() {
return size == 0;
}
+ /**
+ * Returns a sequential stream with this collection as its source.
+ *
+ * @return a stream of the elements in the array
+ */
public Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
+ /**
+ * Ensures that the array has enough capacity to hold the specified number of elements.
+ *
+ * @param minCapacity the minimum capacity required
+ */
private void ensureCapacity(int minCapacity) {
if (minCapacity > elements.length) {
int newCapacity = Math.max(elements.length * 2, minCapacity);
@@ -134,6 +148,12 @@ private void ensureCapacity(int minCapacity) {
}
}
+ /**
+ * Removes the element at the specified index without resizing the array.
+ * This method shifts any subsequent elements to the left and clears the last element.
+ *
+ * @param index the index of the element to remove
+ */
private void fastRemove(int index) {
int numMoved = size - index - 1;
if (numMoved > 0) {
@@ -142,31 +162,58 @@ private void fastRemove(int index) {
elements[--size] = null; // Clear to let GC do its work
}
+ /**
+ * Returns a string representation of the array, including only the elements that are currently stored.
+ *
+ * @return a string containing the elements in the array
+ */
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elements, size));
}
+ /**
+ * Returns an iterator over the elements in this array in proper sequence.
+ *
+ * @return an Iterator over the elements in the array
+ */
@Override
public Iterator iterator() {
return new DynamicArrayIterator();
}
+ /**
+ * Private iterator class for the DynamicArray.
+ */
private final class DynamicArrayIterator implements Iterator {
private int cursor;
private int expectedModCount;
+ /**
+ * Constructs a new iterator for the dynamic array.
+ */
DynamicArrayIterator() {
this.expectedModCount = modCount;
}
+ /**
+ * Checks if there are more elements in the iteration.
+ *
+ * @return true if there are more elements, false otherwise
+ */
@Override
public boolean hasNext() {
checkForComodification();
return cursor < size;
}
+ /**
+ * Returns the next element in the iteration.
+ *
+ * @return the next element in the iteration
+ * @throws NoSuchElementException if the iteration has no more elements
+ */
@Override
@SuppressWarnings("unchecked")
public E next() {
@@ -177,22 +224,38 @@ public E next() {
return (E) elements[cursor++];
}
+ /**
+ * Removes the last element returned by this iterator.
+ *
+ * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method
+ */
@Override
public void remove() {
if (cursor <= 0) {
- throw new IllegalStateException();
+ throw new IllegalStateException("Cannot remove element before calling next()");
}
checkForComodification();
DynamicArray.this.remove(--cursor);
- expectedModCount = ++modCount;
+ expectedModCount = modCount;
}
+ /**
+ * Checks for concurrent modifications to the array during iteration.
+ *
+ * @throws ConcurrentModificationException if the array has been modified structurally
+ */
private void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
+ /**
+ * Performs the given action for each remaining element in the iterator until all elements have been processed.
+ *
+ * @param action the action to be performed for each element
+ * @throws NullPointerException if the specified action is null
+ */
@Override
public void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
index 54fb5fba5c1b..741caa59c5b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
@@ -1,25 +1,26 @@
-/*
- Time Complexity = O(E), where E is equal to the number of edges
- */
package com.thealgorithms.datastructures.graphs;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
+/**
+ * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph.
+ * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate
+ * the cost to reach the destination node.
+ * Time Complexity = O(E), where E is equal to the number of edges
+ */
public final class AStar {
private AStar() {
}
- private static class Graph {
-
- // Graph's structure can be changed only applying changes to this class.
-
+ /**
+ * Represents a graph using an adjacency list.
+ */
+ static class Graph {
private ArrayList> graph;
- // Initialise ArrayLists in Constructor
Graph(int size) {
this.graph = new ArrayList<>();
for (int i = 0; i < size; i++) {
@@ -31,15 +32,17 @@ private ArrayList getNeighbours(int from) {
return this.graph.get(from);
}
- // Graph is bidirectional, for just one direction remove second instruction of this method.
+ // Add a bidirectional edge to the graph
private void addEdge(Edge edge) {
this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight()));
this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight()));
}
}
+ /**
+ * Represents an edge in the graph with a start node, end node, and weight.
+ */
private static class Edge {
-
private int from;
private int to;
private int weight;
@@ -63,12 +66,13 @@ public int getWeight() {
}
}
- // class to iterate during the algorithm execution, and also used to return the solution.
- private static class PathAndDistance {
-
- private int distance; // distance advanced so far.
- private ArrayList path; // list of visited nodes in this path.
- private int estimated; // heuristic value associated to the last node od the path (current node).
+ /**
+ * Contains information about the path and its total distance.
+ */
+ static class PathAndDistance {
+ private int distance; // total distance from the start node
+ private ArrayList path; // list of nodes in the path
+ private int estimated; // heuristic estimate for reaching the destination
PathAndDistance(int distance, ArrayList path, int estimated) {
this.distance = distance;
@@ -87,112 +91,54 @@ public ArrayList getPath() {
public int getEstimated() {
return estimated;
}
-
- private void printSolution() {
- if (this.path != null) {
- System.out.println("Optimal path: " + this.path + ", distance: " + this.distance);
- } else {
- System.out.println("There is no path available to connect the points");
- }
- }
}
- private static void initializeGraph(Graph graph, ArrayList data) {
+ // Initializes the graph with edges defined in the input data
+ static void initializeGraph(Graph graph, List data) {
for (int i = 0; i < data.size(); i += 4) {
graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2)));
}
- /*
- .x. node
- (y) cost
- - or | or / bidirectional connection
-
- ( 98)- .7. -(86)- .4.
- |
- ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11.
- |
- . 1. -------------------- (160)
- | \ |
- (211) \ .6.
- | \ |
- . 5. (101)-.13. -(138) (115)
- | | | /
- ( 99) ( 97) | /
- | | | /
- .12. -(151)- .15. -(80)- .14. | /
- | | | | /
- ( 71) (140) (146)- .2. -(120)
- | | |
- .19. -( 75)- . 0. .10. -(75)- .3.
- | |
- (118) ( 70)
- | |
- .16. -(111)- .9.
- */
- }
-
- public static void main(String[] args) {
- // heuristic function optimistic values
- int[] heuristic = {
- 366,
- 0,
- 160,
- 242,
- 161,
- 178,
- 77,
- 151,
- 226,
- 244,
- 241,
- 234,
- 380,
- 98,
- 193,
- 253,
- 329,
- 80,
- 199,
- 374,
- };
-
- Graph graph = new Graph(20);
- ArrayList graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null,
- 15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87));
- initializeGraph(graph, graphData);
-
- PathAndDistance solution = aStar(3, 1, graph, heuristic);
- solution.printSolution();
}
+ /**
+ * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node.
+ *
+ * @param from the starting node
+ * @param to the destination node
+ * @param graph the graph representation of the problem
+ * @param heuristic the heuristic estimates for each node
+ * @return a PathAndDistance object containing the shortest path and its distance
+ */
public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) {
- // nodes are prioritised by the less value of the current distance of their paths, and the
- // estimated value
- // given by the heuristic function to reach the destination point from the current point.
+ // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination
PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
- // dummy data to start the algorithm from the beginning point
- queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0));
+ // Start with the initial node
+ queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from]));
boolean solutionFound = false;
PathAndDistance currentData = new PathAndDistance(-1, null, -1);
+
while (!queue.isEmpty() && !solutionFound) {
- currentData = queue.poll(); // first in the queue, best node so keep exploring.
- int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node.
+ currentData = queue.poll(); // get the best node from the queue
+ int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node
+
+ // Check if the destination has been reached
if (currentPosition == to) {
solutionFound = true;
} else {
for (Edge edge : graph.getNeighbours(currentPosition)) {
- if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles
+ // Avoid cycles by checking if the next node is already in the path
+ if (!currentData.getPath().contains(edge.getTo())) {
ArrayList updatedPath = new ArrayList<>(currentData.getPath());
- updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance,
- // and the heuristic function value associated to that path.
+ updatedPath.add(edge.getTo());
+
+ // Update the distance and heuristic for the new path
queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
}
}
}
}
return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1);
- // Out of while loop, if there is a solution, the current Data stores the optimal path, and
- // its distance
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
index b67c5512e622..aea2b74bd13b 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java
@@ -6,7 +6,6 @@
class Cycle {
private final int nodes;
- private final int edges;
private int[][] adjacencyMatrix;
private boolean[] visited;
ArrayList> cycles = new ArrayList>();
@@ -16,7 +15,7 @@ class Cycle {
System.out.print("Enter the no. of nodes: ");
nodes = in.nextInt();
System.out.print("Enter the no. of Edges: ");
- edges = in.nextInt();
+ final int edges = in.nextInt();
adjacencyMatrix = new int[nodes][nodes];
visited = new boolean[nodes];
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
new file mode 100644
index 000000000000..a686b808a970
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java
@@ -0,0 +1,66 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.TreeSet;
+import org.apache.commons.lang3.tuple.Pair;
+
+/**
+ * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph.
+ */
+public class DijkstraOptimizedAlgorithm {
+
+ private final int vertexCount;
+
+ /**
+ * Constructs a Dijkstra object with the given number of vertices.
+ *
+ * @param vertexCount The number of vertices in the graph.
+ */
+ public DijkstraOptimizedAlgorithm(int vertexCount) {
+ this.vertexCount = vertexCount;
+ }
+
+ /**
+ * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices.
+ *
+ * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i}
+ * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices.
+ *
+ * @param graph The graph represented as an adjacency matrix.
+ * @param source The source vertex.
+ * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}.
+ * @throws IllegalArgumentException if the source vertex is out of range.
+ */
+ public int[] run(int[][] graph, int source) {
+ if (source < 0 || source >= vertexCount) {
+ throw new IllegalArgumentException("Incorrect source");
+ }
+
+ int[] distances = new int[vertexCount];
+ boolean[] processed = new boolean[vertexCount];
+ Set> unprocessed = new TreeSet<>();
+
+ Arrays.fill(distances, Integer.MAX_VALUE);
+ Arrays.fill(processed, false);
+ distances[source] = 0;
+ unprocessed.add(Pair.of(0, source));
+
+ while (!unprocessed.isEmpty()) {
+ Pair distanceAndU = unprocessed.iterator().next();
+ unprocessed.remove(distanceAndU);
+ int u = distanceAndU.getRight();
+ processed[u] = true;
+
+ for (int v = 0; v < vertexCount; v++) {
+ if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) {
+ unprocessed.remove(Pair.of(distances[v], v));
+ distances[v] = distances[u] + graph[u][v];
+ unprocessed.add(Pair.of(distances[v], v));
+ }
+ }
+ }
+
+ return distances;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java
index 27ad96d71876..db716580d689 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java
@@ -30,7 +30,7 @@ private EdmondsBlossomAlgorithm() {
* @param vertexCount The number of vertices in the graph.
* @return A list of matched pairs of vertices.
*/
- public static List maximumMatching(List edges, int vertexCount) {
+ public static List maximumMatching(Iterable edges, int vertexCount) {
List> graph = new ArrayList<>(vertexCount);
// Initialize each vertex's adjacency list.
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
index d47ffe3aa27d..e5e673a21794 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java
@@ -1,42 +1,75 @@
package com.thealgorithms.datastructures.graphs;
-import java.util.Scanner;
-
+/**
+ * The {@code FloydWarshall} class provides an implementation of the Floyd-Warshall algorithm
+ * to compute the shortest paths between all pairs of vertices in a weighted graph.
+ * It handles both positive and negative edge weights but does not support negative cycles.
+ * The algorithm is based on dynamic programming and runs in O(V^3) time complexity,
+ * where V is the number of vertices in the graph.
+ *
+ *
+ * The distance matrix is updated iteratively to find the shortest distance between any two vertices
+ * by considering each vertex as an intermediate step.
+ *
+ *
+ * Reference: Floyd-Warshall Algorithm
+ */
public class FloydWarshall {
private int[][] distanceMatrix;
- private int numberofvertices; // number of vertices in the graph
+ private int numberofvertices;
public static final int INFINITY = 999;
+ /**
+ * Constructs a Floyd-Warshall instance for a graph with the given number of vertices.
+ * Initializes the distance matrix for the graph.
+ *
+ * @param numberofvertices The number of vertices in the graph.
+ */
public FloydWarshall(int numberofvertices) {
- distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1]; // stores the value of distance from all the possible path form the source
- // vertex to destination vertex
+ distanceMatrix = new int[numberofvertices + 1][numberofvertices + 1];
// The matrix is initialized with 0's by default
this.numberofvertices = numberofvertices;
}
- public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the distances from source to destination vertex
+ /**
+ * Executes the Floyd-Warshall algorithm to compute the shortest path between all pairs of vertices.
+ * It uses an adjacency matrix to calculate the distance matrix by considering each vertex as an intermediate point.
+ *
+ * @param adjacencyMatrix The weighted adjacency matrix representing the graph.
+ * A value of 0 means no direct edge between the vertices, except for diagonal elements which are 0 (distance to self).
+ */
+ public void floydwarshall(int[][] adjacencyMatrix) {
+ // Initialize the distance matrix with the adjacency matrix.
for (int source = 1; source <= numberofvertices; source++) {
- for (int destination = 1; destination <= numberofvertices; destination++) {
- distanceMatrix[source][destination] = adjacencyMatrix[source][destination];
- }
+ System.arraycopy(adjacencyMatrix[source], 1, distanceMatrix[source], 1, numberofvertices);
}
for (int intermediate = 1; intermediate <= numberofvertices; intermediate++) {
for (int source = 1; source <= numberofvertices; source++) {
for (int destination = 1; destination <= numberofvertices; destination++) {
- if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) { // calculated distance it get replaced as
- // new shortest distance // if the new
- // distance calculated is less then the
- // earlier shortest
+ // Update distance if a shorter path through the intermediate vertex exists.
+ if (distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination] < distanceMatrix[source][destination]) {
distanceMatrix[source][destination] = distanceMatrix[source][intermediate] + distanceMatrix[intermediate][destination];
}
}
}
}
+
+ printDistanceMatrix();
+ }
+
+ /**
+ * Prints the distance matrix representing the shortest paths between all pairs of vertices.
+ * The rows and columns correspond to the source and destination vertices.
+ */
+ private void printDistanceMatrix() {
+ // Print header for vertices
for (int source = 1; source <= numberofvertices; source++) {
System.out.print("\t" + source);
}
System.out.println();
+
+ // Print the distance matrix
for (int source = 1; source <= numberofvertices; source++) {
System.out.print(source + "\t");
for (int destination = 1; destination <= numberofvertices; destination++) {
@@ -46,27 +79,7 @@ public void floydwarshall(int[][] adjacencyMatrix) { // calculates all the dista
}
}
- public static void main(String... arg) {
- Scanner scan = new Scanner(System.in);
- System.out.println("Enter the number of vertices");
- int numberOfVertices = scan.nextInt();
- int[][] adjacencyMatrix = new int[numberOfVertices + 1][numberOfVertices + 1];
- System.out.println("Enter the Weighted Matrix for the graph");
- for (int source = 1; source <= numberOfVertices; source++) {
- for (int destination = 1; destination <= numberOfVertices; destination++) {
- adjacencyMatrix[source][destination] = scan.nextInt();
- if (source == destination) {
- adjacencyMatrix[source][destination] = 0;
- continue;
- }
- if (adjacencyMatrix[source][destination] == 0) {
- adjacencyMatrix[source][destination] = INFINITY;
- }
- }
- }
- System.out.println("The Transitive Closure of the Graph");
- FloydWarshall floydwarshall = new FloydWarshall(numberOfVertices);
- floydwarshall.floydwarshall(adjacencyMatrix);
- scan.close();
+ public Object[] getDistanceMatrix() {
+ return distanceMatrix;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java
index af2665cfaebb..b3a2053d54b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java
@@ -3,12 +3,30 @@
import java.util.LinkedList;
import java.util.Queue;
+/**
+ * This class implements the Ford-Fulkerson algorithm to compute the maximum flow
+ * in a flow network.
+ *
+ * The algorithm uses breadth-first search (BFS) to find augmenting paths from
+ * the source vertex to the sink vertex, updating the flow in the network until
+ * no more augmenting paths can be found.
+ */
public final class FordFulkerson {
private static final int INF = Integer.MAX_VALUE;
private FordFulkerson() {
}
+ /**
+ * Computes the maximum flow in a flow network using the Ford-Fulkerson algorithm.
+ *
+ * @param vertexCount the number of vertices in the flow network
+ * @param capacity a 2D array representing the capacity of edges in the network
+ * @param flow a 2D array representing the current flow in the network
+ * @param source the source vertex in the flow network
+ * @param sink the sink vertex in the flow network
+ * @return the total maximum flow from the source to the sink
+ */
public static int networkFlow(int vertexCount, int[][] capacity, int[][] flow, int source, int sink) {
int totalFlow = 0;
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
index f12e3892b1b2..5c95850c4971 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java
@@ -1,8 +1,14 @@
package com.thealgorithms.datastructures.graphs;
+import java.util.Arrays;
+
/**
- * Java program for Hamiltonian Cycle
- * wikipedia
+ * Java program to find a Hamiltonian Cycle in a graph.
+ * A Hamiltonian Cycle is a cycle that visits every vertex exactly once
+ * and returns to the starting vertex.
+ *
+ * For more details, see the
+ * Wikipedia article.
*
* @author Akshay Dubey
*/
@@ -14,30 +20,30 @@ public class HamiltonianCycle {
private int[][] graph;
/**
- * Find hamiltonian cycle for given graph G(V,E)
+ * Finds a Hamiltonian Cycle for the given graph.
*
- * @param graph Adjacency matrix of a graph G(V, E)
- * for which hamiltonian path is to be found
- * @return Array containing hamiltonian cycle
- * else returns 1D array with value -1.
+ * @param graph Adjacency matrix representing the graph G(V, E), where V is
+ * the set of vertices and E is the set of edges.
+ * @return An array representing the Hamiltonian cycle if found, otherwise an
+ * array filled with -1 indicating no Hamiltonian cycle exists.
*/
public int[] findHamiltonianCycle(int[][] graph) {
+ // Single vertex graph
+ if (graph.length == 1) {
+ return new int[] {0, 0};
+ }
+
this.vertex = graph.length;
this.cycle = new int[this.vertex + 1];
- // Initialize path array with -1 value
- for (int i = 0; i < this.cycle.length; i++) {
- this.cycle[i] = -1;
- }
+ // Initialize the cycle array with -1 to represent unvisited vertices
+ Arrays.fill(this.cycle, -1);
this.graph = graph;
-
this.cycle[0] = 0;
this.pathCount = 1;
if (!isPathFound(0)) {
- for (int i = 0; i < this.cycle.length; i++) {
- this.cycle[i] = -1;
- }
+ Arrays.fill(this.cycle, -1);
} else {
this.cycle[this.cycle.length - 1] = this.cycle[0];
}
@@ -46,11 +52,10 @@ public int[] findHamiltonianCycle(int[][] graph) {
}
/**
- * function to find paths recursively
- * Find paths recursively from given vertex
+ * Recursively searches for a Hamiltonian cycle from the given vertex.
*
- * @param vertex Vertex from which path is to be found
- * @returns true if path is found false otherwise
+ * @param vertex The current vertex from which to explore paths.
+ * @return {@code true} if a Hamiltonian cycle is found, otherwise {@code false}.
*/
public boolean isPathFound(int vertex) {
boolean isLastVertexConnectedToStart = this.graph[vertex][0] == 1 && this.pathCount == this.vertex;
@@ -58,31 +63,26 @@ public boolean isPathFound(int vertex) {
return true;
}
- /* all vertices selected but last vertex not linked to 0 **/
+ // If all vertices are visited but the last vertex is not connected to the start
if (this.pathCount == this.vertex) {
return false;
}
for (int v = 0; v < this.vertex; v++) {
- /* if connected **/
- if (this.graph[vertex][v] == 1) {
- /* add to path **/
- this.cycle[this.pathCount++] = v;
-
- /* remove connection **/
+ if (this.graph[vertex][v] == 1) { // Check if there is an edge
+ this.cycle[this.pathCount++] = v; // Add the vertex to the cycle
this.graph[vertex][v] = 0;
this.graph[v][vertex] = 0;
- /* if vertex not already selected solve recursively **/
+ // Recursively attempt to complete the cycle
if (!isPresent(v)) {
return isPathFound(v);
}
- /* restore connection **/
+ // Restore the edge if the path does not work
this.graph[vertex][v] = 1;
this.graph[v][vertex] = 1;
- /* remove path **/
this.cycle[--this.pathCount] = -1;
}
}
@@ -90,10 +90,10 @@ public boolean isPathFound(int vertex) {
}
/**
- * function to check if path is already selected
- * Check if path is already selected
+ * Checks if a vertex is already part of the current Hamiltonian path.
*
- * @param vertex Starting vertex
+ * @param vertex The vertex to check.
+ * @return {@code true} if the vertex is already in the path, otherwise {@code false}.
*/
public boolean isPresent(int vertex) {
for (int i = 0; i < pathCount - 1; i++) {
@@ -101,7 +101,6 @@ public boolean isPresent(int vertex) {
return true;
}
}
-
return false;
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java
new file mode 100644
index 000000000000..351bd5b009e8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java
@@ -0,0 +1,200 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class implements Johnson's algorithm for finding all-pairs shortest paths in a weighted,
+ * directed graph that may contain negative edge weights.
+ *
+ * Johnson's algorithm works by using the Bellman-Ford algorithm to compute a transformation of the
+ * input graph that removes all negative weights, allowing Dijkstra's algorithm to be used for
+ * efficient shortest path computations.
+ *
+ * Time Complexity: O(V^2 * log(V) + V*E)
+ * Space Complexity: O(V^2)
+ *
+ * Where V is the number of vertices and E is the number of edges in the graph.
+ *
+ * For more information, please visit {@link https://en.wikipedia.org/wiki/Johnson%27s_algorithm}
+ */
+public final class JohnsonsAlgorithm {
+
+ private static final double INF = Double.POSITIVE_INFINITY;
+
+ private JohnsonsAlgorithm() {
+ }
+
+ /**
+ * Executes Johnson's algorithm on the given graph.
+ * Steps:
+ * 1. Add a new vertex to the graph and run Bellman-Ford to compute modified weights
+ * 2. t the graph using the modified weights
+ * 3. Run Dijkstra's algorithm for each vertex to compute the shortest paths
+ * The final result is a 2D array of shortest distances between all pairs of vertices.
+ *
+ * @param graph The input graph represented as an adjacency matrix.
+ * @return A 2D array representing the shortest distances between all pairs of vertices.
+ */
+ public static double[][] johnsonAlgorithm(double[][] graph) {
+ int numVertices = graph.length;
+ double[][] edges = convertToEdgeList(graph);
+
+ double[] modifiedWeights = bellmanFord(edges, numVertices);
+
+ double[][] reweightedGraph = reweightGraph(graph, modifiedWeights);
+
+ double[][] shortestDistances = new double[numVertices][numVertices];
+ for (int source = 0; source < numVertices; source++) {
+ shortestDistances[source] = dijkstra(reweightedGraph, source, modifiedWeights);
+ }
+
+ return shortestDistances;
+ }
+
+ /**
+ * Converts the adjacency matrix representation of the graph to an edge list.
+ *
+ * @param graph The input graph as an adjacency matrix.
+ * @return An array of edges, where each edge is represented as [from, to, weight].
+ */
+ public static double[][] convertToEdgeList(double[][] graph) {
+ int numVertices = graph.length;
+ List edgeList = new ArrayList<>();
+
+ for (int i = 0; i < numVertices; i++) {
+ for (int j = 0; j < numVertices; j++) {
+ if (i != j && !Double.isInfinite(graph[i][j])) {
+ // Only add edges that are not self-loops and have a finite weight
+ edgeList.add(new double[] {i, j, graph[i][j]});
+ }
+ }
+ }
+
+ return edgeList.toArray(new double[0][]);
+ }
+
+ /**
+ * Implements the Bellman-Ford algorithm to compute the shortest paths from a new vertex
+ * to all other vertices. This is used to calculate the weight function h(v) for reweighting.
+ *
+ * @param edges The edge list of the graph.
+ * @param numVertices The number of vertices in the original graph.
+ * @return An array of modified weights for each vertex.
+ */
+ private static double[] bellmanFord(double[][] edges, int numVertices) {
+ double[] dist = new double[numVertices + 1];
+ Arrays.fill(dist, INF);
+ dist[numVertices] = 0;
+
+ // Add edges from the new vertex to all original vertices
+ double[][] allEdges = Arrays.copyOf(edges, edges.length + numVertices);
+ for (int i = 0; i < numVertices; i++) {
+ allEdges[edges.length + i] = new double[] {numVertices, i, 0};
+ }
+
+ // Relax all edges V times
+ for (int i = 0; i < numVertices; i++) {
+ for (double[] edge : allEdges) {
+ int u = (int) edge[0];
+ int v = (int) edge[1];
+ double weight = edge[2];
+ if (dist[u] != INF && dist[u] + weight < dist[v]) {
+ dist[v] = dist[u] + weight;
+ }
+ }
+ }
+
+ // Check for negative weight cycles
+ for (double[] edge : allEdges) {
+ int u = (int) edge[0];
+ int v = (int) edge[1];
+ double weight = edge[2];
+ if (dist[u] + weight < dist[v]) {
+ throw new IllegalArgumentException("Graph contains a negative weight cycle");
+ }
+ }
+
+ return Arrays.copyOf(dist, numVertices);
+ }
+
+ /**
+ * Reweights the graph using the modified weights computed by Bellman-Ford.
+ *
+ * @param graph The original graph.
+ * @param modifiedWeights The modified weights from Bellman-Ford.
+ * @return The reweighted graph.
+ */
+ public static double[][] reweightGraph(double[][] graph, double[] modifiedWeights) {
+ int numVertices = graph.length;
+ double[][] reweightedGraph = new double[numVertices][numVertices];
+
+ for (int i = 0; i < numVertices; i++) {
+ for (int j = 0; j < numVertices; j++) {
+ if (graph[i][j] != 0) {
+ // New weight = original weight + h(u) - h(v)
+ reweightedGraph[i][j] = graph[i][j] + modifiedWeights[i] - modifiedWeights[j];
+ }
+ }
+ }
+
+ return reweightedGraph;
+ }
+
+ /**
+ * Implements Dijkstra's algorithm for finding shortest paths from a source vertex.
+ *
+ * @param reweightedGraph The reweighted graph to run Dijkstra's on.
+ * @param source The source vertex.
+ * @param modifiedWeights The modified weights from Bellman-Ford.
+ * @return An array of shortest distances from the source to all other vertices.
+ */
+ public static double[] dijkstra(double[][] reweightedGraph, int source, double[] modifiedWeights) {
+ int numVertices = reweightedGraph.length;
+ double[] dist = new double[numVertices];
+ boolean[] visited = new boolean[numVertices];
+ Arrays.fill(dist, INF);
+ dist[source] = 0;
+
+ for (int count = 0; count < numVertices - 1; count++) {
+ int u = minDistance(dist, visited);
+ visited[u] = true;
+
+ for (int v = 0; v < numVertices; v++) {
+ if (!visited[v] && reweightedGraph[u][v] != 0 && dist[u] != INF && dist[u] + reweightedGraph[u][v] < dist[v]) {
+ dist[v] = dist[u] + reweightedGraph[u][v];
+ }
+ }
+ }
+
+ // Adjust distances back to the original graph weights
+ for (int i = 0; i < numVertices; i++) {
+ if (dist[i] != INF) {
+ dist[i] = dist[i] - modifiedWeights[source] + modifiedWeights[i];
+ }
+ }
+
+ return dist;
+ }
+
+ /**
+ * Finds the vertex with the minimum distance value from the set of vertices
+ * not yet included in the shortest path tree.
+ *
+ * @param dist Array of distances.
+ * @param visited Array of visited vertices.
+ * @return The index of the vertex with minimum distance.
+ */
+ public static int minDistance(double[] dist, boolean[] visited) {
+ double min = INF;
+ int minIndex = -1;
+ for (int v = 0; v < dist.length; v++) {
+ if (!visited[v] && dist[v] <= min) {
+ min = dist[v];
+ minIndex = v;
+ }
+ }
+ return minIndex;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
index d5035cf625a6..9a97bc3f4808 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java
@@ -9,96 +9,120 @@
import java.util.Set;
/**
- * A class that represents the adjaceny list of a graph
+ * A class representing the adjacency list of a directed graph. The adjacency list
+ * maintains a mapping of vertices to their adjacent vertices.
+ *
+ * @param the type of vertices, extending Comparable to ensure that vertices
+ * can be compared
*/
class AdjacencyList> {
Map> adj;
+ /**
+ * Constructor to initialize the adjacency list.
+ */
AdjacencyList() {
- adj = new LinkedHashMap>();
+ adj = new LinkedHashMap<>();
}
/**
- * This function adds an Edge to the adjaceny list
+ * Adds a directed edge from one vertex to another in the adjacency list.
+ * If the vertex does not exist, it will be added to the list.
*
- * @param from , the vertex the edge is from
- * @param to, the vertex the edge is going to
+ * @param from the starting vertex of the directed edge
+ * @param to the destination vertex of the directed edge
*/
void addEdge(E from, E to) {
- try {
- adj.get(from).add(to);
- } catch (Exception E) {
- adj.put(from, new ArrayList());
- adj.get(from).add(to);
+ if (!adj.containsKey(from)) {
+ adj.put(from, new ArrayList<>());
}
+ adj.get(from).add(to);
if (!adj.containsKey(to)) {
- adj.put(to, new ArrayList());
+ adj.put(to, new ArrayList<>());
}
}
/**
- * @param v, A vertex in a graph
- * @return returns an ArrayList of all the adjacents of vertex v
+ * Retrieves the list of adjacent vertices for a given vertex.
+ *
+ * @param v the vertex whose adjacent vertices are to be fetched
+ * @return an ArrayList of adjacent vertices for vertex v
*/
ArrayList getAdjacents(E v) {
return adj.get(v);
}
/**
- * @return returns a set of all vertices in the graph
+ * Retrieves the set of all vertices present in the graph.
+ *
+ * @return a set containing all vertices in the graph
*/
Set getVertices() {
return adj.keySet();
}
}
+/**
+ * A class that performs topological sorting on a directed graph using Kahn's algorithm.
+ *
+ * @param the type of vertices, extending Comparable to ensure that vertices
+ * can be compared
+ */
class TopologicalSort> {
AdjacencyList graph;
Map inDegree;
+ /**
+ * Constructor to initialize the topological sorting class with a given graph.
+ *
+ * @param graph the directed graph represented as an adjacency list
+ */
TopologicalSort(AdjacencyList graph) {
this.graph = graph;
}
/**
- * Calculates the in degree of all vertices
+ * Calculates the in-degree of all vertices in the graph. The in-degree is
+ * the number of edges directed into a vertex.
*/
void calculateInDegree() {
inDegree = new HashMap<>();
for (E vertex : graph.getVertices()) {
- if (!inDegree.containsKey(vertex)) {
- inDegree.put(vertex, 0);
- }
+ inDegree.putIfAbsent(vertex, 0);
for (E adjacent : graph.getAdjacents(vertex)) {
- try {
- inDegree.put(adjacent, inDegree.get(adjacent) + 1);
- } catch (Exception e) {
- inDegree.put(adjacent, 1);
- }
+ inDegree.put(adjacent, inDegree.getOrDefault(adjacent, 0) + 1);
}
}
}
/**
- * Returns an ArrayList with vertices arranged in topological order
+ * Returns an ArrayList containing the vertices of the graph arranged in
+ * topological order. Topological sorting ensures that for any directed edge
+ * (u, v), vertex u appears before vertex v in the ordering.
+ *
+ * @return an ArrayList of vertices in topological order
+ * @throws IllegalStateException if the graph contains a cycle
*/
ArrayList topSortOrder() {
calculateInDegree();
- Queue q = new LinkedList();
+ Queue q = new LinkedList<>();
- for (final var entry : inDegree.entrySet()) {
+ for (var entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
q.add(entry.getKey());
}
}
ArrayList answer = new ArrayList<>();
+ int processedVertices = 0;
while (!q.isEmpty()) {
E current = q.poll();
answer.add(current);
+ processedVertices++;
+
for (E adjacent : graph.getAdjacents(current)) {
inDegree.put(adjacent, inDegree.get(adjacent) - 1);
if (inDegree.get(adjacent) == 0) {
@@ -107,12 +131,16 @@ ArrayList topSortOrder() {
}
}
+ if (processedVertices != graph.getVertices().size()) {
+ throw new IllegalStateException("Graph contains a cycle, topological sort not possible");
+ }
+
return answer;
}
}
/**
- * A driver class that sorts a given graph in topological order.
+ * A driver class that sorts a given graph in topological order using Kahn's algorithm.
*/
public final class KahnsAlgorithm {
private KahnsAlgorithm() {
@@ -130,7 +158,7 @@ public static void main(String[] args) {
TopologicalSort topSort = new TopologicalSort<>(graph);
- // Printing the order
+ // Printing the topological order
for (String s : topSort.topSortOrder()) {
System.out.print(s + " ");
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
index c5f15839f997..78a184f042b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java
@@ -5,82 +5,91 @@
import java.util.Stack;
/**
- * Java program that implements Kosaraju Algorithm.
- * @author Shivanagouda S A
+ * This class implements the Kosaraju Algorithm to find all the Strongly Connected Components (SCCs)
+ * of a directed graph. Kosaraju's algorithm runs in linear time and leverages the concept that
+ * the SCCs of a directed graph remain the same in its transpose (reverse) graph.
+ *
*
- * Kosaraju algorithm is a linear time algorithm to find the strongly connected components of a
-directed graph, which, from here onwards will be referred by SCC. It leverages the fact that the
-transpose graph (same graph with all the edges reversed) has exactly the same SCCs as the original
-graph.
-
- * A graph is said to be strongly connected if every vertex is reachable from every other vertex.
-The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
-connected. Single node is always a SCC.
-
- * Example:
-
-0 <--- 2 -------> 3 -------- > 4 ---- > 7
-| ^ | ^ ^
-| / | \ /
-| / | \ /
-v / v \ /
-1 5 --> 6
-
-For the above graph, the SCC list goes as follows:
-0, 1, 2
-3
-4, 5, 6
-7
-
-We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
-
-{@summary}
- * Kosaraju Algorithm:
-1. Perform DFS traversal of the graph. Push node to stack before returning. This gives edges
-sorted by lowest finish time.
-2. Find the transpose graph by reversing the edges.
-3. Pop nodes one by one from the stack and again to DFS on the modified graph.
-
-The transpose graph of the above graph:
-0 ---> 2 <------- 3 <------- 4 <------ 7
-^ / ^ \ /
-| / | \ /
-| / | \ /
-| v | v v
-1 5 <--- 6
-
-We can observe that this graph has the same SCC as that of original graph.
-
+ * A strongly connected component (SCC) of a directed graph is a subgraph where every vertex
+ * is reachable from every other vertex in the subgraph. The Kosaraju algorithm is particularly
+ * efficient for finding SCCs because it performs two Depth First Search (DFS) passes on the
+ * graph and its transpose.
+ *
+ *
+ * Algorithm:
+ *
+ * - Perform DFS on the original graph and push nodes to a stack in the order of their finishing time.
+ * - Generate the transpose (reversed edges) of the original graph.
+ * - Perform DFS on the transpose graph, using the stack from the first DFS. Each DFS run on the transpose graph gives a SCC.
+ *
+ *
+ * Example Graph:
+ *
+ * 0 <--- 2 -------> 3 -------- > 4 ---- > 7
+ * | ^ | ^ ^
+ * | / | \ /
+ * | / | \ /
+ * v / v \ /
+ * 1 5 --> 6
+ *
+ *
+ * SCCs in the example:
+ *
+ * - {0, 1, 2}
+ * - {3}
+ * - {4, 5, 6}
+ * - {7}
+ *
+ *
+ * The order of nodes in an SCC does not matter because every node in an SCC is reachable from every other node within the same SCC.
+ *
+ * Graph Transpose Example:
+ *
+ * 0 ---> 2 <------- 3 <------- 4 <------ 7
+ * ^ / ^ \ /
+ * | / | \ /
+ * | / | \ /
+ * | v | v v
+ * 1 5 <--- 6
+ *
+ *
+ * The SCCs of this transpose graph are the same as the original graph.
*/
-
public class Kosaraju {
- // Sort edges according to lowest finish time
- Stack stack = new Stack();
+ // Stack to sort edges by the lowest finish time (used in the first DFS)
+ private final Stack stack = new Stack<>();
- // Store each component
+ // Store each strongly connected component
private List scc = new ArrayList<>();
- // All the strongly connected components
- private List> sccsList = new ArrayList<>();
+ // List of all SCCs
+ private final List> sccsList = new ArrayList<>();
/**
+ * Main function to perform Kosaraju's Algorithm.
+ * Steps:
+ * 1. Sort nodes by the lowest finishing time
+ * 2. Create the transpose (reverse edges) of the original graph
+ * 3. Find SCCs by performing DFS on the transpose graph
+ * 4. Return the list of SCCs
*
- * @param v Node count
- * @param list Adjacency list of graph
- * @return List of SCCs
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the directed graph
+ * @return a list of SCCs where each SCC is a list of vertices
*/
public List> kosaraju(int v, List> list) {
-
sortEdgesByLowestFinishTime(v, list);
-
List> transposeGraph = createTransposeMatrix(v, list);
-
findStronglyConnectedComponents(v, transposeGraph);
-
return sccsList;
}
+ /**
+ * Performs DFS on the original graph to sort nodes by their finishing times.
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the original graph
+ */
private void sortEdgesByLowestFinishTime(int v, List> list) {
int[] vis = new int[v];
for (int i = 0; i < v; i++) {
@@ -90,8 +99,14 @@ private void sortEdgesByLowestFinishTime(int v, List> list) {
}
}
+ /**
+ * Creates the transpose (reverse) of the original graph.
+ * @param v the number of vertices in the graph
+ * @param list the adjacency list representing the original graph
+ * @return the adjacency list representing the transposed graph
+ */
private List> createTransposeMatrix(int v, List> list) {
- var transposeGraph = new ArrayList>(v);
+ List> transposeGraph = new ArrayList<>(v);
for (int i = 0; i < v; i++) {
transposeGraph.add(new ArrayList<>());
}
@@ -104,14 +119,14 @@ private List> createTransposeMatrix(int v, List> lis
}
/**
- *
- * @param v Node count
- * @param transposeGraph Transpose of the given adjacency list
+ * Finds the strongly connected components (SCCs) by performing DFS on the transposed graph.
+ * @param v the number of vertices in the graph
+ * @param transposeGraph the adjacency list representing the transposed graph
*/
public void findStronglyConnectedComponents(int v, List> transposeGraph) {
int[] vis = new int[v];
while (!stack.isEmpty()) {
- var node = stack.pop();
+ int node = stack.pop();
if (vis[node] == 0) {
dfs2(node, vis, transposeGraph);
sccsList.add(scc);
@@ -120,7 +135,12 @@ public void findStronglyConnectedComponents(int v, List> transpose
}
}
- // Dfs to store the nodes in order of lowest finish time
+ /**
+ * Performs DFS on the original graph and pushes nodes onto the stack in order of their finish time.
+ * @param node the current node being visited
+ * @param vis array to keep track of visited nodes
+ * @param list the adjacency list of the graph
+ */
private void dfs(int node, int[] vis, List> list) {
vis[node] = 1;
for (Integer neighbour : list.get(node)) {
@@ -131,7 +151,12 @@ private void dfs(int node, int[] vis, List> list) {
stack.push(node);
}
- // Dfs to find all the nodes of each strongly connected component
+ /**
+ * Performs DFS on the transposed graph to find the strongly connected components.
+ * @param node the current node being visited
+ * @param vis array to keep track of visited nodes
+ * @param list the adjacency list of the transposed graph
+ */
private void dfs2(int node, int[] vis, List> list) {
vis[node] = 1;
for (Integer neighbour : list.get(node)) {
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java
index eb5b65d5c0d4..25c4548daa7a 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java
@@ -1,27 +1,34 @@
package com.thealgorithms.datastructures.graphs;
-// Problem -> Connect all the edges with the minimum cost.
-// Possible Solution -> Kruskal Algorithm (KA), KA finds the minimum-spanning-tree, which means, the
-// group of edges with the minimum sum of their weights that connect the whole graph.
-// The graph needs to be connected, because if there are nodes impossible to reach, there are no
-// edges that could connect every node in the graph.
-// KA is a Greedy Algorithm, because edges are analysed based on their weights, that is why a
-// Priority Queue is used, to take first those less weighted.
-// This implementations below has some changes compared to conventional ones, but they are explained
-// all along the code.
import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
+/**
+ * The Kruskal class implements Kruskal's Algorithm to find the Minimum Spanning Tree (MST)
+ * of a connected, undirected graph. The algorithm constructs the MST by selecting edges
+ * with the least weight, ensuring no cycles are formed, and using union-find to track the
+ * connected components.
+ *
+ * Key Features:
+ *
+ * - The graph is represented using an adjacency list, where each node points to a set of edges.
+ * - Each edge is processed in ascending order of weight using a priority queue.
+ * - The algorithm stops when all nodes are connected or no more edges are available.
+ *
+ *
+ * Time Complexity: O(E log V), where E is the number of edges and V is the number of vertices.
+ */
public class Kruskal {
- // Complexity: O(E log V) time, where E is the number of edges in the graph and V is the number
- // of vertices
- private static class Edge {
+ /**
+ * Represents an edge in the graph with a source, destination, and weight.
+ */
+ static class Edge {
- private int from;
- private int to;
- private int weight;
+ int from;
+ int to;
+ int weight;
Edge(int from, int to, int weight) {
this.from = from;
@@ -30,51 +37,30 @@ private static class Edge {
}
}
- private static void addEdge(HashSet[] graph, int from, int to, int weight) {
+ /**
+ * Adds an edge to the graph.
+ *
+ * @param graph the adjacency list representing the graph
+ * @param from the source vertex of the edge
+ * @param to the destination vertex of the edge
+ * @param weight the weight of the edge
+ */
+ static void addEdge(HashSet[] graph, int from, int to, int weight) {
graph[from].add(new Edge(from, to, weight));
}
- public static void main(String[] args) {
- HashSet[] graph = new HashSet[7];
- for (int i = 0; i < graph.length; i++) {
- graph[i] = new HashSet<>();
- }
- addEdge(graph, 0, 1, 2);
- addEdge(graph, 0, 2, 3);
- addEdge(graph, 0, 3, 3);
- addEdge(graph, 1, 2, 4);
- addEdge(graph, 2, 3, 5);
- addEdge(graph, 1, 4, 3);
- addEdge(graph, 2, 4, 1);
- addEdge(graph, 3, 5, 7);
- addEdge(graph, 4, 5, 8);
- addEdge(graph, 5, 6, 9);
-
- System.out.println("Initial Graph: ");
- for (int i = 0; i < graph.length; i++) {
- for (Edge edge : graph[i]) {
- System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to);
- }
- }
-
- Kruskal k = new Kruskal();
- HashSet[] solGraph = k.kruskal(graph);
-
- System.out.println("\nMinimal Graph: ");
- for (int i = 0; i < solGraph.length; i++) {
- for (Edge edge : solGraph[i]) {
- System.out.println(i + " <-- weight " + edge.weight + " --> " + edge.to);
- }
- }
- }
-
+ /**
+ * Kruskal's algorithm to find the Minimum Spanning Tree (MST) of a graph.
+ *
+ * @param graph the adjacency list representing the input graph
+ * @return the adjacency list representing the MST
+ */
public HashSet[] kruskal(HashSet[] graph) {
int nodes = graph.length;
- int[] captain = new int[nodes];
- // captain of i, stores the set with all the connected nodes to i
+ int[] captain = new int[nodes]; // Stores the "leader" of each node's connected component
HashSet[] connectedGroups = new HashSet[nodes];
HashSet[] minGraph = new HashSet[nodes];
- PriorityQueue edges = new PriorityQueue<>((Comparator.comparingInt(edge -> edge.weight)));
+ PriorityQueue edges = new PriorityQueue<>(Comparator.comparingInt(edge -> edge.weight));
for (int i = 0; i < nodes; i++) {
minGraph[i] = new HashSet<>();
connectedGroups[i] = new HashSet<>();
@@ -83,18 +69,21 @@ public HashSet[] kruskal(HashSet[] graph) {
edges.addAll(graph[i]);
}
int connectedElements = 0;
- // as soon as two sets merge all the elements, the algorithm must stop
while (connectedElements != nodes && !edges.isEmpty()) {
Edge edge = edges.poll();
- // This if avoids cycles
+
+ // Avoid forming cycles by checking if the nodes belong to different connected components
if (!connectedGroups[captain[edge.from]].contains(edge.to) && !connectedGroups[captain[edge.to]].contains(edge.from)) {
- // merge sets of the captains of each point connected by the edge
+ // Merge the two sets of nodes connected by the edge
connectedGroups[captain[edge.from]].addAll(connectedGroups[captain[edge.to]]);
- // update captains of the elements merged
+
+ // Update the captain for each merged node
connectedGroups[captain[edge.from]].forEach(i -> captain[i] = captain[edge.from]);
- // add Edge to minimal graph
+
+ // Add the edge to the resulting MST graph
addEdge(minGraph, edge.from, edge.to, edge.weight);
- // count how many elements have been merged
+
+ // Update the count of connected nodes
connectedElements = connectedGroups[captain[edge.from]].size();
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
index 902553f9a54c..c1d47df457da 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java
@@ -102,7 +102,7 @@ public int numberOfVertices() {
/**
* Updates the number of edges in the graph
*
- * @param newNumberOfEdges
+ * @param newNumberOfEdges the new number of edges
*
*/
private void setNumberOfEdges(int newNumberOfEdges) {
@@ -202,7 +202,7 @@ public boolean addEdge(int from, int to) {
* exists and is removed
*/
public boolean removeEdge(int from, int to) {
- if (!this.vertexDoesExist(from) || !this.vertexDoesExist(to)) {
+ if (this.vertexDoesExist(from) && this.vertexDoesExist(to)) {
if (this.adjacencyOfEdgeDoesExist(from, to)) {
this.adjacency()[from][to] = AdjacencyMatrixGraph.EDGE_NONE;
this.adjacency()[to][from] = AdjacencyMatrixGraph.EDGE_NONE;
@@ -223,14 +223,14 @@ public boolean removeEdge(int from, int to) {
public List depthFirstOrder(int startVertex) {
// If the startVertex is invalid, return an empty list
if (startVertex >= vertexCount || startVertex < 0) {
- return new ArrayList();
+ return new ArrayList<>();
}
// Create an array to track the visited vertices
boolean[] visited = new boolean[vertexCount];
// Create a list to keep track of the order of our traversal
- ArrayList orderList = new ArrayList();
+ ArrayList orderList = new ArrayList<>();
// Perform our DFS algorithm
depthFirstOrder(startVertex, visited, orderList);
@@ -278,18 +278,18 @@ private void depthFirstOrder(int currentVertex, boolean[] visited, List
public List breadthFirstOrder(int startVertex) {
// If the specified startVertex is invalid, return an empty list
if (startVertex >= vertexCount || startVertex < 0) {
- return new ArrayList();
+ return new ArrayList<>();
}
// Create an array to keep track of the visited vertices
boolean[] visited = new boolean[vertexCount];
// Create a list to keep track of the ordered vertices
- ArrayList orderList = new ArrayList();
+ ArrayList orderList = new ArrayList<>();
// Create a queue for our BFS algorithm and add the startVertex
// to the queue
- Queue queue = new LinkedList();
+ Queue queue = new LinkedList<>();
queue.add(startVertex);
// Continue until the queue is empty
@@ -327,19 +327,19 @@ public List breadthFirstOrder(int startVertex) {
* @return returns a string describing this graph
*/
public String toString() {
- String s = " ";
+ StringBuilder s = new StringBuilder(" ");
for (int i = 0; i < this.numberOfVertices(); i++) {
- s = s + i + " ";
+ s.append(i).append(" ");
}
- s = s + " \n";
+ s.append(" \n");
for (int i = 0; i < this.numberOfVertices(); i++) {
- s = s + i + " : ";
+ s.append(i).append(" : ");
for (int j = 0; j < this.numberOfVertices(); j++) {
- s = s + this.adjMatrix[i][j] + " ";
+ s.append(this.adjMatrix[i][j]).append(" ");
}
- s = s + "\n";
+ s.append("\n");
}
- return s;
+ return s.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java b/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java
index 7e11862786f6..8017c18ce6ac 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java
@@ -1,19 +1,17 @@
package com.thealgorithms.datastructures.graphs;
/**
- * A Java program for Prim's Minimum Spanning Tree (MST) algorithm. adjacency
- * matrix representation of the graph
+ * A Java program for Prim's Minimum Spanning Tree (MST) algorithm.
+ * Adjacency matrix representation of the graph.
*/
-class PrimMST {
+public class PrimMST {
// Number of vertices in the graph
-
private static final int V = 5;
- // A utility function to find the vertex with minimum key
- // value, from the set of vertices not yet included in MST
+ // A utility function to find the vertex with the minimum key
+ // value, from the set of vertices not yet included in the MST
int minKey(int[] key, Boolean[] mstSet) {
- // Initialize min value
int min = Integer.MAX_VALUE;
int minIndex = -1;
@@ -27,54 +25,30 @@ int minKey(int[] key, Boolean[] mstSet) {
return minIndex;
}
- // A utility function to print the constructed MST stored in
- // parent[]
- void printMST(int[] parent, int n, int[][] graph) {
- System.out.println("Edge Weight");
- for (int i = 1; i < V; i++) {
- System.out.println(parent[i] + " - " + i + " " + graph[i][parent[i]]);
- }
- }
-
- // Function to construct and print MST for a graph represented
- // using adjacency matrix representation
- void primMST(int[][] graph) {
- // Array to store constructed MST
- int[] parent = new int[V];
-
- // Key values used to pick minimum weight edge in cut
- int[] key = new int[V];
+ // Function to construct MST for a graph using adjacency matrix representation
+ public int[] primMST(int[][] graph) {
+ int[] parent = new int[V]; // Array to store constructed MST
+ int[] key = new int[V]; // Key values to pick minimum weight edge
+ Boolean[] mstSet = new Boolean[V]; // Vertices not yet included in MST
- // To represent set of vertices not yet included in MST
- Boolean[] mstSet = new Boolean[V];
-
- // Initialize all keys as INFINITE
+ // Initialize all keys as INFINITE and mstSet[] as false
for (int i = 0; i < V; i++) {
key[i] = Integer.MAX_VALUE;
mstSet[i] = Boolean.FALSE;
}
- // Always include first 1st vertex in MST.
- key[0] = 0; // Make key 0 so that this vertex is
- // picked as first vertex
+ // Always include the first vertex in MST
+ key[0] = 0; // Make key 0 to pick the first vertex
parent[0] = -1; // First node is always root of MST
// The MST will have V vertices
for (int count = 0; count < V - 1; count++) {
- // Pick thd minimum key vertex from the set of vertices
- // not yet included in MST
+ // Pick the minimum key vertex not yet included in MST
int u = minKey(key, mstSet);
-
- // Add the picked vertex to the MST Set
mstSet[u] = Boolean.TRUE;
- // Update key value and parent index of the adjacent
- // vertices of the picked vertex. Consider only those
- // vertices which are not yet included in MST
- for (int v = 0; v < V; v++) // Update the key only if graph[u][v] is smaller than key[v] // mstSet[v] is
- // false for vertices not yet included in MST // graph[u][v] is non zero only
- // for adjacent vertices of m
- {
+ // Update key value and parent index of adjacent vertices of the picked vertex
+ for (int v = 0; v < V; v++) {
if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
@@ -82,29 +56,6 @@ void primMST(int[][] graph) {
}
}
- // print the constructed MST
- printMST(parent, V, graph);
- }
-
- public static void main(String[] args) {
- /* Let us create the following graph
- 2 3
- (0)--(1)--(2)
- | / \ |
- 6| 8/ \5 |7
- | / \ |
- (3)-------(4)
- 9 */
- PrimMST t = new PrimMST();
- int[][] graph = new int[][] {
- {0, 2, 0, 6, 0},
- {2, 0, 3, 8, 5},
- {0, 3, 0, 0, 7},
- {6, 8, 0, 0, 9},
- {0, 5, 7, 9, 0},
- };
-
- // Print the solution
- t.primMST(graph);
+ return parent; // Return the MST parent array
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/README.md b/src/main/java/com/thealgorithms/datastructures/graphs/README.md
index 057adb46acf5..4798e372667b 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/README.md
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/README.md
@@ -88,4 +88,39 @@ It means there are edges from 0 to 1, 2, and 3; from 1 to 0 and 2, and so on.
2 1 1 0 0 0
3 1 0 0 0 1
4 0 0 0 1 0
-```
+
+###Graph Terminologies
+
+Degree of a vertex: Number of edges that are incident at a vertex.
+Weighted graph: A graph that has weights assigned for each of the edges (used in cases such as shortest path problems).
+Connected components: A set of vertices that can reach others from it but not to those outside this connected component.
+Cycle: A path that begins and ends at the same vertex.
+Bipartite Graph: A graph whose vertices can be partitioned into two disjoint sets, with every edge connecting a vertex in one set to a vertex in the other set.
+
+###Graph Algorithms
+
+Breadth-First Search: It explores neighbors in layer after layer and applies on shortest path problems for unweighted graphs.
+Depth-First Search (DFS): It continues moving up as far along each branch as possible before backtracking. DFS is typically used for traversing all nodes and testing connectivity.
+Dijkstra's Algorithm: This algorithm finds the shortest path from a single starting vertex to all other vertices in a weighted graph.
+Prim's and Kruskal's Algorithm: To find the minimum spanning tree.
+Bellman-Ford Algorithm: This algorithm solves shortest path problems even when there are negative weights.
+Graph Types
+Multigraphs: Graphs with more edges between the same set of vertices.
+Complete Graphs: A graph in which there is a unique edge between each pair of vertices.
+Planar Graphs: A graph that can be drawn in a plane such that no two edges cross.
+
+###Graph Algorithm Applications
+
+Google Maps (Dijkstra's Algorithm): How maps apps find shortest routes.
+Job Scheduling: Topological Sort A real application of DAG (Directed Acyclic Graph) to manage the dependency of jobs between tasks.
+Web Crawling: How to use BFS for web crawlers to index pages in search engines.
+Big-O Complexity of Graph Operations
+Adjacency List vs Adjacency Matrix : Provide comparison tables of time complexity for operations such as addition of an edge, checking if an edge exists, etc.
+BFS and DFS Complexity : Describe their computational cost
+
+###Common Graph Problems
+
+Graph Coloring
+Finding Bridges and Articulation Points
+Finding Strongly Connected Components
+Maximum Flow (Ford-Fulkerson algorithm)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java
index de50044256c6..91974ba13319 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java
@@ -5,66 +5,73 @@
import java.util.Stack;
/**
- * Java program that implements Tarjan's Algorithm.
- * @author Shivanagouda S A
+ * Java program that implements Tarjan's Algorithm to find Strongly Connected Components (SCCs) in a directed graph.
+ *
*
- * Tarjan's algorithm is a linear time algorithm to find the strongly connected components of a
-directed graph, which, from here onwards will be referred as SCC.
-
- * A graph is said to be strongly connected if every vertex is reachable from every other vertex.
-The SCCs of a directed graph form a partition into subgraphs that are themselves strongly
-connected. Single node is always a SCC.
-
- * Example:
-0 --------> 1 -------> 3 --------> 4
-^ /
-| /
-| /
-| /
-| /
-| /
-| /
-| /
-| /
-| /
-|V
-2
-
-For the above graph, the SCC list goes as follows:
-1, 2, 0
-3
-4
-
-We can also see that order of the nodes in an SCC doesn't matter since they are in cycle.
-
-{@summary}
-Tarjan's Algorithm:
- * DFS search produces a DFS tree
- * Strongly Connected Components form subtrees of the DFS tree.
- * If we can find the head of these subtrees, we can get all the nodes in that subtree (including
-the head) and that will be one SCC.
- * There is no back edge from one SCC to another (here can be cross edges, but they will not be
-used).
-
- * Kosaraju Algorithm aims at doing the same but uses two DFS traversalse whereas Tarjan’s
-algorithm does the same in a single DFS, which leads to much lower constant factors in the latter.
-
+ * Tarjan's algorithm is a linear time algorithm (O(V + E)) that identifies the SCCs of a directed graph.
+ * An SCC is a maximal subgraph where every vertex is reachable from every other vertex within the subgraph.
+ *
+ *
Algorithm Overview:
+ *
+ * - DFS Search: A depth-first search (DFS) is performed on the graph to generate a DFS tree.
+ * - Identification of SCCs: SCCs correspond to subtrees within this DFS tree.
+ * - Low-Link Values: For each node, a low-link value is maintained, which indicates the earliest visited
+ * vertex (the one with the minimum insertion time) that can be reached from that subtree.
+ * - Stack Usage: Nodes are stored in a stack during DFS. When an SCC is identified, nodes are popped from
+ * the stack until the head of the SCC is reached.
+ *
+ *
+ *
+ * Example of a directed graph:
+ *
+ * 0 --------> 1 -------> 3 --------> 4
+ * ^ /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * | /
+ * V
+ * 2
+ *
+ *
+ *
+ * For the above graph, the SCC list is as follows:
+ *
+ * - 1, 2, 0
+ * - 3
+ * - 4
+ *
+ * The order of nodes in an SCC does not matter as they form cycles.
+ *
+ * Comparison with Kosaraju's Algorithm:
+ *
+ * Kosaraju's algorithm also identifies SCCs but does so using two DFS traversals.
+ * In contrast, Tarjan's algorithm achieves this in a single DFS traversal, leading to improved performance
+ * in terms of constant factors.
+ *
*/
public class TarjansAlgorithm {
- // Timer for tracking lowtime and insertion time
+ // Timer for tracking low time and insertion time
private int time;
- private final List> sccList = new ArrayList>();
+ // List to store all strongly connected components
+ private final List> sccList = new ArrayList<>();
+ /**
+ * Finds and returns the strongly connected components (SCCs) of the directed graph.
+ *
+ * @param v the number of vertices in the graph
+ * @param graph the adjacency list representation of the graph
+ * @return a list of lists, where each inner list represents a strongly connected component
+ */
public List> stronglyConnectedComponents(int v, List> graph) {
-
- // Initially all vertices as unvisited, insertion and low time are undefined
-
- // insertionTime:Time when a node is visited 1st time while DFS traversal
-
- // lowTime: indicates the earliest visited vertex (the vertex with minimum insertion time)
- // that can be reached from a subtree rooted with a particular node.
+ // Initialize arrays for insertion time and low-link values
int[] lowTime = new int[v];
int[] insertionTime = new int[v];
for (int i = 0; i < v; i++) {
@@ -72,11 +79,11 @@ public List> stronglyConnectedComponents(int v, List
lowTime[i] = -1;
}
- // To check if element is present in stack
+ // Track if vertices are in the stack
boolean[] isInStack = new boolean[v];
- // Store nodes during DFS
- Stack st = new Stack();
+ // Stack to hold nodes during DFS
+ Stack st = new Stack<>();
for (int i = 0; i < v; i++) {
if (insertionTime[i] == -1) {
@@ -87,36 +94,44 @@ public List> stronglyConnectedComponents(int v, List
return sccList;
}
+ /**
+ * A utility function to perform DFS and find SCCs.
+ *
+ * @param u the current vertex being visited
+ * @param lowTime array to keep track of the low-link values
+ * @param insertionTime array to keep track of the insertion times
+ * @param isInStack boolean array indicating if a vertex is in the stack
+ * @param st the stack used for DFS
+ * @param graph the adjacency list representation of the graph
+ */
private void stronglyConnCompsUtil(int u, int[] lowTime, int[] insertionTime, boolean[] isInStack, Stack st, List> graph) {
-
- // Initialize insertion time and lowTime value of current node
+ // Set insertion time and low-link value
insertionTime[u] = time;
lowTime[u] = time;
- time += 1;
+ time++;
- // Push current node into stack
+ // Push current node onto the stack
isInStack[u] = true;
st.push(u);
- // Go through all vertices adjacent to this
+ // Explore adjacent vertices
for (Integer vertex : graph.get(u)) {
- // If the adjacent node is unvisited, do DFS
if (insertionTime[vertex] == -1) {
stronglyConnCompsUtil(vertex, lowTime, insertionTime, isInStack, st, graph);
- // update lowTime for the current node comparing lowtime of adj node
+ // Update low-link value
lowTime[u] = Math.min(lowTime[u], lowTime[vertex]);
} else if (isInStack[vertex]) {
- // If adj node is in stack, update low
+ // Vertex is in the stack; update low-link value
lowTime[u] = Math.min(lowTime[u], insertionTime[vertex]);
}
}
- // If lowtime and insertion time are same, current node is the head of an SCC
- // head node found, get all the nodes in this SCC
+
+ // Check if the current vertex is the root of an SCC
if (lowTime[u] == insertionTime[u]) {
int w = -1;
- var scc = new ArrayList();
+ List scc = new ArrayList<>();
- // Stack has all the nodes of the current SCC
+ // Pop vertices from the stack until the root is found
while (w != u) {
w = st.pop();
scc.add(w);
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java
new file mode 100644
index 000000000000..8aafc1ef3368
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.datastructures.graphs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class UndirectedAdjacencyListGraph {
+ private ArrayList> adjacencyList = new ArrayList<>();
+
+ /**
+ * Adds a new node to the graph by adding an empty HashMap for its neighbors.
+ * @return the index of the newly added node in the adjacency list
+ */
+ public int addNode() {
+ adjacencyList.add(new HashMap<>());
+ return adjacencyList.size() - 1;
+ }
+
+ /**
+ * Adds an undirected edge between the origin node (@orig) and the destination node (@dest) with the specified weight.
+ * If the edge already exists, no changes are made.
+ * @param orig the index of the origin node
+ * @param dest the index of the destination node
+ * @param weight the weight of the edge between @orig and @dest
+ * @return true if the edge was successfully added, false if the edge already exists or if any node index is invalid
+ */
+ public boolean addEdge(int orig, int dest, int weight) {
+ int numNodes = adjacencyList.size();
+ if (orig >= numNodes || dest >= numNodes || orig < 0 || dest < 0) {
+ return false;
+ }
+
+ if (adjacencyList.get(orig).containsKey(dest)) {
+ return false;
+ }
+
+ adjacencyList.get(orig).put(dest, weight);
+ adjacencyList.get(dest).put(orig, weight);
+ return true;
+ }
+
+ /**
+ * Returns the set of all adjacent nodes (neighbors) for the given node.
+ * @param node the index of the node whose neighbors are to be retrieved
+ * @return a HashSet containing the indices of all neighboring nodes
+ */
+ public HashSet getNeighbors(int node) {
+ return new HashSet<>(adjacencyList.get(node).keySet());
+ }
+
+ /**
+ * Returns the weight of the edge between the origin node (@orig) and the destination node (@dest).
+ * If no edge exists, returns null.
+ * @param orig the index of the origin node
+ * @param dest the index of the destination node
+ * @return the weight of the edge between @orig and @dest, or null if no edge exists
+ */
+ public Integer getEdgeWeight(int orig, int dest) {
+ return adjacencyList.get(orig).getOrDefault(dest, null);
+ }
+
+ /**
+ * Returns the number of nodes currently in the graph.
+ * @return the number of nodes in the graph
+ */
+ public int size() {
+ return adjacencyList.size();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java
index 0981638d4903..26ca97736fe9 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java
@@ -5,21 +5,41 @@
import java.util.HashSet;
import java.util.stream.IntStream;
-/*
- * The Welsh-Powell algorithm is a graph coloring algorithm
- * used for coloring a graph with the minimum number of colors.
- * https://en.wikipedia.org/wiki/Graph_coloring
+/**
+ * The Welsh-Powell algorithm is a graph coloring algorithm that aims to color a graph
+ * using the minimum number of colors such that no two adjacent vertices share the same color.
+ *
+ *
+ * The algorithm works by:
+ *
+ * - Sorting the vertices in descending order based on their degrees (number of edges connected).
+ * - Iterating through each vertex and assigning it the smallest available color that has not been used by its adjacent vertices.
+ * - Coloring adjacent vertices with the same color is avoided.
+ *
+ *
+ *
+ *
+ * For more information, see Graph Coloring.
+ *
*/
-
public final class WelshPowell {
- private static final int BLANK_COLOR = -1; // Representing uncolored state
+ private static final int BLANK_COLOR = -1; // Constant representing an uncolored state
private WelshPowell() {
}
+ /**
+ * Represents a graph using an adjacency list.
+ */
static final class Graph {
- private HashSet[] adjacencyLists;
-
+ private final HashSet[] adjacencyLists;
+
+ /**
+ * Initializes a graph with a specified number of vertices.
+ *
+ * @param vertices the number of vertices in the graph
+ * @throws IllegalArgumentException if the number of vertices is negative
+ */
private Graph(int vertices) {
if (vertices < 0) {
throw new IllegalArgumentException("Number of vertices cannot be negative");
@@ -29,6 +49,13 @@ private Graph(int vertices) {
Arrays.setAll(adjacencyLists, i -> new HashSet<>());
}
+ /**
+ * Adds an edge between two vertices in the graph.
+ *
+ * @param nodeA one end of the edge
+ * @param nodeB the other end of the edge
+ * @throws IllegalArgumentException if the vertices are out of bounds or if a self-loop is attempted
+ */
private void addEdge(int nodeA, int nodeB) {
validateVertex(nodeA);
validateVertex(nodeB);
@@ -39,21 +66,46 @@ private void addEdge(int nodeA, int nodeB) {
adjacencyLists[nodeB].add(nodeA);
}
+ /**
+ * Validates that the vertex index is within the bounds of the graph.
+ *
+ * @param vertex the index of the vertex to validate
+ * @throws IllegalArgumentException if the vertex is out of bounds
+ */
private void validateVertex(int vertex) {
if (vertex < 0 || vertex >= getNumVertices()) {
throw new IllegalArgumentException("Vertex " + vertex + " is out of bounds");
}
}
+ /**
+ * Returns the adjacency list for a specific vertex.
+ *
+ * @param vertex the index of the vertex
+ * @return the set of adjacent vertices
+ */
HashSet getAdjacencyList(int vertex) {
return adjacencyLists[vertex];
}
+ /**
+ * Returns the number of vertices in the graph.
+ *
+ * @return the number of vertices
+ */
int getNumVertices() {
return adjacencyLists.length;
}
}
+ /**
+ * Creates a graph with the specified number of vertices and edges.
+ *
+ * @param numberOfVertices the total number of vertices
+ * @param listOfEdges a 2D array representing edges where each inner array contains two vertex indices
+ * @return a Graph object representing the created graph
+ * @throws IllegalArgumentException if the edge array is invalid or vertices are out of bounds
+ */
public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) {
Graph graph = new Graph(numberOfVertices);
for (int[] edge : listOfEdges) {
@@ -65,6 +117,12 @@ public static Graph makeGraph(int numberOfVertices, int[][] listOfEdges) {
return graph;
}
+ /**
+ * Finds the coloring of the given graph using the Welsh-Powell algorithm.
+ *
+ * @param graph the input graph to color
+ * @return an array of integers where each index represents a vertex and the value represents the color assigned
+ */
public static int[] findColoring(Graph graph) {
int[] colors = initializeColors(graph.getNumVertices());
Integer[] sortedVertices = getSortedNodes(graph);
@@ -83,30 +141,70 @@ public static int[] findColoring(Graph graph) {
return colors;
}
+ /**
+ * Helper method to check if a color is unassigned
+ *
+ * @param color the color to check
+ * @return {@code true} if the color is unassigned, {@code false} otherwise
+ */
private static boolean isBlank(int color) {
return color == BLANK_COLOR;
}
+ /**
+ * Checks if a vertex has adjacent colored vertices
+ *
+ * @param graph the input graph
+ * @param vertex the vertex to check
+ * @param colors the array of colors assigned to the vertices
+ * @return {@code true} if the vertex has adjacent colored vertices, {@code false} otherwise
+ */
private static boolean isAdjacentToColored(Graph graph, int vertex, int[] colors) {
return graph.getAdjacencyList(vertex).stream().anyMatch(otherVertex -> !isBlank(colors[otherVertex]));
}
+ /**
+ * Initializes the colors array with blank color
+ *
+ * @param numberOfVertices the number of vertices in the graph
+ * @return an array of integers representing the colors assigned to the vertices
+ */
private static int[] initializeColors(int numberOfVertices) {
int[] colors = new int[numberOfVertices];
Arrays.fill(colors, BLANK_COLOR);
return colors;
}
+ /**
+ * Sorts the vertices by their degree in descending order
+ *
+ * @param graph the input graph
+ * @return an array of integers representing the vertices sorted by degree
+ */
private static Integer[] getSortedNodes(final Graph graph) {
return IntStream.range(0, graph.getNumVertices()).boxed().sorted(Comparator.comparingInt(v -> - graph.getAdjacencyList(v).size())).toArray(Integer[] ::new);
}
+ /**
+ * Computes the colors already used by the adjacent vertices
+ *
+ * @param graph the input graph
+ * @param vertex the vertex to check
+ * @param colors the array of colors assigned to the vertices
+ * @return an array of booleans representing the colors used by the adjacent vertices
+ */
private static boolean[] computeUsedColors(final Graph graph, final int vertex, final int[] colors) {
boolean[] usedColors = new boolean[graph.getNumVertices()];
graph.getAdjacencyList(vertex).stream().map(neighbor -> colors[neighbor]).filter(color -> !isBlank(color)).forEach(color -> usedColors[color] = true);
return usedColors;
}
+ /**
+ * Finds the first unused color
+ *
+ * @param usedColors the array of colors used by the adjacent vertices
+ * @return the first unused color
+ */
private static int firstUnusedColor(boolean[] usedColors) {
return IntStream.range(0, usedColors.length).filter(color -> !usedColors[color]).findFirst().getAsInt();
}
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java
index 416cee99d028..3637e323f097 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java
@@ -2,23 +2,45 @@
import java.util.LinkedList;
-// implementation of generic hashmaps using array of Linked Lists
-
+/**
+ * A generic implementation of a hash map using an array of linked lists for collision resolution.
+ * This class provides a way to store key-value pairs efficiently, allowing for average-case
+ * constant time complexity for insertion, deletion, and retrieval operations.
+ *
+ *
+ * The hash map uses separate chaining for collision resolution. Each bucket in the hash map is a
+ * linked list that stores nodes containing key-value pairs. When a collision occurs (i.e., when
+ * two keys hash to the same index), the new key-value pair is simply added to the corresponding
+ * linked list.
+ *
+ *
+ *
+ * The hash map automatically resizes itself when the load factor exceeds 0.75. The load factor is
+ * defined as the ratio of the number of entries to the number of buckets. When resizing occurs,
+ * all existing entries are rehashed and inserted into the new buckets.
+ *
+ *
+ * @param the type of keys maintained by this hash map
+ * @param the type of mapped values
+ */
public class GenericHashMapUsingArray {
- private int size; // n (total number of key-value pairs)
- private LinkedList[] buckets; // N = buckets.length
- private float lf = 0.75f;
+ private int size; // Total number of key-value pairs
+ private LinkedList[] buckets; // Array of linked lists (buckets) for storing entries
+ /**
+ * Constructs a new empty hash map with an initial capacity of 16.
+ */
public GenericHashMapUsingArray() {
initBuckets(16);
size = 0;
}
- // load factor = 0.75 means if we need to add 100 items and we have added
- // 75, then adding 76th item it will double the size, copy all elements
- // & then add 76th item.
-
+ /**
+ * Initializes the buckets for the hash map with the specified number of buckets.
+ *
+ * @param n the number of buckets to initialize
+ */
private void initBuckets(int n) {
buckets = new LinkedList[n];
for (int i = 0; i < buckets.length; i++) {
@@ -26,43 +48,66 @@ private void initBuckets(int n) {
}
}
+ /**
+ * Associates the specified value with the specified key in this map.
+ * If the map previously contained a mapping for the key, the old value is replaced.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
int bucketIndex = hashFunction(key);
LinkedList nodes = buckets[bucketIndex];
- for (Node node : nodes) { // if key present => update
+ // Update existing key's value if present
+ for (Node node : nodes) {
if (node.key.equals(key)) {
node.value = value;
return;
}
}
- // key is not present => insert
+ // Insert new key-value pair
nodes.add(new Node(key, value));
size++;
- if ((float) size / buckets.length > lf) {
+ // Check if rehashing is needed
+ // Load factor threshold for resizing
+ float loadFactorThreshold = 0.75f;
+ if ((float) size / buckets.length > loadFactorThreshold) {
reHash();
}
}
- // tells which bucket to go to
+ /**
+ * Returns the index of the bucket in which the key would be stored.
+ *
+ * @param key the key whose bucket index is to be computed
+ * @return the bucket index
+ */
private int hashFunction(K key) {
return Math.floorMod(key.hashCode(), buckets.length);
}
+ /**
+ * Rehashes the map by doubling the number of buckets and re-inserting all entries.
+ */
private void reHash() {
- System.out.println("Rehashing!");
- LinkedList[] old = buckets;
- initBuckets(old.length * 2);
+ LinkedList[] oldBuckets = buckets;
+ initBuckets(oldBuckets.length * 2);
this.size = 0;
- for (LinkedList nodes : old) {
+ for (LinkedList nodes : oldBuckets) {
for (Node node : nodes) {
put(node.key, node.value);
}
}
}
+ /**
+ * Removes the mapping for the specified key from this map if present.
+ *
+ * @param key the key whose mapping is to be removed from the map
+ */
public void remove(K key) {
int bucketIndex = hashFunction(key);
LinkedList nodes = buckets[bucketIndex];
@@ -74,14 +119,28 @@ public void remove(K key) {
break;
}
}
- nodes.remove(target);
- size--;
+
+ if (target != null) {
+ nodes.remove(target);
+ size--;
+ }
}
+ /**
+ * Returns the number of key-value pairs in this map.
+ *
+ * @return the number of key-value pairs
+ */
public int size() {
return this.size;
}
+ /**
+ * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the specified key, or null if no mapping exists
+ */
public V get(K key) {
int bucketIndex = hashFunction(key);
LinkedList nodes = buckets[bucketIndex];
@@ -96,7 +155,6 @@ public V get(K key) {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
-
builder.append("{");
for (LinkedList nodes : buckets) {
for (Node node : nodes) {
@@ -106,19 +164,37 @@ public String toString() {
builder.append(", ");
}
}
+ // Remove trailing comma and space
+ if (builder.length() > 1) {
+ builder.setLength(builder.length() - 2);
+ }
builder.append("}");
return builder.toString();
}
+ /**
+ * Returns true if this map contains a mapping for the specified key.
+ *
+ * @param key the key whose presence in this map is to be tested
+ * @return true if this map contains a mapping for the specified key
+ */
public boolean containsKey(K key) {
return get(key) != null;
}
+ /**
+ * A private class representing a key-value pair (node) in the hash map.
+ */
public class Node {
-
K key;
V value;
+ /**
+ * Constructs a new Node with the specified key and value.
+ *
+ * @param key the key of the key-value pair
+ * @param value the value of the key-value pair
+ */
public Node(K key, V value) {
this.key = key;
this.value = value;
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java
index a1ef457f3432..89e25f4eb0f7 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java
@@ -3,12 +3,34 @@
import java.util.ArrayList;
import java.util.LinkedList;
+/**
+ * A generic implementation of a hash map using an array list of linked lists for collision resolution.
+ * This class allows storage of key-value pairs with average-case constant time complexity for insertion,
+ * deletion, and retrieval operations.
+ *
+ *
+ * The hash map uses separate chaining to handle collisions. Each bucket in the hash map is represented
+ * by a linked list that holds nodes containing key-value pairs. When multiple keys hash to the same index,
+ * they are stored in the same linked list.
+ *
+ *
+ *
+ * The hash map automatically resizes itself when the load factor exceeds 0.5. The load factor is defined
+ * as the ratio of the number of entries to the number of buckets. When resizing occurs, all existing entries
+ * are rehashed and inserted into the new buckets.
+ *
+ *
+ * @param the type of keys maintained by this hash map
+ * @param the type of mapped values
+ */
public class GenericHashMapUsingArrayList {
- ArrayList> buckets;
- private float lf = 0.5f;
- private int size;
+ private ArrayList> buckets; // Array list of buckets (linked lists)
+ private int size; // Number of key-value pairs in the hash map
+ /**
+ * Constructs a new empty hash map with an initial capacity of 10 buckets.
+ */
public GenericHashMapUsingArrayList() {
buckets = new ArrayList<>();
for (int i = 0; i < 10; i++) {
@@ -17,6 +39,13 @@ public GenericHashMapUsingArrayList() {
size = 0;
}
+ /**
+ * Associates the specified value with the specified key in this map.
+ * If the map previously contained a mapping for the key, the old value is replaced.
+ *
+ * @param key the key with which the specified value is to be associated
+ * @param value the value to be associated with the specified key
+ */
public void put(K key, V value) {
int hash = Math.abs(key.hashCode() % buckets.size());
LinkedList nodes = buckets.get(hash);
@@ -31,25 +60,36 @@ public void put(K key, V value) {
nodes.add(new Node(key, value));
size++;
- if ((float) size / buckets.size() > lf) {
+ // Load factor threshold for resizing
+ float loadFactorThreshold = 0.5f;
+ if ((float) size / buckets.size() > loadFactorThreshold) {
reHash();
}
}
+ /**
+ * Resizes the hash map by doubling the number of buckets and rehashing existing entries.
+ */
private void reHash() {
- ArrayList> old = buckets;
+ ArrayList> oldBuckets = buckets;
buckets = new ArrayList<>();
size = 0;
- for (int i = 0; i < old.size() * 2; i++) {
+ for (int i = 0; i < oldBuckets.size() * 2; i++) {
buckets.add(new LinkedList<>());
}
- for (LinkedList nodes : buckets) {
+ for (LinkedList nodes : oldBuckets) {
for (Node node : nodes) {
put(node.key, node.val);
}
}
}
+ /**
+ * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the specified key, or null if no mapping exists
+ */
public V get(K key) {
int hash = Math.abs(key.hashCode() % buckets.size());
LinkedList nodes = buckets.get(hash);
@@ -61,6 +101,11 @@ public V get(K key) {
return null;
}
+ /**
+ * Removes the mapping for the specified key from this map if present.
+ *
+ * @param key the key whose mapping is to be removed from the map
+ */
public void remove(K key) {
int hash = Math.abs(key.hashCode() % buckets.size());
LinkedList nodes = buckets.get(hash);
@@ -72,18 +117,36 @@ public void remove(K key) {
break;
}
}
- nodes.remove(target);
- size--;
+ if (target != null) {
+ nodes.remove(target);
+ size--;
+ }
}
+ /**
+ * Returns true if this map contains a mapping for the specified key.
+ *
+ * @param key the key whose presence in this map is to be tested
+ * @return true if this map contains a mapping for the specified key
+ */
public boolean containsKey(K key) {
return get(key) != null;
}
+ /**
+ * Returns the number of key-value pairs in this map.
+ *
+ * @return the number of key-value pairs
+ */
public int size() {
return this.size;
}
+ /**
+ * Returns a string representation of the map, containing all key-value pairs.
+ *
+ * @return a string representation of the map
+ */
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -96,15 +159,27 @@ public String toString() {
builder.append(", ");
}
}
+ // Remove trailing comma and space if there are any elements
+ if (builder.length() > 1) {
+ builder.setLength(builder.length() - 2);
+ }
builder.append("}");
return builder.toString();
}
+ /**
+ * A private inner class representing a key-value pair (node) in the hash map.
+ */
private class Node {
-
K key;
V val;
+ /**
+ * Constructs a new Node with the specified key and value.
+ *
+ * @param key the key of the key-value pair
+ * @param val the value of the key-value pair
+ */
Node(K key, V val) {
this.key = key;
this.val = val;
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java
index 1aae122b48ec..aed39c941430 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java
@@ -85,6 +85,28 @@ public void display() {
}
}
+ /**
+ * Clears the contents of the hash map by reinitializing each bucket.
+ */
+ public void clear() {
+ for (int i = 0; i < hashSize; i++) {
+ buckets[i] = new LinkedList<>();
+ }
+ }
+
+ /**
+ * Gets the number of key-value pairs in the hash map.
+ *
+ * @return the number of key-value pairs in the hash map
+ */
+ public int size() {
+ int size = 0;
+ for (int i = 0; i < hashSize; i++) {
+ size += buckets[i].isEmpty() ? 0 : 1;
+ }
+ return size;
+ }
+
/**
* A nested static class that represents a linked list used for separate chaining in the hash map.
*
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java
index a67968d7e659..8bcf00730acb 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java
@@ -3,25 +3,26 @@
import java.util.Objects;
/**
- * This class is an implementation of a hash table using Cuckoo Hashing It uses
- * a dynamic array to lengthen the size of the hash table when load factor > .7
+ * This class implements a hash table using Cuckoo Hashing.
+ * Cuckoo hashing is a type of open-addressing hash table that resolves collisions
+ * by relocating existing keys. It utilizes two hash functions to minimize collisions
+ * and automatically resizes the table when the load factor exceeds 0.7.
*
- * ...
+ * For more information on cuckoo hashing, refer to
+ * this Wikipedia page.
*/
public class HashMapCuckooHashing {
- private int tableSize; // size of the hash table
- private Integer[] buckets; // array representing the table
- private final Integer emptySlot;
- private int size; // number of elements in the hash table
-
- private int thresh; // threshold for infinite loop checking
+ private int tableSize; // Size of the hash table
+ private Integer[] buckets; // Array representing the hash table
+ private final Integer emptySlot; // Placeholder for deleted slots
+ private int size; // Number of elements in the hash table
+ private int thresh; // Threshold for detecting infinite loops during insertion
/**
- * Constructor initializes buckets array, hsize, and creates dummy object
- * for emptySlot
+ * Constructs a HashMapCuckooHashing object with the specified initial table size.
*
- * @param tableSize the desired size of the hash map
+ * @param tableSize the initial size of the hash map
*/
public HashMapCuckooHashing(int tableSize) {
this.buckets = new Integer[tableSize];
@@ -32,13 +33,11 @@ public HashMapCuckooHashing(int tableSize) {
}
/**
- * The 2 Hash Functions takes a given key and finds an index based on its data, 2 distinctive
- * ways to minimize collisions
+ * Computes the first hash index for a given key using the modulo operation.
*
- * @param key the desired key to be converted
- * @return int an index corresponding to the key
+ * @param key the key for which the hash index is computed
+ * @return an integer index corresponding to the key
*/
-
public int hashFunction1(int key) {
int hash = key % tableSize;
if (hash < 0) {
@@ -47,6 +46,12 @@ public int hashFunction1(int key) {
return hash;
}
+ /**
+ * Computes the second hash index for a given key using integer division.
+ *
+ * @param key the key for which the hash index is computed
+ * @return an integer index corresponding to the key
+ */
public int hashFunction2(int key) {
int hash = key / tableSize;
hash %= tableSize;
@@ -57,14 +62,14 @@ public int hashFunction2(int key) {
}
/**
- * inserts the key into the hash map by wrapping it as an Integer object, then uses while loop
- * to insert new key if desired place is empty, return. if already occupied, continue while loop
- * over the new key that has just been pushed out. if while loop continues more than Thresh,
- * rehash table to new size, then push again.
+ * Inserts a key into the hash table using cuckoo hashing.
+ * If the target bucket is occupied, it relocates the existing key and attempts to insert
+ * it into its alternate location. If the insertion process exceeds the threshold,
+ * the table is resized.
*
- * @param key the desired key to be inserted in the hash map
+ * @param key the key to be inserted into the hash table
+ * @throws IllegalArgumentException if the key already exists in the table
*/
-
public void insertKey2HashTable(int key) {
Integer wrappedInt = key;
Integer temp;
@@ -77,7 +82,7 @@ public void insertKey2HashTable(int key) {
}
if (checkTableContainsKey(key)) {
- throw new IllegalArgumentException("Key already inside, no duplicates allowed");
+ throw new IllegalArgumentException("Key already exists; duplicates are not allowed.");
}
while (loopCounter <= thresh) {
@@ -117,9 +122,7 @@ public void insertKey2HashTable(int key) {
}
/**
- * creates new HashMapCuckooHashing object, then inserts each of the elements in the previous
- * table to it with its new hash functions. then refers current array to new table.
- *
+ * Rehashes the current table to a new size (double the current size) and reinserts existing keys.
*/
public void reHashTableIncreasesTableSize() {
HashMapCuckooHashing newT = new HashMapCuckooHashing(tableSize * 2);
@@ -134,15 +137,16 @@ public void reHashTableIncreasesTableSize() {
}
/**
- * deletes a key from the hash map and adds an available placeholder
+ * Deletes a key from the hash table, marking its position as available.
*
- * @param key the desired key to be deleted
+ * @param key the key to be deleted from the hash table
+ * @throws IllegalArgumentException if the table is empty or if the key is not found
*/
public void deleteKeyFromHashTable(int key) {
Integer wrappedInt = key;
int hash = hashFunction1(key);
if (isEmpty()) {
- throw new IllegalArgumentException("Table is empty");
+ throw new IllegalArgumentException("Table is empty, cannot delete.");
}
if (Objects.equals(buckets[hash], wrappedInt)) {
@@ -157,11 +161,11 @@ public void deleteKeyFromHashTable(int key) {
size--;
return;
}
- throw new IllegalArgumentException("Key " + key + " already inside, no duplicates allowed");
+ throw new IllegalArgumentException("Key " + key + " not found in the table.");
}
/**
- * Displays the hash table line by line
+ * Displays the hash table contents, bucket by bucket.
*/
public void displayHashtable() {
for (int i = 0; i < tableSize; i++) {
@@ -175,17 +179,18 @@ public void displayHashtable() {
}
/**
- * Finds the index of location based on an inputted key
+ * Finds the index of a given key in the hash table.
*
- * @param key the desired key to be found
- * @return int the index where the key is located
+ * @param key the key to be found
+ * @return the index where the key is located
+ * @throws IllegalArgumentException if the table is empty or the key is not found
*/
public int findKeyInTable(int key) {
Integer wrappedInt = key;
int hash = hashFunction1(key);
if (isEmpty()) {
- throw new IllegalArgumentException("Table is empty");
+ throw new IllegalArgumentException("Table is empty; cannot find keys.");
}
if (Objects.equals(buckets[hash], wrappedInt)) {
@@ -194,66 +199,70 @@ public int findKeyInTable(int key) {
hash = hashFunction2(key);
if (!Objects.equals(buckets[hash], wrappedInt)) {
- throw new IllegalArgumentException("Key " + key + " not found in table");
+ throw new IllegalArgumentException("Key " + key + " not found in the table.");
} else {
return hash;
}
}
/**
- * checks if key is inside without any output other than returned boolean.
+ * Checks if the given key is present in the hash table.
*
- * @param key the desired key to be found
- * @return int the index where the key is located
+ * @param key the key to be checked
+ * @return true if the key exists, false otherwise
*/
public boolean checkTableContainsKey(int key) {
- return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)] == key));
+ return ((buckets[hashFunction1(key)] != null && buckets[hashFunction1(key)].equals(key)) || (buckets[hashFunction2(key)] != null && buckets[hashFunction2(key)].equals(key)));
}
/**
- * Checks the load factor of the hash table if greater than .7,
- * automatically lengthens table to prevent further collisions
+ * Checks the load factor of the hash table. If the load factor exceeds 0.7,
+ * the table is resized to prevent further collisions.
+ *
+ * @return the current load factor of the hash table
*/
public double checkLoadFactor() {
double factor = (double) size / tableSize;
if (factor > .7) {
- System.out.printf("Load factor is %.2f , rehashing table%n", factor);
+ System.out.printf("Load factor is %.2f, rehashing table.%n", factor);
reHashTableIncreasesTableSize();
}
return factor;
}
/**
- * isFull returns true if the hash map is full and false if not full
+ * Checks if the hash map is full.
*
- * @return boolean is Empty
+ * @return true if the hash map is full, false otherwise
*/
public boolean isFull() {
- boolean response = true;
for (int i = 0; i < tableSize; i++) {
if (buckets[i] == null || Objects.equals(buckets[i], emptySlot)) {
return false;
}
}
- return response;
+ return true;
}
/**
- * isEmpty returns true if the hash map is empty and false if not empty
+ * Checks if the hash map is empty.
*
- * @return boolean is Empty
+ * @return true if the hash map is empty, false otherwise
*/
public boolean isEmpty() {
- boolean response = true;
for (int i = 0; i < tableSize; i++) {
if (buckets[i] != null) {
- response = false;
- break;
+ return false;
}
}
- return response;
+ return true;
}
+ /**
+ * Returns the current number of keys in the hash table.
+ *
+ * @return the number of keys present in the hash table
+ */
public int getNumberOfKeysInTable() {
return size;
}
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
index 54bd10de50fa..0e49218d6348 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java
@@ -1,27 +1,57 @@
package com.thealgorithms.datastructures.hashmap.hashing;
-/*
- * this is algo which implies common mathematical set theory concept
- * called intersection in which result is common values of both the sets
- * here metaphor of sets is HashMap
- */
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+/**
+ * The {@code Intersection} class provides a method to compute the intersection of two integer arrays.
+ * The intersection is defined as the set of common elements present in both arrays.
+ *
+ * This class utilizes a HashMap to efficiently count occurrences of elements in the first array,
+ * allowing for an efficient lookup of common elements in the second array.
+ *
+ *
+ *
+ * Example:
+ *
+ * int[] array1 = {1, 2, 2, 1};
+ * int[] array2 = {2, 2};
+ * List result = Intersection.intersection(array1, array2); // result will contain [2, 2]
+ *
+ *
+ *
+ *
+ * Note: The order of the returned list may vary since it depends on the order of elements
+ * in the input arrays.
+ *
+ */
public final class Intersection {
+ /**
+ * Computes the intersection of two integer arrays.
+ * Steps:
+ * 1. Count the occurrences of each element in the first array using a HashMap.
+ * 2. Iterate over the second array and check if the element is present in the HashMap.
+ * If it is, add it to the result list and decrement the count in the HashMap.
+ * 3. Return the result list containing the intersection of the two arrays.
+ *
+ * @param arr1 the first array of integers
+ * @param arr2 the second array of integers
+ * @return a list containing the intersection of the two arrays, or an empty list if either array is null or empty
+ */
public static List intersection(int[] arr1, int[] arr2) {
if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0) {
return Collections.emptyList();
}
+
Map cnt = new HashMap<>(16);
for (int v : arr1) {
cnt.put(v, cnt.getOrDefault(v, 0) + 1);
}
+
List res = new ArrayList<>();
for (int v : arr2) {
if (cnt.containsKey(v) && cnt.get(v) > 0) {
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java
index c96da27c0331..10d5dc7decae 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java
@@ -2,24 +2,51 @@
import java.util.ArrayList;
-/***
- * This class is an implementation of a hash table using linear probing.
+/**
+ * This class implements a hash table using linear probing to resolve collisions.
+ * Linear probing is a collision resolution method where each slot in the hash table is checked in a sequential manner
+ * until an empty slot is found.
+ *
+ *
+ * The class allows for storing key-value pairs, where both the key and value are generic types.
+ * The key must be of a type that implements the Comparable interface to ensure that the keys can be compared for sorting.
+ *
+ *
+ *
+ * This implementation supports basic operations such as:
+ *
+ * - put(Key key, Value value): Adds a key-value pair to the hash table. If the key already exists, its value is updated.
+ * - get(Key key): Retrieves the value associated with the given key.
+ * - delete(Key key): Removes the key and its associated value from the hash table.
+ * - contains(Key key): Checks if the hash table contains a given key.
+ * - size(): Returns the number of key-value pairs in the hash table.
+ * - keys(): Returns an iterable collection of keys stored in the hash table.
+ *
+ *
+ *
+ *
+ * The internal size of the hash table is automatically resized when the load factor exceeds 0.5 or falls below 0.125,
+ * ensuring efficient space utilization.
+ *
+ *
* @see Linear Probing Hash Table
*
- * @param keys type.
- * @param values type.
+ * @param the type of keys maintained by this map
+ * @param the type of mapped values
*/
public class LinearProbingHashMap, Value> extends Map {
private int hsize; // size of the hash table
- private Key[] keys;
- private Value[] values;
- private int size; // amount of elements in the hash table
+ private Key[] keys; // array to store keys
+ private Value[] values; // array to store values
+ private int size; // number of elements in the hash table
+ // Default constructor initializes the table with a default size of 16
public LinearProbingHashMap() {
this(16);
}
@SuppressWarnings("unchecked")
+ // Constructor to initialize the hash table with a specified size
public LinearProbingHashMap(int size) {
this.hsize = size;
keys = (Key[]) new Comparable[size];
@@ -81,7 +108,7 @@ public boolean delete(Key key) {
i = increment(i);
while (keys[i] != null) {
- // delete keys[i] an vals[i] and reinsert
+ // Save the key and value for rehashing
Key keyToRehash = keys[i];
Value valToRehash = values[i];
keys[i] = null;
diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
index 5424e14c72fd..915e4228b618 100644
--- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
+++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java
@@ -3,19 +3,23 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-/*
-This class finds the majority element(s) in an array of integers.
-A majority element is an element that appears more than or equal to n/2 times, where n is the length
-of the array.
-*/
+
+/**
+ * This class provides a method to find the majority element(s) in an array of integers.
+ * A majority element is defined as an element that appears at least ⌊n/2⌋ times,
+ * where n is the length of the array. If multiple elements qualify as majority elements,
+ * they are all returned in a list.
+ */
public final class MajorityElement {
private MajorityElement() {
}
- /*
- This method returns the majority element(s) in the given array of integers.
- @param nums: an array of integers
- @return a list of majority elements
- */
+
+ /**
+ * Returns a list of majority element(s) from the given array of integers.
+ *
+ * @param nums an array of integers
+ * @return a list containing the majority element(s); returns an empty list if none exist
+ */
public static List majority(int[] nums) {
HashMap numToCount = new HashMap<>();
for (final var num : nums) {
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java
index d248bc3316ed..7a263fc08ac5 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java
@@ -1,5 +1,30 @@
package com.thealgorithms.datastructures.heaps;
+/**
+ * The {@code FibonacciHeap} class implements a Fibonacci Heap data structure,
+ * which is a collection of trees that satisfy the minimum heap property.
+ * This heap allows for efficient merging of heaps, as well as faster
+ * decrease-key and delete operations compared to other heap data structures.
+ *
+ * Key features of the Fibonacci Heap include:
+ *
+ * - Amortized O(1) time complexity for insert and decrease-key operations.
+ * - Amortized O(log n) time complexity for delete and delete-min operations.
+ * - Meld operation that combines two heaps in O(1) time.
+ * - Potential function that helps analyze the amortized time complexity.
+ *
+ *
+ * This implementation maintains additional statistics such as the total number
+ * of link and cut operations performed during the lifetime of the heap, which can
+ * be accessed through static methods.
+ *
+ *
The Fibonacci Heap is composed of nodes represented by the inner class
+ * {@code HeapNode}. Each node maintains a key, rank, marked status, and pointers
+ * to its children and siblings. Nodes can be linked and cut as part of the heap
+ * restructuring processes.
+ *
+ * @see HeapNode
+ */
public class FibonacciHeap {
private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java
index d546b7cc88d4..b8a289db60b9 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java
@@ -3,45 +3,83 @@
import java.util.ArrayList;
import java.util.HashMap;
+/**
+ * A generic implementation of a max heap data structure.
+ *
+ * @param the type of elements in this heap, must extend Comparable.
+ */
public class GenericHeap> {
- ArrayList data = new ArrayList<>();
- HashMap map = new HashMap<>();
+ private final ArrayList data = new ArrayList<>();
+ private final HashMap map = new HashMap<>();
+ /**
+ * Adds an item to the heap, maintaining the heap property.
+ *
+ * @param item the item to be added
+ */
public void add(T item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Cannot insert null into the heap.");
+ }
+
this.data.add(item);
- map.put(item, this.data.size() - 1); //
+ map.put(item, this.data.size() - 1);
upHeapify(this.data.size() - 1);
}
+ /**
+ * Restores the heap property by moving the item at the given index upwards.
+ *
+ * @param ci the index of the current item
+ */
private void upHeapify(int ci) {
int pi = (ci - 1) / 2;
- if (isLarger(this.data.get(ci), this.data.get(pi)) > 0) {
+ if (ci > 0 && isLarger(this.data.get(ci), this.data.get(pi)) > 0) {
swap(pi, ci);
upHeapify(pi);
}
}
- public void display() {
- System.out.println(this.data);
- }
-
+ /**
+ * Returns the number of elements in the heap.
+ *
+ * @return the size of the heap
+ */
public int size() {
return this.data.size();
}
+ /**
+ * Checks if the heap is empty.
+ *
+ * @return true if the heap is empty, false otherwise
+ */
public boolean isEmpty() {
return this.size() == 0;
}
+ /**
+ * Removes and returns the maximum item from the heap.
+ *
+ * @return the maximum item
+ */
public T remove() {
+ if (isEmpty()) {
+ throw new IllegalStateException("Heap is empty");
+ }
this.swap(0, this.size() - 1);
T rv = this.data.remove(this.size() - 1);
- downHeapify(0);
map.remove(rv);
+ downHeapify(0);
return rv;
}
+ /**
+ * Restores the heap property by moving the item at the given index downwards.
+ *
+ * @param pi the index of the current item
+ */
private void downHeapify(int pi) {
int lci = 2 * pi + 1;
int rci = 2 * pi + 2;
@@ -58,15 +96,35 @@ private void downHeapify(int pi) {
}
}
+ /**
+ * Retrieves the maximum item from the heap without removing it.
+ *
+ * @return the maximum item
+ */
public T get() {
- return this.data.get(0);
+ if (isEmpty()) {
+ throw new IllegalStateException("Heap is empty");
+ }
+ return this.data.getFirst();
}
- // t has higher property then return +ve
+ /**
+ * Compares two items to determine their order.
+ *
+ * @param t the first item
+ * @param o the second item
+ * @return a positive integer if t is greater than o, negative if t is less, and zero if they are equal
+ */
private int isLarger(T t, T o) {
return t.compareTo(o);
}
+ /**
+ * Swaps two items in the heap and updates their indices in the map.
+ *
+ * @param i index of the first item
+ * @param j index of the second item
+ */
private void swap(int i, int j) {
T ith = this.data.get(i);
T jth = this.data.get(j);
@@ -76,9 +134,16 @@ private void swap(int i, int j) {
map.put(jth, i);
}
+ /**
+ * Updates the priority of the specified item by restoring the heap property.
+ *
+ * @param item the item whose priority is to be updated
+ */
public void updatePriority(T item) {
+ if (!map.containsKey(item)) {
+ throw new IllegalArgumentException("Item not found in the heap");
+ }
int index = map.get(item);
- // because we enter lesser value then old vale
upHeapify(index);
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java b/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java
index 63e101d9b13d..8cb93edf78f3 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java
@@ -40,5 +40,5 @@ public interface Heap {
* @param elementIndex int containing the position in the heap of the
* element to be deleted.
*/
- void deleteElement(int elementIndex);
+ void deleteElement(int elementIndex) throws EmptyHeapException;
}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
index 7c457a340645..a9cfac7036eb 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
@@ -1,14 +1,24 @@
package com.thealgorithms.datastructures.heaps;
/**
- * Class for heap elements.
+ * Class representing an element in a heap.
*
*
- * A heap element contains two attributes: a key which will be used to build the
- * tree (int or double, either primitive type or object) and any kind of
- * IMMUTABLE object the user sees fit to carry any information he/she likes. Be
- * aware that the use of a mutable object might jeopardize the integrity of this
- * information.
+ * A heap element contains two attributes: a key used for ordering in the heap
+ * (which can be of type int or double, either as primitive types or as wrapper objects)
+ * and an additional immutable object that can store any supplementary information the user desires.
+ * Note that using mutable objects may compromise the integrity of this information.
+ *
+ *
+ *
+ * The key attribute is used to determine the order of elements in the heap,
+ * while the additionalInfo attribute can carry user-defined data associated with the key.
+ *
+ *
+ *
+ * This class provides multiple constructors to accommodate various key types and includes
+ * methods to retrieve the key and additional information.
+ *
*
* @author Nicolas Renard
*/
@@ -19,9 +29,10 @@ public class HeapElement {
// Constructors
/**
- * @param key : a number of primitive type 'double'
- * @param info : any kind of IMMUTABLE object. May be null, since the
- * purpose is only to carry additional information of use for the user
+ * Creates a HeapElement with the specified key and additional information.
+ *
+ * @param key the key of the element (primitive type double)
+ * @param info any immutable object containing additional information, may be null
*/
public HeapElement(double key, Object info) {
this.key = key;
@@ -29,9 +40,10 @@ public HeapElement(double key, Object info) {
}
/**
- * @param key : a number of primitive type 'int'
- * @param info : any kind of IMMUTABLE object. May be null, since the
- * purpose is only to carry additional information of use for the user
+ * Creates a HeapElement with the specified key and additional information.
+ *
+ * @param key the key of the element (primitive type int)
+ * @param info any immutable object containing additional information, may be null
*/
public HeapElement(int key, Object info) {
this.key = key;
@@ -39,9 +51,10 @@ public HeapElement(int key, Object info) {
}
/**
- * @param key : a number of object type 'Integer'
- * @param info : any kind of IMMUTABLE object. May be null, since the
- * purpose is only to carry additional information of use for the user
+ * Creates a HeapElement with the specified key and additional information.
+ *
+ * @param key the key of the element (object type Integer)
+ * @param info any immutable object containing additional information, may be null
*/
public HeapElement(Integer key, Object info) {
this.key = key;
@@ -49,9 +62,10 @@ public HeapElement(Integer key, Object info) {
}
/**
- * @param key : a number of object type 'Double'
- * @param info : any kind of IMMUTABLE object. May be null, since the
- * purpose is only to carry additional information of use for the user
+ * Creates a HeapElement with the specified key and additional information.
+ *
+ * @param key the key of the element (object type Double)
+ * @param info any immutable object containing additional information, may be null
*/
public HeapElement(Double key, Object info) {
this.key = key;
@@ -59,7 +73,9 @@ public HeapElement(Double key, Object info) {
}
/**
- * @param key : a number of primitive type 'double'
+ * Creates a HeapElement with the specified key.
+ *
+ * @param key the key of the element (primitive type double)
*/
public HeapElement(double key) {
this.key = key;
@@ -67,7 +83,9 @@ public HeapElement(double key) {
}
/**
- * @param key : a number of primitive type 'int'
+ * Creates a HeapElement with the specified key.
+ *
+ * @param key the key of the element (primitive type int)
*/
public HeapElement(int key) {
this.key = key;
@@ -75,7 +93,9 @@ public HeapElement(int key) {
}
/**
- * @param key : a number of object type 'Integer'
+ * Creates a HeapElement with the specified key.
+ *
+ * @param key the key of the element (object type Integer)
*/
public HeapElement(Integer key) {
this.key = key;
@@ -83,7 +103,9 @@ public HeapElement(Integer key) {
}
/**
- * @param key : a number of object type 'Double'
+ * Creates a HeapElement with the specified key.
+ *
+ * @param key the key of the element (object type Double)
*/
public HeapElement(Double key) {
this.key = key;
@@ -92,46 +114,60 @@ public HeapElement(Double key) {
// Getters
/**
- * @return the object containing the additional info provided by the user.
+ * Returns the object containing the additional information provided by the user.
+ *
+ * @return the additional information
*/
public Object getInfo() {
return additionalInfo;
}
/**
- * @return the key value of the element
+ * Returns the key value of the element.
+ *
+ * @return the key of the element
*/
public double getKey() {
return key;
}
// Overridden object methods
+ /**
+ * Returns a string representation of the heap element.
+ *
+ * @return a string describing the key and additional information
+ */
+ @Override
public String toString() {
- return "Key: " + key + " - " + additionalInfo.toString();
+ return "Key: " + key + " - " + (additionalInfo != null ? additionalInfo.toString() : "No additional info");
}
/**
- * @param otherHeapElement
+ * @param o : an object to compare with the current element
* @return true if the keys on both elements are identical and the
* additional info objects are identical.
*/
@Override
public boolean equals(Object o) {
- if (o != null) {
- if (!(o instanceof HeapElement)) {
- return false;
- }
- HeapElement otherHeapElement = (HeapElement) o;
- return ((this.key == otherHeapElement.key) && (this.additionalInfo.equals(otherHeapElement.additionalInfo)));
+ if (o instanceof HeapElement otherHeapElement) {
+ return this.key == otherHeapElement.key && (this.additionalInfo != null ? this.additionalInfo.equals(otherHeapElement.additionalInfo) : otherHeapElement.additionalInfo == null);
}
return false;
}
+ /**
+ * Returns a hash code value for the heap element.
+ *
+ * @return a hash code value for this heap element
+ */
@Override
public int hashCode() {
- int result = 0;
- result = 31 * result + (int) key;
- result = 31 * result + (additionalInfo != null ? additionalInfo.hashCode() : 0);
+ int result = 31 * (int) key;
+ result += (additionalInfo != null) ? additionalInfo.hashCode() : 0;
return result;
}
+
+ public String getValue() {
+ return additionalInfo.toString();
+ }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java b/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java
new file mode 100644
index 000000000000..7ad92e8ba3c1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java
@@ -0,0 +1,60 @@
+
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.PriorityQueue;
+
+/**
+ * This class provides methods to find the Kth largest or Kth smallest element
+ * in an array using heaps. It leverages a min-heap to find the Kth largest element
+ * and a max-heap to find the Kth smallest element efficiently.
+ *
+ * @author Hardvan
+ */
+public final class KthElementFinder {
+ private KthElementFinder() {
+ }
+
+ /**
+ * Finds the Kth largest element in the given array.
+ * Uses a min-heap of size K to track the largest K elements.
+ *
+ * Time Complexity: O(n * log(k)), where n is the size of the input array.
+ * Space Complexity: O(k), as we maintain a heap of size K.
+ *
+ * @param nums the input array of integers
+ * @param k the desired Kth position (1-indexed, i.e., 1 means the largest element)
+ * @return the Kth largest element in the array
+ */
+ public static int findKthLargest(int[] nums, int k) {
+ PriorityQueue minHeap = new PriorityQueue<>(k);
+ for (int num : nums) {
+ minHeap.offer(num);
+ if (minHeap.size() > k) {
+ minHeap.poll();
+ }
+ }
+ return minHeap.peek();
+ }
+
+ /**
+ * Finds the Kth smallest element in the given array.
+ * Uses a max-heap of size K to track the smallest K elements.
+ *
+ * Time Complexity: O(n * log(k)), where n is the size of the input array.
+ * Space Complexity: O(k), as we maintain a heap of size K.
+ *
+ * @param nums the input array of integers
+ * @param k the desired Kth position (1-indexed, i.e., 1 means the smallest element)
+ * @return the Kth smallest element in the array
+ */
+ public static int findKthSmallest(int[] nums, int k) {
+ PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b - a);
+ for (int num : nums) {
+ maxHeap.offer(num);
+ if (maxHeap.size() > k) {
+ maxHeap.poll();
+ }
+ }
+ return maxHeap.peek();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java
index ca18673c6724..1c91d24f0fb5 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java
@@ -2,24 +2,33 @@
import java.util.ArrayList;
-/*
- * This is a leftist heap that follows the same operations as a
- * binary min heap, but may be unbalanced at times and follows a
- * leftist property, in which the left side is more heavy on the
- * right based on the null-path length (npl) values.
+/**
+ * This class implements a Leftist Heap, which is a type of priority queue
+ * that follows similar operations to a binary min-heap but allows for
+ * unbalanced structures based on the leftist property.
*
- * Source: https://iq.opengenus.org/leftist-heap/
+ *
+ * A Leftist Heap maintains the leftist property, which ensures that the
+ * left subtree is heavier than the right subtree based on the
+ * null-path length (npl) values. This allows for efficient merging
+ * of heaps and supports operations like insertion, extraction of
+ * the minimum element, and in-order traversal.
+ *
*
+ *
+ * For more information on Leftist Heaps, visit:
+ * OpenGenus
+ *
*/
-
public class LeftistHeap {
+ // Node class representing each element in the Leftist Heap
private static final class Node {
private final int element;
private int npl;
private Node left;
private Node right;
- // Node constructor setting the data element and left/right pointers to null
+ // Node constructor that initializes the element and sets child pointers to null
private Node(int element) {
this.element = element;
left = null;
@@ -30,31 +39,45 @@ private Node(int element) {
private Node root;
- // Constructor
+ // Constructor initializing an empty Leftist Heap
public LeftistHeap() {
root = null;
}
- // Checks if heap is empty
+ /**
+ * Checks if the heap is empty.
+ *
+ * @return true if the heap is empty; false otherwise
+ */
public boolean isEmpty() {
return root == null;
}
- // Resets structure to initial state
+ /**
+ * Resets the heap to its initial state, effectively clearing all elements.
+ */
public void clear() {
- // We will put head is null
- root = null;
+ root = null; // Set root to null to clear the heap
}
- // Merge function that merges the contents of another leftist heap with the
- // current one
+ /**
+ * Merges the contents of another Leftist Heap into this one.
+ *
+ * @param h1 the LeftistHeap to be merged into this heap
+ */
public void merge(LeftistHeap h1) {
- // If the present function is rhs then we ignore the merge
+ // Merge the current heap with the provided heap and set the provided heap's root to null
root = merge(root, h1.root);
h1.root = null;
}
- // Function merge with two Nodes a and b
+ /**
+ * Merges two nodes, maintaining the leftist property.
+ *
+ * @param a the first node
+ * @param b the second node
+ * @return the merged node maintaining the leftist property
+ */
public Node merge(Node a, Node b) {
if (a == null) {
return b;
@@ -64,17 +87,17 @@ public Node merge(Node a, Node b) {
return a;
}
- // Violates leftist property, so must do a swap
+ // Ensure that the leftist property is maintained
if (a.element > b.element) {
Node temp = a;
a = b;
b = temp;
}
- // Now we call the function merge to merge a and b
+ // Merge the right child of node a with node b
a.right = merge(a.right, b);
- // Violates leftist property so must swap here
+ // If left child is null, make right child the left child
if (a.left == null) {
a.left = a.right;
a.right = null;
@@ -89,14 +112,21 @@ public Node merge(Node a, Node b) {
return a;
}
- // Function insert. Uses the merge function to add the data
+ /**
+ * Inserts a new element into the Leftist Heap.
+ *
+ * @param a the element to be inserted
+ */
public void insert(int a) {
root = merge(new Node(a), root);
}
- // Returns and removes the minimum element in the heap
+ /**
+ * Extracts and removes the minimum element from the heap.
+ *
+ * @return the minimum element in the heap, or -1 if the heap is empty
+ */
public int extractMin() {
- // If is empty return -1
if (isEmpty()) {
return -1;
}
@@ -106,14 +136,23 @@ public int extractMin() {
return min;
}
- // Function returning a list of an in order traversal of the data structure
+ /**
+ * Returns a list of the elements in the heap in in-order traversal.
+ *
+ * @return an ArrayList containing the elements in in-order
+ */
public ArrayList inOrder() {
ArrayList lst = new ArrayList<>();
inOrderAux(root, lst);
return new ArrayList<>(lst);
}
- // Auxiliary function for in_order
+ /**
+ * Auxiliary function for in-order traversal
+ *
+ * @param n the current node
+ * @param lst the list to store the elements in in-order
+ */
private void inOrderAux(Node n, ArrayList lst) {
if (n == null) {
return;
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
index 9010aae4cae5..5b4b29cf1c2d 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
@@ -4,128 +4,245 @@
import java.util.List;
/**
- * Heap tree where a node's key is higher than or equal to its parent's and
- * lower than or equal to its children's.
+ * A Max Heap implementation where each node's key is higher than or equal to its children's keys.
+ * This data structure provides O(log n) time complexity for insertion and deletion operations,
+ * and O(1) for retrieving the maximum element.
+ *
+ * Properties:
+ * 1. Complete Binary Tree
+ * 2. Parent node's key ≥ Children nodes' keys
+ * 3. Root contains the maximum element
+ *
+ * Example usage:
+ *
+ * List elements = Arrays.asList(
+ * new HeapElement(5, "Five"),
+ * new HeapElement(2, "Two")
+ * );
+ * MaxHeap heap = new MaxHeap(elements);
+ * heap.insertElement(new HeapElement(7, "Seven"));
+ * HeapElement max = heap.getElement(); // Returns and removes the maximum element
+ *
*
* @author Nicolas Renard
*/
public class MaxHeap implements Heap {
+ /** The internal list that stores heap elements */
private final List maxHeap;
+ /**
+ * Constructs a new MaxHeap from a list of elements.
+ * Null elements in the input list are ignored.
+ *
+ * @param listElements List of HeapElement objects to initialize the heap
+ * @throws IllegalArgumentException if the input list is null
+ */
public MaxHeap(List listElements) {
+ if (listElements == null) {
+ throw new IllegalArgumentException("Input list cannot be null");
+ }
+
maxHeap = new ArrayList<>();
+
+ // Safe initialization: directly add non-null elements first
for (HeapElement heapElement : listElements) {
if (heapElement != null) {
- insertElement(heapElement);
- } else {
- System.out.println("Null element. Not added to heap");
+ maxHeap.add(heapElement);
}
}
- if (maxHeap.isEmpty()) {
- System.out.println("No element has been added, empty heap.");
+
+ // Heapify the array bottom-up
+ for (int i = maxHeap.size() / 2; i >= 0; i--) {
+ heapifyDown(i + 1); // +1 because heapifyDown expects 1-based index
+ }
+ }
+
+ /**
+ * Maintains heap properties by moving an element down the heap.
+ * Similar to toggleDown but used specifically during initialization.
+ *
+ * @param elementIndex 1-based index of the element to heapify
+ */
+ private void heapifyDown(int elementIndex) {
+ int largest = elementIndex - 1;
+ int leftChild = 2 * elementIndex - 1;
+ int rightChild = 2 * elementIndex;
+
+ if (leftChild < maxHeap.size() && maxHeap.get(leftChild).getKey() > maxHeap.get(largest).getKey()) {
+ largest = leftChild;
+ }
+
+ if (rightChild < maxHeap.size() && maxHeap.get(rightChild).getKey() > maxHeap.get(largest).getKey()) {
+ largest = rightChild;
+ }
+
+ if (largest != elementIndex - 1) {
+ HeapElement swap = maxHeap.get(elementIndex - 1);
+ maxHeap.set(elementIndex - 1, maxHeap.get(largest));
+ maxHeap.set(largest, swap);
+
+ heapifyDown(largest + 1);
}
}
/**
- * Get the element at a given index. The key for the list is equal to index
- * value - 1
+ * Retrieves the element at the specified index without removing it.
+ * Note: The index is 1-based for consistency with heap operations.
*
- * @param elementIndex index
- * @return heapElement
+ * @param elementIndex 1-based index of the element to retrieve
+ * @return HeapElement at the specified index
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
public HeapElement getElement(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
return maxHeap.get(elementIndex - 1);
}
- // Get the key of the element at a given index
+ /**
+ * Retrieves the key value of an element at the specified index.
+ *
+ * @param elementIndex 1-based index of the element
+ * @return double value representing the key
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
private double getElementKey(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
-
return maxHeap.get(elementIndex - 1).getKey();
}
- // Swaps two elements in the heap
+ /**
+ * Swaps two elements in the heap.
+ *
+ * @param index1 1-based index of first element
+ * @param index2 1-based index of second element
+ */
private void swap(int index1, int index2) {
HeapElement temporaryElement = maxHeap.get(index1 - 1);
maxHeap.set(index1 - 1, maxHeap.get(index2 - 1));
maxHeap.set(index2 - 1, temporaryElement);
}
- // Toggle an element up to its right place as long as its key is lower than its parent's
+ /**
+ * Moves an element up the heap until heap properties are satisfied.
+ * This operation is called after insertion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move up
+ */
private void toggleUp(int elementIndex) {
double key = maxHeap.get(elementIndex - 1).getKey();
- while (getElementKey((int) Math.floor(elementIndex / 2.0)) < key) {
+ while (elementIndex > 1 && getElementKey((int) Math.floor(elementIndex / 2.0)) < key) {
swap(elementIndex, (int) Math.floor(elementIndex / 2.0));
elementIndex = (int) Math.floor(elementIndex / 2.0);
}
}
- // Toggle an element down to its right place as long as its key is higher
- // than any of its children's
+ /**
+ * Moves an element down the heap until heap properties are satisfied.
+ * This operation is called after deletion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move down
+ */
private void toggleDown(int elementIndex) {
double key = maxHeap.get(elementIndex - 1).getKey();
- boolean wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size())));
- while ((2 * elementIndex <= maxHeap.size()) && wrongOrder) {
- // Check whether it shall swap the element with its left child or its right one if any.
- if ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2))) {
- swap(elementIndex, 2 * elementIndex + 1);
- elementIndex = 2 * elementIndex + 1;
+ boolean wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1));
+
+ while (2 * elementIndex <= maxHeap.size() && wrongOrder) {
+ int largerChildIndex;
+ if (2 * elementIndex + 1 <= maxHeap.size() && getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2)) {
+ largerChildIndex = 2 * elementIndex + 1;
} else {
- swap(elementIndex, 2 * elementIndex);
- elementIndex = 2 * elementIndex;
+ largerChildIndex = 2 * elementIndex;
}
- wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size())));
+
+ swap(elementIndex, largerChildIndex);
+ elementIndex = largerChildIndex;
+
+ wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1));
}
}
- private HeapElement extractMax() {
- HeapElement result = maxHeap.get(0);
- deleteElement(0);
+ /**
+ * Extracts and returns the maximum element from the heap.
+ *
+ * @return HeapElement with the highest key
+ * @throws EmptyHeapException if the heap is empty
+ */
+ private HeapElement extractMax() throws EmptyHeapException {
+ if (maxHeap.isEmpty()) {
+ throw new EmptyHeapException("Cannot extract from an empty heap");
+ }
+ HeapElement result = maxHeap.getFirst();
+ deleteElement(1);
return result;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public final void insertElement(HeapElement element) {
+ public void insertElement(HeapElement element) {
+ if (element == null) {
+ throw new IllegalArgumentException("Cannot insert null element");
+ }
maxHeap.add(element);
toggleUp(maxHeap.size());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public void deleteElement(int elementIndex) {
+ public void deleteElement(int elementIndex) throws EmptyHeapException {
if (maxHeap.isEmpty()) {
- try {
- throw new EmptyHeapException("Attempt to delete an element from an empty heap");
- } catch (EmptyHeapException e) {
- e.printStackTrace();
- }
+ throw new EmptyHeapException("Cannot delete from an empty heap");
}
if ((elementIndex > maxHeap.size()) || (elementIndex <= 0)) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
- // The last element in heap replaces the one to be deleted
- maxHeap.set(elementIndex - 1, getElement(maxHeap.size()));
- maxHeap.remove(maxHeap.size());
- // Shall the new element be moved up...
- if (getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) {
- toggleUp(elementIndex);
- } // ... or down ?
- else if (((2 * elementIndex <= maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2))) || ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2)))) {
- toggleDown(elementIndex);
+
+ // Replace with last element and remove last position
+ maxHeap.set(elementIndex - 1, maxHeap.getLast());
+ maxHeap.removeLast();
+
+ // No need to toggle if we just removed the last element
+ if (!maxHeap.isEmpty() && elementIndex <= maxHeap.size()) {
+ // Determine whether to toggle up or down
+ if (elementIndex > 1 && getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) {
+ toggleUp(elementIndex);
+ } else {
+ toggleDown(elementIndex);
+ }
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public HeapElement getElement() throws EmptyHeapException {
- try {
- return extractMax();
- } catch (Exception e) {
- throw new EmptyHeapException("Heap is empty. Error retrieving element", e);
- }
+ return extractMax();
+ }
+
+ /**
+ * Returns the current size of the heap.
+ *
+ * @return number of elements in the heap
+ */
+ public int size() {
+ return maxHeap.size();
+ }
+
+ /**
+ * Checks if the heap is empty.
+ *
+ * @return true if the heap contains no elements
+ */
+ public boolean isEmpty() {
+ return maxHeap.isEmpty();
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java b/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java
new file mode 100644
index 000000000000..4e74aaec4a10
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.PriorityQueue;
+
+/**
+ * This class maintains the median of a dynamically changing data stream using
+ * two heaps: a max-heap and a min-heap. The max-heap stores the smaller half
+ * of the numbers, and the min-heap stores the larger half.
+ * This data structure ensures that retrieving the median is efficient.
+ *
+ * Time Complexity:
+ * - Adding a number: O(log n) due to heap insertion.
+ * - Finding the median: O(1).
+ *
+ * Space Complexity: O(n), where n is the total number of elements added.
+ *
+ * @author Hardvan
+ */
+public final class MedianFinder {
+ MedianFinder() {
+ }
+
+ private PriorityQueue minHeap = new PriorityQueue<>();
+ private PriorityQueue maxHeap = new PriorityQueue<>((a, b) -> b - a);
+
+ /**
+ * Adds a new number to the data stream. The number is placed in the appropriate
+ * heap to maintain the balance between the two heaps.
+ *
+ * @param num the number to be added to the data stream
+ */
+ public void addNum(int num) {
+ if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
+ maxHeap.offer(num);
+ } else {
+ minHeap.offer(num);
+ }
+
+ if (maxHeap.size() > minHeap.size() + 1) {
+ minHeap.offer(maxHeap.poll());
+ } else if (minHeap.size() > maxHeap.size()) {
+ maxHeap.offer(minHeap.poll());
+ }
+ }
+
+ /**
+ * Finds the median of the numbers added so far. If the total number of elements
+ * is even, the median is the average of the two middle elements. If odd, the
+ * median is the middle element from the max-heap.
+ *
+ * @return the median of the numbers in the data stream
+ */
+ public double findMedian() {
+ if (maxHeap.size() == minHeap.size()) {
+ return (maxHeap.peek() + minHeap.peek()) / 2.0;
+ }
+ return maxHeap.peek();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java b/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java
new file mode 100644
index 000000000000..e41711f05914
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.Comparator;
+import java.util.PriorityQueue;
+
+/**
+ * This class provides a method to merge multiple sorted arrays into a single sorted array.
+ * It utilizes a min-heap to efficiently retrieve the smallest elements from each array.
+ *
+ * Time Complexity: O(n * log k), where n is the total number of elements across all arrays
+ * and k is the number of arrays.
+ *
+ * Space Complexity: O(k) for the heap, where k is the number of arrays.
+ *
+ * @author Hardvan
+ */
+public final class MergeKSortedArrays {
+ private MergeKSortedArrays() {
+ }
+
+ /**
+ * Merges k sorted arrays into one sorted array using a min-heap.
+ * Steps:
+ * 1. Create a min-heap to store elements in the format: {value, array index, element index}
+ * 2. Add the first element from each array to the heap
+ * 3. While the heap is not empty, remove the smallest element from the heap
+ * and add it to the result array. If there are more elements in the same array,
+ * add the next element to the heap.
+ * Continue until all elements have been processed.
+ * The result array will contain all elements in sorted order.
+ * 4. Return the result array.
+ *
+ * @param arrays a 2D array, where each subarray is sorted in non-decreasing order
+ * @return a single sorted array containing all elements from the input arrays
+ */
+ public static int[] mergeKArrays(int[][] arrays) {
+ PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
+
+ int totalLength = 0;
+ for (int i = 0; i < arrays.length; i++) {
+ if (arrays[i].length > 0) {
+ minHeap.offer(new int[] {arrays[i][0], i, 0});
+ totalLength += arrays[i].length;
+ }
+ }
+
+ int[] result = new int[totalLength];
+ int index = 0;
+ while (!minHeap.isEmpty()) {
+ int[] top = minHeap.poll();
+ result[index++] = top[0];
+
+ if (top[2] + 1 < arrays[top[1]].length) {
+ minHeap.offer(new int[] {arrays[top[1]][top[2] + 1], top[1], top[2] + 1});
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java
index 46864fba0047..3a4822142b5f 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java
@@ -4,8 +4,25 @@
import java.util.List;
/**
- * Heap tree where a node's key is higher than or equal to its parent's and
- * lower than or equal to its children's.
+ * A Min Heap implementation where each node's key is lower than or equal to its children's keys.
+ * This data structure provides O(log n) time complexity for insertion and deletion operations,
+ * and O(1) for retrieving the minimum element.
+ *
+ * Properties:
+ * 1. Complete Binary Tree
+ * 2. Parent node's key ≤ Children nodes' keys
+ * 3. Root contains the minimum element
+ *
+ * Example usage:
+ * ```java
+ * List elements = Arrays.asList(
+ * new HeapElement(5, "Five"),
+ * new HeapElement(2, "Two")
+ * );
+ * MinHeap heap = new MinHeap(elements);
+ * heap.insertElement(new HeapElement(1, "One"));
+ * HeapElement min = heap.getElement(); // Returns and removes the minimum element
+ * ```
*
* @author Nicolas Renard
*/
@@ -13,113 +30,242 @@ public class MinHeap implements Heap {
private final List minHeap;
+ /**
+ * Constructs a new MinHeap from a list of elements.
+ * Null elements in the input list are ignored with a warning message.
+ *
+ * @param listElements List of HeapElement objects to initialize the heap
+ * @throws IllegalArgumentException if the input list is null
+ */
public MinHeap(List listElements) {
+ if (listElements == null) {
+ throw new IllegalArgumentException("Input list cannot be null");
+ }
+
minHeap = new ArrayList<>();
+
+ // Safe initialization: directly add elements first
for (HeapElement heapElement : listElements) {
if (heapElement != null) {
- insertElement(heapElement);
+ minHeap.add(heapElement);
} else {
System.out.println("Null element. Not added to heap");
}
}
+
+ // Heapify the array bottom-up
+ for (int i = minHeap.size() / 2; i >= 0; i--) {
+ heapifyDown(i + 1);
+ }
+
if (minHeap.isEmpty()) {
System.out.println("No element has been added, empty heap.");
}
}
- // Get the element at a given index. The key for the list is equal to index value - 1
+ /**
+ * Retrieves the element at the specified index without removing it.
+ * Note: The index is 1-based for consistency with heap operations.
+ *
+ * @param elementIndex 1-based index of the element to retrieve
+ * @return HeapElement at the specified index
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
public HeapElement getElement(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > minHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
}
return minHeap.get(elementIndex - 1);
}
- // Get the key of the element at a given index
+ /**
+ * Retrieves the key value of an element at the specified index.
+ *
+ * @param elementIndex 1-based index of the element
+ * @return double value representing the key
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
private double getElementKey(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > minHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
}
-
return minHeap.get(elementIndex - 1).getKey();
}
- // Swaps two elements in the heap
+ /**
+ * Swaps two elements in the heap.
+ *
+ * @param index1 1-based index of first element
+ * @param index2 1-based index of second element
+ */
private void swap(int index1, int index2) {
HeapElement temporaryElement = minHeap.get(index1 - 1);
minHeap.set(index1 - 1, minHeap.get(index2 - 1));
minHeap.set(index2 - 1, temporaryElement);
}
- // Toggle an element up to its right place as long as its key is lower than its parent's
+ /**
+ * Maintains heap properties by moving an element down the heap.
+ * Used specifically during initialization.
+ *
+ * @param elementIndex 1-based index of the element to heapify
+ */
+ private void heapifyDown(int elementIndex) {
+ int smallest = elementIndex - 1; // Convert to 0-based index
+ int leftChild = 2 * elementIndex - 1;
+ int rightChild = 2 * elementIndex;
+
+ // Check if left child is smaller than root
+ if (leftChild < minHeap.size() && minHeap.get(leftChild).getKey() < minHeap.get(smallest).getKey()) {
+ smallest = leftChild;
+ }
+
+ // Check if right child is smaller than smallest so far
+ if (rightChild < minHeap.size() && minHeap.get(rightChild).getKey() < minHeap.get(smallest).getKey()) {
+ smallest = rightChild;
+ }
+
+ // If smallest is not root
+ if (smallest != elementIndex - 1) {
+ HeapElement swap = minHeap.get(elementIndex - 1);
+ minHeap.set(elementIndex - 1, minHeap.get(smallest));
+ minHeap.set(smallest, swap);
+
+ // Recursively heapify the affected sub-tree
+ heapifyDown(smallest + 1); // Convert back to 1-based index
+ }
+ }
+
+ /**
+ * Moves an element up the heap until heap properties are satisfied.
+ * This operation is called after insertion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move up
+ */
private void toggleUp(int elementIndex) {
+ if (elementIndex <= 1) {
+ return;
+ }
+
double key = minHeap.get(elementIndex - 1).getKey();
- while (getElementKey((int) Math.floor(elementIndex / 2.0) + 1) > key) {
- swap(elementIndex, (int) Math.floor(elementIndex / 2.0));
- elementIndex = (int) Math.floor(elementIndex / 2.0);
+ int parentIndex = (int) Math.floor(elementIndex / 2.0);
+
+ while (elementIndex > 1 && getElementKey(parentIndex) > key) {
+ swap(elementIndex, parentIndex);
+ elementIndex = parentIndex;
+ parentIndex = (int) Math.floor(elementIndex / 2.0);
}
}
- // Toggle an element down to its right place as long as its key is higher
- // than any of its children's
+ /**
+ * Moves an element down the heap until heap properties are satisfied.
+ * This operation is called after deletion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move down
+ */
private void toggleDown(int elementIndex) {
double key = minHeap.get(elementIndex - 1).getKey();
- boolean wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size())));
- while ((2 * elementIndex <= minHeap.size()) && wrongOrder) {
- // Check whether it shall swap the element with its left child or its right one if any.
- if ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex * 2 + 1) < getElementKey(elementIndex * 2))) {
- swap(elementIndex, 2 * elementIndex + 1);
- elementIndex = 2 * elementIndex + 1;
- } else {
- swap(elementIndex, 2 * elementIndex);
- elementIndex = 2 * elementIndex;
+ int size = minHeap.size();
+
+ while (true) {
+ int smallest = elementIndex;
+ int leftChild = 2 * elementIndex;
+ int rightChild = 2 * elementIndex + 1;
+
+ if (leftChild <= size && getElementKey(leftChild) < key) {
+ smallest = leftChild;
+ }
+
+ if (rightChild <= size && getElementKey(rightChild) < getElementKey(smallest)) {
+ smallest = rightChild;
+ }
+
+ if (smallest == elementIndex) {
+ break;
}
- wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size())));
+
+ swap(elementIndex, smallest);
+ elementIndex = smallest;
}
}
- private HeapElement extractMin() {
- HeapElement result = minHeap.get(0);
- deleteElement(0);
+ /**
+ * Extracts and returns the minimum element from the heap.
+ *
+ * @return HeapElement with the lowest key
+ * @throws EmptyHeapException if the heap is empty
+ */
+ private HeapElement extractMin() throws EmptyHeapException {
+ if (minHeap.isEmpty()) {
+ throw new EmptyHeapException("Cannot extract from empty heap");
+ }
+ HeapElement result = minHeap.getFirst();
+ deleteElement(1);
return result;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public final void insertElement(HeapElement element) {
+ public void insertElement(HeapElement element) {
+ if (element == null) {
+ throw new IllegalArgumentException("Cannot insert null element");
+ }
minHeap.add(element);
toggleUp(minHeap.size());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public void deleteElement(int elementIndex) {
+ public void deleteElement(int elementIndex) throws EmptyHeapException {
if (minHeap.isEmpty()) {
- try {
- throw new EmptyHeapException("Attempt to delete an element from an empty heap");
- } catch (EmptyHeapException e) {
- e.printStackTrace();
- }
+ throw new EmptyHeapException("Cannot delete from empty heap");
}
if ((elementIndex > minHeap.size()) || (elementIndex <= 0)) {
- throw new IndexOutOfBoundsException("Index out of heap range");
- }
- // The last element in heap replaces the one to be deleted
- minHeap.set(elementIndex - 1, getElement(minHeap.size()));
- minHeap.remove(minHeap.size());
- // Shall the new element be moved up...
- if (getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) {
- toggleUp(elementIndex);
- } // ... or down ?
- else if (((2 * elementIndex <= minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2))) || ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2)))) {
- toggleDown(elementIndex);
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
+ }
+
+ // Replace with last element and remove last position
+ minHeap.set(elementIndex - 1, minHeap.getLast());
+ minHeap.removeLast();
+
+ // No need to toggle if we just removed the last element
+ if (!minHeap.isEmpty() && elementIndex <= minHeap.size()) {
+ // Determine whether to toggle up or down
+ if (elementIndex > 1 && getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) {
+ toggleUp(elementIndex);
+ } else {
+ toggleDown(elementIndex);
+ }
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public HeapElement getElement() throws EmptyHeapException {
- try {
- return extractMin();
- } catch (Exception e) {
- throw new EmptyHeapException("Heap is empty. Error retrieving element", e);
- }
+ return extractMin();
+ }
+
+ /**
+ * Returns the current size of the heap.
+ *
+ * @return number of elements in the heap
+ */
+ public int size() {
+ return minHeap.size();
+ }
+
+ /**
+ * Checks if the heap is empty.
+ *
+ * @return true if the heap contains no elements
+ */
+ public boolean isEmpty() {
+ return minHeap.isEmpty();
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java
index 9d19e9aaee1a..a1360b14dc5a 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java
@@ -1,16 +1,23 @@
package com.thealgorithms.datastructures.heaps;
/**
- * Minimum Priority Queue It is a part of heap data structure A heap is a
- * specific tree based data structure in which all the nodes of tree are in a
- * specific order. that is the children are arranged in some respect of their
- * parents, can either be greater or less than the parent. This makes it a min
- * priority queue or max priority queue.
+ * A MinPriorityQueue is a specialized data structure that maintains the
+ * min-heap property, where the smallest element has the highest priority.
*
- *
+ *
In a min-priority queue, every parent node is less than or equal
+ * to its child nodes, which ensures that the smallest element can
+ * always be efficiently retrieved.
*
- *
- * Functions: insert, delete, peek, isEmpty, print, heapSort, sink
+ *
Functions:
+ *
+ * - insert(int key): Inserts a new key into the queue.
+ * - delete(): Removes and returns the highest priority value (the minimum).
+ * - peek(): Returns the highest priority value without removing it.
+ * - isEmpty(): Checks if the queue is empty.
+ * - isFull(): Checks if the queue is full.
+ * - heapSort(): Sorts the elements in ascending order.
+ * - print(): Prints the current elements in the queue.
+ *
*/
public class MinPriorityQueue {
@@ -18,18 +25,25 @@ public class MinPriorityQueue {
private final int capacity;
private int size;
- // class the constructor and initializes the capacity
- MinPriorityQueue(int c) {
+ /**
+ * Initializes a new MinPriorityQueue with a specified capacity.
+ *
+ * @param c the maximum number of elements the queue can hold
+ */
+ public MinPriorityQueue(int c) {
this.capacity = c;
this.size = 0;
this.heap = new int[c + 1];
}
- // inserts the key at the end and rearranges it
- // so that the binary heap is in appropriate order
+ /**
+ * Inserts a new key into the min-priority queue.
+ *
+ * @param key the value to be inserted
+ */
public void insert(int key) {
if (this.isFull()) {
- return;
+ throw new IllegalStateException("MinPriorityQueue is full. Cannot insert new element.");
}
this.heap[this.size + 1] = key;
int k = this.size + 1;
@@ -44,89 +58,98 @@ public void insert(int key) {
this.size++;
}
- // returns the highest priority value
+ /**
+ * Retrieves the highest priority value (the minimum) without removing it.
+ *
+ * @return the minimum value in the queue
+ * @throws IllegalStateException if the queue is empty
+ */
public int peek() {
+ if (isEmpty()) {
+ throw new IllegalStateException("MinPriorityQueue is empty. Cannot peek.");
+ }
return this.heap[1];
}
- // returns boolean value whether the heap is empty or not
+ /**
+ * Checks whether the queue is empty.
+ *
+ * @return true if the queue is empty, false otherwise
+ */
public boolean isEmpty() {
- return 0 == this.size;
+ return size == 0;
}
- // returns boolean value whether the heap is full or not
+ /**
+ * Checks whether the queue is full.
+ *
+ * @return true if the queue is full, false otherwise
+ */
public boolean isFull() {
- return this.size == this.capacity;
+ return size == capacity;
}
- // prints the heap
+ /**
+ * Prints the elements of the queue.
+ */
public void print() {
- for (int i = 1; i <= this.capacity; i++) {
+ for (int i = 1; i <= this.size; i++) {
System.out.print(this.heap[i] + " ");
}
System.out.println();
}
- // heap sorting can be done by performing
- // delete function to the number of times of the size of the heap
- // it returns reverse sort because it is a min priority queue
+ /**
+ * Sorts the elements in the queue using heap sort.
+ */
public void heapSort() {
- for (int i = 1; i < this.capacity; i++) {
+ for (int i = 1; i <= this.size; i++) {
this.delete();
}
}
- // this function reorders the heap after every delete function
+ /**
+ * Reorders the heap after a deletion to maintain the heap property.
+ */
private void sink() {
int k = 1;
- while (2 * k <= this.size || 2 * k + 1 <= this.size) {
- int minIndex;
- if (this.heap[2 * k] >= this.heap[k]) {
- if (2 * k + 1 <= this.size && this.heap[2 * k + 1] >= this.heap[k]) {
- break;
- } else if (2 * k + 1 > this.size) {
- break;
- }
+ while (2 * k <= this.size) {
+ int minIndex = k; // Assume current index is the minimum
+
+ if (2 * k <= this.size && this.heap[2 * k] < this.heap[minIndex]) {
+ minIndex = 2 * k; // Left child is smaller
}
- if (2 * k + 1 > this.size) {
- minIndex = this.heap[2 * k] < this.heap[k] ? 2 * k : k;
- } else {
- if (this.heap[k] > this.heap[2 * k] || this.heap[k] > this.heap[2 * k + 1]) {
- minIndex = this.heap[2 * k] < this.heap[2 * k + 1] ? 2 * k : 2 * k + 1;
- } else {
- minIndex = k;
- }
+ if (2 * k + 1 <= this.size && this.heap[2 * k + 1] < this.heap[minIndex]) {
+ minIndex = 2 * k + 1; // Right child is smaller
}
+
+ if (minIndex == k) {
+ break; // No swap needed, heap property is satisfied
+ }
+
+ // Swap with the smallest child
int temp = this.heap[k];
this.heap[k] = this.heap[minIndex];
this.heap[minIndex] = temp;
- k = minIndex;
+
+ k = minIndex; // Move down to the smallest child
}
}
- // deletes the highest priority value from the heap
+ /**
+ * Deletes and returns the highest priority value (the minimum) from the queue.
+ *
+ * @return the minimum value from the queue
+ * @throws IllegalStateException if the queue is empty
+ */
public int delete() {
+ if (isEmpty()) {
+ throw new IllegalStateException("MinPriorityQueue is empty. Cannot delete.");
+ }
int min = this.heap[1];
- this.heap[1] = this.heap[this.size];
- this.heap[this.size] = min;
+ this.heap[1] = this.heap[this.size]; // Move last element to the root
this.size--;
this.sink();
return min;
}
-
- public static void main(String[] args) {
- // testing
- MinPriorityQueue q = new MinPriorityQueue(8);
- q.insert(5);
- q.insert(2);
- q.insert(4);
- q.insert(1);
- q.insert(7);
- q.insert(6);
- q.insert(3);
- q.insert(8);
- q.print(); // [ 1, 2, 3, 5, 7, 6, 4, 8 ]
- q.heapSort();
- q.print(); // [ 8, 7, 6, 5, 4, 3, 2, 1 ]
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java
index 2b50f73101fb..422e8953625f 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java
@@ -1,8 +1,23 @@
package com.thealgorithms.datastructures.lists;
+/**
+ * This class is a circular singly linked list implementation. In a circular linked list,
+ * the last node points back to the first node, creating a circular chain.
+ *
+ * This implementation includes basic operations such as appending elements
+ * to the end, removing elements from a specified position, and converting
+ * the list to a string representation.
+ *
+ * @param the type of elements held in this list
+ */
public class CircleLinkedList {
- private static final class Node {
+ /**
+ * A static nested class representing a node in the circular linked list.
+ *
+ * @param the type of element stored in the node
+ */
+ static final class Node {
Node next;
E value;
@@ -13,44 +28,56 @@ private Node(E value, Node next) {
}
}
- // For better O.O design this should be private allows for better black box design
private int size;
- // this will point to dummy node;
- private Node head = null;
- private Node tail = null; // keeping a tail pointer to keep track of the end of list
+ Node head = null;
+ private Node tail;
- // constructor for class.. here we will make a dummy node for circly linked list implementation
- // with reduced error catching as our list will never be empty;
+ /**
+ * Initializes a new circular linked list. A dummy head node is used for simplicity,
+ * pointing initially to itself to ensure the list is never empty.
+ */
public CircleLinkedList() {
- // creation of the dummy node
- head = new Node(null, head);
+ head = new Node<>(null, head);
tail = head;
size = 0;
}
- // getter for the size... needed because size is private.
+ /**
+ * Returns the current size of the list.
+ *
+ * @return the number of elements in the list
+ */
public int getSize() {
return size;
}
- // for the sake of simplistiy this class will only contain the append function or addLast other
- // add functions can be implemented however this is the basses of them all really.
+ /**
+ * Appends a new element to the end of the list. Throws a NullPointerException if
+ * a null value is provided.
+ *
+ * @param value the value to append to the list
+ * @throws NullPointerException if the value is null
+ */
public void append(E value) {
if (value == null) {
- // we do not want to add null elements to the list.
throw new NullPointerException("Cannot add null element to the list");
}
- // head.next points to the last element;
if (tail == null) {
- tail = new Node(value, head);
+ tail = new Node<>(value, head);
head.next = tail;
} else {
- tail.next = new Node(value, head);
+ tail.next = new Node<>(value, head);
tail = tail.next;
}
size++;
}
+ /**
+ * Returns a string representation of the list in the format "[ element1, element2, ... ]".
+ * An empty list is represented as "[]".
+ *
+ * @return the string representation of the list
+ */
public String toString() {
if (size == 0) {
return "[]";
@@ -68,23 +95,27 @@ public String toString() {
return sb.toString();
}
+ /**
+ * Removes and returns the element at the specified position in the list.
+ * Throws an IndexOutOfBoundsException if the position is invalid.
+ *
+ * @param pos the position of the element to remove
+ * @return the value of the removed element
+ * @throws IndexOutOfBoundsException if the position is out of range
+ */
public E remove(int pos) {
if (pos >= size || pos < 0) {
- // catching errors
- throw new IndexOutOfBoundsException("position cannot be greater than size or negative");
+ throw new IndexOutOfBoundsException("Position out of bounds");
}
- // we need to keep track of the element before the element we want to remove we can see why
- // bellow.
+
Node before = head;
for (int i = 1; i <= pos; i++) {
before = before.next;
}
Node destroy = before.next;
E saved = destroy.value;
- // assigning the next reference to the element following the element we want to remove...
- // the last element will be assigned to the head.
- before.next = before.next.next;
- // scrubbing
+ before.next = destroy.next;
+
if (destroy == tail) {
tail = before;
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java
index 8d864bc8caae..b58d51e7e5fe 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java
@@ -1,27 +1,26 @@
package com.thealgorithms.datastructures.lists;
+/**
+ * CountSinglyLinkedListRecursion extends a singly linked list to include a
+ * recursive count method, which calculates the number of nodes in the list.
+ */
public class CountSinglyLinkedListRecursion extends SinglyLinkedList {
- public static void main(String[] args) {
- CountSinglyLinkedListRecursion list = new CountSinglyLinkedListRecursion();
- for (int i = 1; i <= 5; ++i) {
- list.insert(i);
- }
- assert list.count() == 5;
- }
-
/**
- * Calculate the count of the list manually using recursion.
+ * Recursively calculates the number of nodes in the list.
*
- * @param head head of the list.
- * @return count of the list.
+ * @param head the head node of the list segment being counted.
+ * @return the count of nodes from the given head node onward.
*/
- private int countRecursion(Node head) {
+ private int countRecursion(SinglyLinkedListNode head) {
return head == null ? 0 : 1 + countRecursion(head.next);
}
/**
- * Returns the count of the list.
+ * Returns the total number of nodes in the list by invoking the recursive
+ * count helper method.
+ *
+ * @return the total node count in the list.
*/
@Override
public int count() {
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java
index 441c95702050..3902d08bfd14 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java
@@ -1,64 +1,80 @@
package com.thealgorithms.datastructures.lists;
-import java.util.Scanner;
-
+/**
+ * CreateAndDetectLoop provides utility methods for creating and detecting loops
+ * (cycles) in a singly linked list. Loops in a linked list are created by
+ * connecting the "next" pointer of one node to a previous node in the list,
+ * forming a cycle.
+ */
public final class CreateAndDetectLoop {
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ */
private CreateAndDetectLoop() {
+ throw new UnsupportedOperationException("Utility class");
}
/**
- * Prints the linked list.
- *
- * @param head head node of the linked list
+ * Node represents an individual element in the linked list, containing
+ * data and a reference to the next node.
*/
- static void printList(Node head) {
- Node cur = head;
+ static final class Node {
+ int data;
+ Node next;
- while (cur != null) {
- System.out.print(cur.value + " ");
- cur = cur.next;
+ Node(int data) {
+ this.data = data;
+ next = null;
}
}
/**
- * Creates a loop in the linked list.
+ * Creates a loop in a linked list by connecting the next pointer of a node
+ * at a specified starting position (position2) to another node at a specified
+ * destination position (position1). If either position is invalid, no loop
+ * will be created.
*
- * @see
- *
- * GeeksForGeeks: Make a loop at K-th position
- * @param head head node of the linked list
- * @param k position of node where loop is to be created
+ * @param head the head node of the linked list
+ * @param position1 the position in the list where the loop should end
+ * @param position2 the position in the list where the loop should start
*/
- static void createLoop(Node head, int k) {
- if (head == null) {
+ static void createLoop(Node head, int position1, int position2) {
+ if (position1 == 0 || position2 == 0) {
return;
}
- Node temp = head;
- int count = 1;
- while (count < k) { // Traverse the list till the kth node
- temp = temp.next;
- count++;
- }
- Node connectedPoint = temp;
+ Node node1 = head;
+ Node node2 = head;
+
+ int count1 = 1;
+ int count2 = 1;
+ // Traverse to the node at position1
+ while (count1 < position1 && node1 != null) {
+ node1 = node1.next;
+ count1++;
+ }
- while (temp.next != null) { // Traverse remaining nodes
- temp = temp.next;
+ // Traverse to the node at position2
+ while (count2 < position2 && node2 != null) {
+ node2 = node2.next;
+ count2++;
}
- temp.next = connectedPoint; // Connect last node to k-th element
+ // If both nodes are valid, create the loop
+ if (node1 != null && node2 != null) {
+ node2.next = node1;
+ }
}
/**
- * Detects the presence of a loop in the linked list.
- *
- * @see
- *
- * Floyd's Cycle Detection Algorithm
+ * Detects the presence of a loop in the linked list using Floyd's cycle-finding
+ * algorithm, also known as the "tortoise and hare" method.
*
* @param head the head node of the linked list
- *
- * @return true if loop exists else false
+ * @return true if a loop is detected, false otherwise
+ * @see
+ * Floyd's Cycle Detection Algorithm
*/
static boolean detectLoop(Node head) {
Node sptr = head;
@@ -67,40 +83,10 @@ static boolean detectLoop(Node head) {
while (fptr != null && fptr.next != null) {
sptr = sptr.next;
fptr = fptr.next.next;
- if (fptr == sptr) {
+ if (sptr == fptr) {
return true;
}
}
-
return false;
}
-
- public static void main(String[] args) {
- SinglyLinkedList singlyLinkedList = new SinglyLinkedList();
- Scanner sc = new Scanner(System.in);
-
- System.out.println("Enter the number of elements to be inserted: ");
- int n = sc.nextInt();
- System.out.printf("Enter the %d elements: %n", n);
- while (n-- > 0) {
- singlyLinkedList.insert(sc.nextInt());
- }
-
- System.out.print("Given list: ");
- printList(singlyLinkedList.getHead());
- System.out.println();
-
- System.out.println("Enter the location to generate loop: ");
- int k = sc.nextInt();
-
- createLoop(singlyLinkedList.getHead(), k);
-
- if (detectLoop(singlyLinkedList.getHead())) {
- System.out.println("Loop found");
- } else {
- System.out.println("No loop found");
- }
-
- sc.close();
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java
index b4fa9c51d4dc..ff3d39115c3b 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java
@@ -3,18 +3,20 @@
import java.util.Objects;
/**
- * This class implements a Cursor Linked List.
- *
- * A CursorLinkedList is an array version of a Linked List. Essentially you have
- * an array of list nodes but instead of each node containing a pointer to the
- * next item in the linked list, each node element in the array contains the
- * index for the next node element.
+ * CursorLinkedList is an array-based implementation of a singly linked list.
+ * Each node in the array simulates a linked list node, storing an element and
+ * the index of the next node. This structure allows for efficient list operations
+ * without relying on traditional pointers.
*
+ * @param the type of elements in this list
*/
public class CursorLinkedList {
+ /**
+ * Node represents an individual element in the list, containing the element
+ * itself and a pointer (index) to the next node.
+ */
private static class Node {
-
T element;
int next;
@@ -31,7 +33,7 @@ private static class Node {
private static final int CURSOR_SPACE_SIZE = 100;
{
- // init at loading time
+ // Initialize cursor space array and free list pointers
cursorSpace = new Node[CURSOR_SPACE_SIZE];
for (int i = 0; i < CURSOR_SPACE_SIZE; i++) {
cursorSpace[i] = new Node<>(null, i + 1);
@@ -39,12 +41,18 @@ private static class Node {
cursorSpace[CURSOR_SPACE_SIZE - 1].next = 0;
}
+ /**
+ * Constructs an empty CursorLinkedList with the default capacity.
+ */
public CursorLinkedList() {
os = 0;
count = 0;
head = -1;
}
+ /**
+ * Prints all elements in the list in their current order.
+ */
public void printList() {
if (head != -1) {
int start = head;
@@ -57,27 +65,36 @@ public void printList() {
}
/**
- * @return the logical index of the element within the list , not the actual
- * index of the [cursorSpace] array
+ * Finds the logical index of a specified element in the list.
+ *
+ * @param element the element to search for in the list
+ * @return the logical index of the element, or -1 if not found
+ * @throws NullPointerException if element is null
*/
public int indexOf(T element) {
- Objects.requireNonNull(element);
- Node iterator = cursorSpace[head];
- for (int i = 0; i < count; i++) {
- if (iterator.element.equals(element)) {
- return i;
+ if (element == null) {
+ throw new NullPointerException("Element cannot be null");
+ }
+ try {
+ Objects.requireNonNull(element);
+ Node iterator = cursorSpace[head];
+ for (int i = 0; i < count; i++) {
+ if (iterator.element.equals(element)) {
+ return i;
+ }
+ iterator = cursorSpace[iterator.next];
}
- iterator = cursorSpace[iterator.next];
+ } catch (Exception e) {
+ return -1;
}
-
return -1;
}
/**
- * @param position , the logical index of the element , not the actual one
- * within the [cursorSpace] array . this method should be used to get the
- * index give by indexOf() method.
- * @return
+ * Retrieves an element at a specified logical index in the list.
+ *
+ * @param position the logical index of the element
+ * @return the element at the specified position, or null if index is out of bounds
*/
public T get(int position) {
if (position >= 0 && position < count) {
@@ -88,15 +105,18 @@ public T get(int position) {
if (counter == position) {
return element;
}
-
start = cursorSpace[start].next;
counter++;
}
}
-
return null;
}
+ /**
+ * Removes the element at a specified logical index from the list.
+ *
+ * @param index the logical index of the element to remove
+ */
public void removeByIndex(int index) {
if (index >= 0 && index < count) {
T element = get(index);
@@ -104,19 +124,22 @@ public void removeByIndex(int index) {
}
}
+ /**
+ * Removes a specified element from the list.
+ *
+ * @param element the element to be removed
+ * @throws NullPointerException if element is null
+ */
public void remove(T element) {
Objects.requireNonNull(element);
-
- // case element is in the head
T tempElement = cursorSpace[head].element;
int tempNext = cursorSpace[head].next;
if (tempElement.equals(element)) {
free(head);
head = tempNext;
- } else { // otherwise cases
+ } else {
int prevIndex = head;
int currentIndex = cursorSpace[prevIndex].next;
-
while (currentIndex != -1) {
T currentElement = cursorSpace[currentIndex].element;
if (currentElement.equals(element)) {
@@ -124,15 +147,34 @@ public void remove(T element) {
free(currentIndex);
break;
}
-
prevIndex = currentIndex;
currentIndex = cursorSpace[prevIndex].next;
}
}
-
count--;
}
+ /**
+ * Allocates a new node index for storing an element.
+ *
+ * @return the index of the newly allocated node
+ * @throws OutOfMemoryError if no space is available in cursor space
+ */
+ private int alloc() {
+ int availableNodeIndex = cursorSpace[os].next;
+ if (availableNodeIndex == 0) {
+ throw new OutOfMemoryError();
+ }
+ cursorSpace[os].next = cursorSpace[availableNodeIndex].next;
+ cursorSpace[availableNodeIndex].next = -1;
+ return availableNodeIndex;
+ }
+
+ /**
+ * Releases a node back to the free list.
+ *
+ * @param index the index of the node to release
+ */
private void free(int index) {
Node osNode = cursorSpace[os];
int osNext = osNode.next;
@@ -141,44 +183,26 @@ private void free(int index) {
cursorSpace[index].next = osNext;
}
+ /**
+ * Appends an element to the end of the list.
+ *
+ * @param element the element to append
+ * @throws NullPointerException if element is null
+ */
public void append(T element) {
Objects.requireNonNull(element);
int availableIndex = alloc();
cursorSpace[availableIndex].element = element;
-
if (head == -1) {
head = availableIndex;
+ } else {
+ int iterator = head;
+ while (cursorSpace[iterator].next != -1) {
+ iterator = cursorSpace[iterator].next;
+ }
+ cursorSpace[iterator].next = availableIndex;
}
-
- int iterator = head;
- while (cursorSpace[iterator].next != -1) {
- iterator = cursorSpace[iterator].next;
- }
-
- cursorSpace[iterator].next = availableIndex;
cursorSpace[availableIndex].next = -1;
-
count++;
}
-
- /**
- * @return the index of the next available node
- */
- private int alloc() {
- // 1- get the index at which the os is pointing
- int availableNodeIndex = cursorSpace[os].next;
-
- if (availableNodeIndex == 0) {
- throw new OutOfMemoryError();
- }
-
- // 2- make the os point to the next of the @var{availableNodeIndex}
- cursorSpace[os].next = cursorSpace[availableNodeIndex].next;
-
- // this to indicate an end of the list , helpful at testing since any err
- // would throw an outOfBoundException
- cursorSpace[availableNodeIndex].next = -1;
-
- return availableNodeIndex;
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java
index 0eac20d2e9ad..0f5c50530d92 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java
@@ -1,51 +1,94 @@
package com.thealgorithms.datastructures.lists;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
+ * The MergeKSortedLinkedList class provides a method to merge multiple sorted linked lists
+ * into a single sorted linked list.
+ * This implementation uses a min-heap (priority queue) to efficiently
+ * find the smallest node across all lists, thus optimizing the merge process.
+ *
+ * Example usage:
+ *
+ * Node list1 = new Node(1, new Node(4, new Node(5)));
+ * Node list2 = new Node(1, new Node(3, new Node(4)));
+ * Node list3 = new Node(2, new Node(6));
+ * Node[] lists = { list1, list2, list3 };
+ *
+ * MergeKSortedLinkedList merger = new MergeKSortedLinkedList();
+ * Node mergedHead = merger.mergeKList(lists, lists.length);
+ *
+ *
+ *
+ * This class is designed to handle nodes of integer linked lists and can be expanded for additional data types if needed.
+ *
* @author Arun Pandey (https://github.com/pandeyarun709)
*/
public class MergeKSortedLinkedList {
/**
- * This function merge K sorted LinkedList
+ * Merges K sorted linked lists into a single sorted linked list.
*
- * @param a array of LinkedList
- * @param n size of array
- * @return node
+ * This method uses a priority queue (min-heap) to repeatedly extract the smallest node from the heads of all the lists,
+ * then inserts the next node from that list into the heap. The process continues until all nodes have been processed,
+ * resulting in a fully merged and sorted linked list.
+ *
+ * @param a Array of linked list heads to be merged.
+ * @param n Number of linked lists.
+ * @return Head of the merged sorted linked list.
*/
Node mergeKList(Node[] a, int n) {
- // Min Heap
- PriorityQueue min = new PriorityQueue<>(Comparator.comparingInt(x -> x.data));
+ if (a == null || n == 0) {
+ return null;
+ }
- // adding head of all linkedList in min heap
- min.addAll(Arrays.asList(a).subList(0, n));
+ // Min Heap to store nodes based on their values for efficient retrieval of the smallest element.
+ PriorityQueue minHeap = new PriorityQueue<>(Comparator.comparingInt(x -> x.data));
- // Make new head among smallest heads in K linkedList
- Node head = min.poll();
- min.add(head.next);
- Node curr = head;
+ // Initialize the min-heap with the head of each non-null linked list
+ for (Node node : a) {
+ if (node != null) {
+ minHeap.add(node);
+ }
+ }
+
+ // Start merging process
+ Node head = minHeap.poll(); // Smallest head is the initial head of the merged list
+ if (head != null && head.next != null) {
+ minHeap.add(head.next);
+ }
- // merging LinkedList
- while (!min.isEmpty()) {
- Node temp = min.poll();
+ Node curr = head;
+ while (!minHeap.isEmpty()) {
+ Node temp = minHeap.poll();
curr.next = temp;
curr = temp;
- // Add Node in min Heap only if temp.next is not null
+ // Add the next node in the current list to the heap if it exists
if (temp.next != null) {
- min.add(temp.next);
+ minHeap.add(temp.next);
}
}
return head;
}
- private final class Node {
+ /**
+ * Represents a node in the linked list.
+ */
+ static class Node {
+ int data;
+ Node next;
+
+ Node(int data) {
+ this.data = data;
+ this.next = null;
+ }
- private int data;
- private Node next;
+ Node(int data, Node next) {
+ this.data = data;
+ this.next = next;
+ }
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java
index 99ab09f81c1c..09eb854c8dc2 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java
@@ -1,48 +1,55 @@
package com.thealgorithms.datastructures.lists;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
+ * Utility class for merging two sorted ArrayLists of integers into a single sorted collection.
+ *
+ * This class provides a static `merge` method to combine two pre-sorted lists of integers into a
+ * single sorted list. It does so without modifying the input lists by adding elements from both lists in sorted order
+ * into the result list.
+ *
+ * Example usage:
+ *
+ * List listA = Arrays.asList(1, 3, 5, 7, 9);
+ * List listB = Arrays.asList(2, 4, 6, 8, 10);
+ * List result = new ArrayList<>();
+ * MergeSortedArrayList.merge(listA, listB, result);
+ *
+ *
+ * The resulting `result` list will be [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
+ *
+ * Note: This class cannot be instantiated as it is designed to be used only with its static `merge` method.
+ *
+ * This implementation assumes the input lists are already sorted in ascending order.
+ *
* @author https://github.com/shellhub
+ * @see List
*/
public final class MergeSortedArrayList {
- private MergeSortedArrayList() {
- }
-
- public static void main(String[] args) {
- List listA = new ArrayList<>();
- List listB = new ArrayList<>();
- List listC = new ArrayList<>();
-
- /* init ListA and List B */
- for (int i = 1; i <= 10; i += 2) {
- listA.add(i);
- /* listA: [1, 3, 5, 7, 9] */
- listB.add(i + 1);
- /* listB: [2, 4, 6, 8, 10] */
- }
-
- /* merge listA and listB to listC */
- merge(listA, listB, listC);
- System.out.println("listA: " + listA);
- System.out.println("listB: " + listB);
- System.out.println("listC: " + listC);
+ private MergeSortedArrayList() {
}
/**
- * merge two sorted ArrayList
+ * Merges two sorted lists of integers into a single sorted collection.
+ *
+ * This method does not alter the original lists (`listA` and `listB`). Instead, it inserts elements from both
+ * lists into `listC` in a way that maintains ascending order.
*
- * @param listA the first list to merge
- * @param listB the second list to merge
- * @param listC the result list after merging
+ * @param listA The first sorted list of integers.
+ * @param listB The second sorted list of integers.
+ * @param listC The collection to hold the merged result, maintaining sorted order.
+ * @throws NullPointerException if any of the input lists or result collection is null.
*/
- public static void merge(List listA, List listB, List listC) {
+ public static void merge(List listA, List listB, Collection listC) {
+ if (listA == null || listB == null || listC == null) {
+ throw new NullPointerException("Input lists and result collection must not be null.");
+ }
+
int pa = 0;
- /* the index of listA */
int pb = 0;
- /* the index of listB */
while (pa < listA.size() && pb < listB.size()) {
if (listA.get(pa) <= listB.get(pb)) {
@@ -52,12 +59,11 @@ public static void merge(List listA, List listB, List
}
}
- /* copy left element of listA to listC */
+ // Add remaining elements from listA, if any
while (pa < listA.size()) {
listC.add(listA.get(pa++));
}
-
- /* copy left element of listB to listC */
+ // Add remaining elements from listB, if any
while (pb < listB.size()) {
listC.add(listB.get(pb++));
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java
index 2bee945c9db6..4e99642fccd8 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java
@@ -1,35 +1,53 @@
package com.thealgorithms.datastructures.lists;
+/**
+ * Utility class for merging two sorted singly linked lists.
+ *
+ * This class extends the {@link SinglyLinkedList} class to support the merging of two sorted linked lists.
+ * It provides a static method, `merge`, that takes two sorted singly linked lists, merges them into a single sorted linked list,
+ * and returns the result.
+ *
+ * Example usage:
+ *
+ * SinglyLinkedList listA = new SinglyLinkedList();
+ * SinglyLinkedList listB = new SinglyLinkedList();
+ * for (int i = 2; i <= 10; i += 2) {
+ * listA.insert(i); // listA: 2->4->6->8->10
+ * listB.insert(i - 1); // listB: 1->3->5->7->9
+ * }
+ * SinglyLinkedList mergedList = MergeSortedSinglyLinkedList.merge(listA, listB);
+ * System.out.println(mergedList); // Output: 1->2->3->4->5->6->7->8->9->10
+ *
+ *
+ * The `merge` method assumes that both input lists are already sorted in ascending order.
+ * It returns a new singly linked list that contains all elements from both lists in sorted order.
+ *
+ * @see SinglyLinkedList
+ */
public class MergeSortedSinglyLinkedList extends SinglyLinkedList {
- public static void main(String[] args) {
- SinglyLinkedList listA = new SinglyLinkedList();
- SinglyLinkedList listB = new SinglyLinkedList();
-
- for (int i = 2; i <= 10; i += 2) {
- listA.insert(i);
- listB.insert(i - 1);
- }
- assert listA.toString().equals("2->4->6->8->10");
- assert listB.toString().equals("1->3->5->7->9");
- assert merge(listA, listB).toString().equals("1->2->3->4->5->6->7->8->9->10");
- }
-
/**
- * Merge two sorted SingleLinkedList
+ * Merges two sorted singly linked lists into a single sorted singly linked list.
+ *
+ * This method does not modify the input lists; instead, it creates a new merged linked list
+ * containing all elements from both lists in sorted order.
*
- * @param listA the first sorted list
- * @param listB the second sored list
- * @return merged sorted list
+ * @param listA The first sorted singly linked list.
+ * @param listB The second sorted singly linked list.
+ * @return A new singly linked list containing all elements from both lists in sorted order.
+ * @throws NullPointerException if either input list is null.
*/
public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList listB) {
- Node headA = listA.getHead();
- Node headB = listB.getHead();
+ if (listA == null || listB == null) {
+ throw new NullPointerException("Input lists must not be null.");
+ }
+ SinglyLinkedListNode headA = listA.getHead();
+ SinglyLinkedListNode headB = listB.getHead();
int size = listA.size() + listB.size();
- Node head = new Node();
- Node tail = head;
+ SinglyLinkedListNode head = new SinglyLinkedListNode();
+ SinglyLinkedListNode tail = head;
while (headA != null && headB != null) {
if (headA.value <= headB.value) {
tail.next = headA;
@@ -40,12 +58,10 @@ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList li
}
tail = tail.next;
}
- if (headA == null) {
- tail.next = headB;
- }
- if (headB == null) {
- tail.next = headA;
- }
+
+ // Attach remaining nodes
+ tail.next = (headA == null) ? headB : headA;
+
return new SinglyLinkedList(head.next, size);
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
index 36b6e9c62cbe..f018781ada70 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java
@@ -104,36 +104,52 @@
public class QuickSortLinkedList {
- private SinglyLinkedList list = null; // Linked list
- private Node head = null; // head of the list
- // Counstructor
+ private final SinglyLinkedList list; // The linked list to be sorted
+ private SinglyLinkedListNode head; // Head of the list
+
+ /**
+ * Constructor that initializes the QuickSortLinkedList with a given linked list.
+ *
+ * @param list The singly linked list to be sorted
+ */
public QuickSortLinkedList(SinglyLinkedList list) {
this.list = list;
this.head = list.getHead();
}
- // Function to sort a linked list using the Quick Sort algorithm
+ /**
+ * Sorts the linked list using the QuickSort algorithm.
+ * The sorted list replaces the original list within the SinglyLinkedList instance.
+ */
public void sortList() {
head = sortList(head);
list.setHead(head);
}
- // helper function to apply QuickSort to the stored list
- public Node sortList(Node head) {
+
+ /**
+ * Recursively sorts a linked list by partitioning it around a pivot element.
+ *
+ * Each recursive call selects a pivot, partitions the list into elements less
+ * than the pivot and elements greater than or equal to the pivot, then combines
+ * the sorted sublists around the pivot.
+ *
+ * @param head The head node of the list to sort
+ * @return The head node of the sorted linked list
+ */
+ private SinglyLinkedListNode sortList(SinglyLinkedListNode head) {
if (head == null || head.next == null) {
return head;
}
- // Choose the first element as the pivot
- Node pivot = head;
+ SinglyLinkedListNode pivot = head;
head = head.next;
pivot.next = null;
- Node lessHead = new Node(); // stores the nodes cantaining data less than pivot node
- Node lessTail = lessHead; // tail of lessHead
- Node greaterHead = new Node(); // stores the nodes cantaining data greater than pivot node
- Node greaterTail = greaterHead; // tail of greaterHead
+ SinglyLinkedListNode lessHead = new SinglyLinkedListNode();
+ SinglyLinkedListNode lessTail = lessHead;
+ SinglyLinkedListNode greaterHead = new SinglyLinkedListNode();
+ SinglyLinkedListNode greaterTail = greaterHead;
- // Partition the list around the pivot
while (head != null) {
if (head.value < pivot.value) {
lessTail.next = head;
@@ -145,20 +161,17 @@ public Node sortList(Node head) {
head = head.next;
}
- // Seperating lessHead and greaterHead to form two seperate linkedList
lessTail.next = null;
greaterTail.next = null;
- // Recursively sort the sublists
- Node sortedLess = sortList(lessHead.next);
- Node sortedGreater = sortList(greaterHead.next);
+ SinglyLinkedListNode sortedLess = sortList(lessHead.next);
+ SinglyLinkedListNode sortedGreater = sortList(greaterHead.next);
- // Combine the sorted sublists and pivot
if (sortedLess == null) {
pivot.next = sortedGreater;
return pivot;
} else {
- Node current = sortedLess;
+ SinglyLinkedListNode current = sortedLess;
while (current.next != null) {
current = current.next;
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/README.md b/src/main/java/com/thealgorithms/datastructures/lists/README.md
index ea389c0422ce..6aefa4c98e6d 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/README.md
+++ b/src/main/java/com/thealgorithms/datastructures/lists/README.md
@@ -1,7 +1,7 @@
## Linked List
### Description
-LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next mode.
+LinkedList is a data structure in which data is stored in a linear manner. It usually contains a data field and a link to the memory location of the next node.
### Structure
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java
index 3c4b9331266c..9b9464d388b5 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java
@@ -1,13 +1,45 @@
package com.thealgorithms.datastructures.lists;
/**
- * Reverse K Group LinkedList (https://www.topcoder.com/thrive/articles/reverse-node-in-k-group)
+ * The ReverseKGroup class provides functionality to reverse nodes in a
+ * linked list in groups of k nodes.
+ *
+ * This algorithm follows the approach of reversing the linked list in segments of
+ * size k. If the remaining nodes are fewer than k, they remain unchanged.
+ *
+ *
+ * Example:
+ * Given a linked list: 1 -> 2 -> 3 -> 4 -> 5 and k = 3,
+ * the output will be: 3 -> 2 -> 1 -> 4 -> 5.
+ *
+ *
+ * The implementation contains:
+ * - {@code length(SinglyLinkedListNode head)}: A method to calculate the length of the linked list.
+ * - {@code reverse(SinglyLinkedListNode head, int count, int k)}: A helper method that reverses the nodes
+ * in the linked list in groups of k.
+ * - {@code reverseKGroup(SinglyLinkedListNode head, int k)}: The main method that initiates the reversal
+ * process by calling the reverse method.
+ *
+ *
+ * Complexity:
+ *
+ * - Time Complexity: O(n), where n is the number of nodes in the linked list.
+ * - Space Complexity: O(1), as we are reversing in place.
+ *
+ *
+ *
* Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public class ReverseKGroup {
- public int length(Node head) {
- Node curr = head;
+
+ /**
+ * Calculates the length of the linked list.
+ *
+ * @param head The head node of the linked list.
+ * @return The total number of nodes in the linked list.
+ */
+ public int length(SinglyLinkedListNode head) {
+ SinglyLinkedListNode curr = head;
int count = 0;
while (curr != null) {
curr = curr.next;
@@ -15,15 +47,23 @@ public int length(Node head) {
}
return count;
}
- // reverse function
- public Node reverse(Node head, int count, int k) {
+
+ /**
+ * Reverses the linked list in groups of k nodes.
+ *
+ * @param head The head node of the linked list.
+ * @param count The remaining number of nodes.
+ * @param k The size of the group to reverse.
+ * @return The new head of the reversed linked list segment.
+ */
+ public SinglyLinkedListNode reverse(SinglyLinkedListNode head, int count, int k) {
if (count < k) {
return head;
}
- Node prev = null;
+ SinglyLinkedListNode prev = null;
int count1 = 0;
- Node curr = head;
- Node next = null;
+ SinglyLinkedListNode curr = head;
+ SinglyLinkedListNode next = null;
while (curr != null && count1 < k) {
next = curr.next;
curr.next = prev;
@@ -37,9 +77,16 @@ public Node reverse(Node head, int count, int k) {
}
return prev;
}
- public Node reverseKGroup(Node head, int k) {
+
+ /**
+ * Reverses the linked list in groups of k nodes.
+ *
+ * @param head The head node of the linked list.
+ * @param k The size of the group to reverse.
+ * @return The head of the modified linked list after reversal.
+ */
+ public SinglyLinkedListNode reverseKGroup(SinglyLinkedListNode head, int k) {
int count = length(head);
- Node ans = reverse(head, count, k);
- return ans;
+ return reverse(head, count, k);
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java
index e7ea95d3f037..47ee5397097c 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java
@@ -1,17 +1,49 @@
package com.thealgorithms.datastructures.lists;
/**
- * Rotate a list
- * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
+ * The RotateSinglyLinkedLists class provides a method to rotate a singly linked list
+ * to the right by a specified number of positions.
+ *
+ * In a right rotation by `k` steps, each node in the list moves `k` positions to the right.
+ * Nodes that are rotated off the end of the list are placed back at the beginning.
+ *
+ *
+ * Example:
+ * Given linked list: 1 -> 2 -> 3 -> 4 -> 5 and k = 2, the output will be:
+ * 4 -> 5 -> 1 -> 2 -> 3.
+ *
+ *
+ * Edge Cases:
+ *
+ * - If the list is empty, returns null.
+ * - If `k` is 0 or a multiple of the list length, the list remains unchanged.
+ *
+ *
+ *
+ * Complexity:
+ *
+ * - Time Complexity: O(n), where n is the number of nodes in the linked list.
+ * - Space Complexity: O(1), as we only use a constant amount of additional space.
+ *
+ *
+ *
+ * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public class RotateSinglyLinkedLists {
- public Node rotateRight(Node head, int k) {
+
+ /**
+ * Rotates a singly linked list to the right by `k` positions.
+ *
+ * @param head The head node of the singly linked list.
+ * @param k The number of positions to rotate the list to the right.
+ * @return The head of the rotated linked list.
+ */
+ public SinglyLinkedListNode rotateRight(SinglyLinkedListNode head, int k) {
if (head == null || head.next == null || k == 0) {
return head;
}
- Node curr = head;
+ SinglyLinkedListNode curr = head;
int len = 1;
while (curr.next != null) {
curr = curr.next;
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java
index 35f8c9a95b56..4ac2de422595 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java
@@ -1,31 +1,45 @@
package com.thealgorithms.datastructures.lists;
+/**
+ * The SearchSinglyLinkedListRecursion class extends SinglyLinkedList and provides
+ * a method to search for a value in a singly linked list using recursion.
+ *
+ * This class demonstrates a recursive approach to check if a given integer value is
+ * present in the linked list. The search method calls a private recursive helper method
+ * `searchRecursion`, which checks each node's value and moves to the next node if necessary.
+ *
+ *
+ * Example:
+ * Given a list containing the values 1 -> 2 -> 3 -> 4, calling search(3) will return `true`,
+ * while calling search(5) will return `false`.
+ *
+ *
+ * Complexity:
+ *
+ * - Time Complexity: O(n), where n is the number of nodes in the linked list.
+ * - Space Complexity: O(n), due to the recursive call stack in the worst case.
+ *
+ *
+ */
public class SearchSinglyLinkedListRecursion extends SinglyLinkedList {
- public static void main(String[] args) {
- SearchSinglyLinkedListRecursion list = new SearchSinglyLinkedListRecursion();
- for (int i = 1; i <= 10; ++i) {
- list.insert(i);
- }
-
- for (int i = 1; i <= 10; ++i) {
- assert list.search(i);
- }
- assert !list.search(-1) && !list.search(100);
- }
-
/**
- * Test if the value key is present in the list using recursion.
+ * Recursively searches for a given value in the linked list.
*
- * @param node the head node.
- * @param key the value to be searched.
- * @return {@code true} if key is present in the list, otherwise
- * {@code false}.
+ * @param node the head node to start the search.
+ * @param key the integer value to be searched for.
+ * @return {@code true} if the value `key` is present in the list; otherwise, {@code false}.
*/
- private boolean searchRecursion(Node node, int key) {
+ private boolean searchRecursion(SinglyLinkedListNode node, int key) {
return (node != null && (node.value == key || searchRecursion(node.next, key)));
}
+ /**
+ * Public search method to determine if a key is present in the linked list.
+ *
+ * @param key the integer value to be searched for.
+ * @return {@code true} if the value `key` is present in the list; otherwise, {@code false}.
+ */
@Override
public boolean search(int key) {
return searchRecursion(getHead(), key);
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java
index d5d3f31f4b66..ff4af4437cc7 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java
@@ -12,7 +12,7 @@ public class SinglyLinkedList implements Iterable {
/**
* Head refer to the front of the list
*/
- private Node head;
+ private SinglyLinkedListNode head;
/**
* Size of SinglyLinkedList
@@ -33,7 +33,7 @@ public SinglyLinkedList() {
* @param head the head node of list
* @param size the size of list
*/
- public SinglyLinkedList(Node head, int size) {
+ public SinglyLinkedList(SinglyLinkedListNode head, int size) {
this.head = head;
this.size = size;
}
@@ -44,8 +44,8 @@ public SinglyLinkedList(Node head, int size) {
*
*/
public boolean detectLoop() {
- Node currentNodeFast = head;
- Node currentNodeSlow = head;
+ SinglyLinkedListNode currentNodeFast = head;
+ SinglyLinkedListNode currentNodeSlow = head;
while (currentNodeFast != null && currentNodeFast.next != null) {
currentNodeFast = currentNodeFast.next.next;
currentNodeSlow = currentNodeSlow.next;
@@ -61,12 +61,12 @@ public boolean detectLoop() {
* If the length of the list is even then return item number length/2
* @return middle node of the list
*/
- public Node middle() {
+ public SinglyLinkedListNode middle() {
if (head == null) {
return null;
}
- Node firstCounter = head;
- Node secondCounter = firstCounter.next;
+ SinglyLinkedListNode firstCounter = head;
+ SinglyLinkedListNode secondCounter = firstCounter.next;
while (secondCounter != null && secondCounter.next != null) {
firstCounter = firstCounter.next;
secondCounter = secondCounter.next.next;
@@ -82,15 +82,15 @@ public void swapNodes(int valueFirst, int valueSecond) {
if (valueFirst == valueSecond) {
return;
}
- Node previousA = null;
- Node currentA = head;
+ SinglyLinkedListNode previousA = null;
+ SinglyLinkedListNode currentA = head;
while (currentA != null && currentA.value != valueFirst) {
previousA = currentA;
currentA = currentA.next;
}
- Node previousB = null;
- Node currentB = head;
+ SinglyLinkedListNode previousB = null;
+ SinglyLinkedListNode currentB = head;
while (currentB != null && currentB.value != valueSecond) {
previousB = currentB;
currentB = currentB.next;
@@ -117,7 +117,7 @@ public void swapNodes(int valueFirst, int valueSecond) {
}
// Swap next pointer
- Node temp = currentA.next;
+ var temp = currentA.next;
currentA.next = currentB.next;
currentB.next = temp;
}
@@ -126,12 +126,12 @@ public void swapNodes(int valueFirst, int valueSecond) {
* Reverse a singly linked list[Iterative] from a given node till the end
*
*/
- public Node reverseListIter(Node node) {
- Node prev = null;
- Node curr = node;
+ public SinglyLinkedListNode reverseListIter(SinglyLinkedListNode node) {
+ SinglyLinkedListNode prev = null;
+ SinglyLinkedListNode curr = node;
while (curr != null && curr.next != null) {
- Node next = curr.next;
+ var next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
@@ -149,13 +149,13 @@ public Node reverseListIter(Node node) {
* Reverse a singly linked list[Recursive] from a given node till the end
*
*/
- public Node reverseListRec(Node head) {
+ public SinglyLinkedListNode reverseListRec(SinglyLinkedListNode head) {
if (head == null || head.next == null) {
return head;
}
- Node prev = null;
- Node h2 = reverseListRec(head.next);
+ SinglyLinkedListNode prev = null;
+ SinglyLinkedListNode h2 = reverseListRec(head.next);
head.next.next = head;
head.next = prev;
@@ -167,7 +167,7 @@ public Node reverseListRec(Node head) {
* Clear all nodes in the list
*/
public void clear() {
- Node cur = head;
+ SinglyLinkedListNode cur = head;
while (cur != null) {
cur = cur.next;
}
@@ -198,7 +198,7 @@ public int size() {
*
* @return head of the list.
*/
- public Node getHead() {
+ public SinglyLinkedListNode getHead() {
return head;
}
@@ -206,7 +206,7 @@ public Node getHead() {
* Set head of the list.
*
*/
- public void setHead(Node head) {
+ public void setHead(SinglyLinkedListNode head) {
this.head = head;
}
@@ -249,10 +249,10 @@ public String toString() {
}
public void deleteDuplicates() {
- Node pred = head;
+ SinglyLinkedListNode pred = head;
// predecessor = the node
// having sublist of its duplicates
- Node newHead = head;
+ SinglyLinkedListNode newHead = head;
while (newHead != null) {
// if it's a beginning of duplicates sublist
// skip all duplicates
@@ -273,7 +273,7 @@ public void deleteDuplicates() {
}
public void print() {
- Node temp = head;
+ SinglyLinkedListNode temp = head;
while (temp != null && temp.next != null) {
System.out.print(temp.value + "->");
temp = temp.next;
@@ -310,7 +310,7 @@ public void insert(int data) {
*/
public void insertNth(int data, int position) {
checkBounds(position, 0, size);
- Node newNode = new Node(data);
+ SinglyLinkedListNode newNode = new SinglyLinkedListNode(data);
if (head == null) {
/* the list is empty */
head = newNode;
@@ -325,7 +325,7 @@ public void insertNth(int data, int position) {
return;
}
- Node cur = head;
+ SinglyLinkedListNode cur = head;
for (int i = 0; i < position - 1; ++i) {
cur = cur.next;
}
@@ -359,7 +359,7 @@ public void deleteNth(int position) {
size--;
return;
}
- Node cur = head;
+ SinglyLinkedListNode cur = head;
for (int i = 0; i < position - 1; ++i) {
cur = cur.next;
}
@@ -376,7 +376,7 @@ public void deleteNth(int position) {
*/
public int getNth(int index) {
checkBounds(index, 0, size - 1);
- Node cur = head;
+ SinglyLinkedListNode cur = head;
for (int i = 0; i < index; ++i) {
cur = cur.next;
}
@@ -403,7 +403,7 @@ public static void main(String[] arg) {
SinglyLinkedList list = new SinglyLinkedList();
assert list.isEmpty();
assert list.size() == 0 && list.count() == 0;
- assert list.toString().equals("");
+ assert list.toString().isEmpty();
/* Test insert function */
list.insertHead(5);
@@ -440,7 +440,7 @@ public static void main(String[] arg) {
}
SinglyLinkedList instance = new SinglyLinkedList();
- Node head = new Node(0, new Node(2, new Node(3, new Node(3, new Node(4)))));
+ SinglyLinkedListNode head = new SinglyLinkedListNode(0, new SinglyLinkedListNode(2, new SinglyLinkedListNode(3, new SinglyLinkedListNode(3, new SinglyLinkedListNode(4)))));
instance.setHead(head);
instance.deleteDuplicates();
instance.print();
@@ -452,7 +452,7 @@ public Iterator iterator() {
}
private class SinglyLinkedListIterator implements Iterator {
- private Node current;
+ private SinglyLinkedListNode current;
SinglyLinkedListIterator() {
current = head;
@@ -474,43 +474,3 @@ public Integer next() {
}
}
}
-
-/**
- * This class is the nodes of the SinglyLinked List. They consist of a value and
- * a pointer to the node after them.
- */
-class Node {
-
- /**
- * The value of the node
- */
- int value;
-
- /**
- * Point to the next node
- */
- Node next;
-
- Node() {
- }
-
- /**
- * Constructor
- *
- * @param value Value to be put in the node
- */
- Node(int value) {
- this(value, null);
- }
-
- /**
- * Constructor
- *
- * @param value Value to be put in the node
- * @param next Reference to the next node
- */
- Node(int value, Node next) {
- this.value = value;
- this.next = next;
- }
-}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java
new file mode 100644
index 000000000000..d0a06369215a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.datastructures.lists;
+
+/**
+ * This class is the nodes of the SinglyLinked List. They consist of a value and
+ * a pointer to the node after them.
+ */
+class SinglyLinkedListNode {
+
+ int value;
+ SinglyLinkedListNode next = null;
+
+ SinglyLinkedListNode() {
+ }
+
+ /**
+ * Constructor
+ *
+ * @param value Value to be put in the node
+ */
+ SinglyLinkedListNode(int value) {
+ this(value, null);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param value Value to be put in the node
+ * @param next Reference to the next node
+ */
+ SinglyLinkedListNode(int value, SinglyLinkedListNode next) {
+ this.value = value;
+ this.next = next;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java
index 4cf782679b7c..e515c9e4adc4 100644
--- a/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java
@@ -4,24 +4,42 @@
import java.util.List;
/**
- * A SortedLinkedList is a data structure that maintains a sorted list of elements.
- * Elements are ordered based on their natural ordering or by a Comparator provided at the time of creation.
- * This implementation uses a singly linked list to store the elements.
- * Further details can be found on this link
+ * The SortedLinkedList class represents a singly linked list that maintains its elements in sorted order.
+ * Elements are ordered based on their natural ordering, with smaller elements at the head and larger elements toward the tail.
+ * The class provides methods for inserting, deleting, and searching elements, as well as checking if the list is empty.
+ *
+ * This implementation utilizes a singly linked list to maintain a dynamically sorted list.
+ *
+ *
+ * Further information can be found here:
* https://runestone.academy/ns/books/published/cppds/LinearLinked/ImplementinganOrderedList.html
+ *
+ *
+ * Usage Example:
+ *
+ * SortedLinkedList list = new SortedLinkedList();
+ * list.insert(10);
+ * list.insert(5);
+ * list.insert(20);
+ * System.out.println(list); // Outputs: [5, 10, 20]
+ *
*/
public class SortedLinkedList {
private Node head;
private Node tail;
+ /**
+ * Initializes an empty sorted linked list.
+ */
public SortedLinkedList() {
this.head = null;
this.tail = null;
}
/**
- * Inserts a new element into the sorted linked list.
- * @param value the value to be inserted
+ * Inserts a new integer into the list, maintaining sorted order.
+ *
+ * @param value the integer to insert
*/
public void insert(int value) {
Node newNode = new Node(value);
@@ -48,16 +66,10 @@ public void insert(int value) {
}
/**
- * Displays the elements of the sorted linked list.
- */
- public void display() {
- System.out.println(this.toString());
- }
-
- /**
- * Deletes the first occurrence of the specified element in the sorted linked list.
- * @param value the value to be deleted
- * @return true if the element is found and deleted, false otherwise
+ * Deletes the first occurrence of a specified integer in the list.
+ *
+ * @param value the integer to delete
+ * @return {@code true} if the element was found and deleted; {@code false} otherwise
*/
public boolean delete(int value) {
if (this.head == null) {
@@ -87,9 +99,10 @@ public boolean delete(int value) {
}
/**
- * Searches for the specified element in the sorted linked list.
- * @param value the value to be searched
- * @return true if the element is found, false otherwise
+ * Searches for a specified integer in the list.
+ *
+ * @param value the integer to search for
+ * @return {@code true} if the value is present in the list; {@code false} otherwise
*/
public boolean search(int value) {
Node temp = this.head;
@@ -103,14 +116,17 @@ public boolean search(int value) {
}
/**
- * Checks if the sorted linked list is empty.
- * @return true if the list is empty, false otherwise
+ * Checks if the list is empty.
+ *
+ * @return {@code true} if the list is empty; {@code false} otherwise
*/
public boolean isEmpty() {
return head == null;
}
+
/**
- * Returns a string representation of the sorted linked list.
+ * Returns a string representation of the sorted linked list in the format [element1, element2, ...].
+ *
* @return a string representation of the sorted linked list
*/
@Override
@@ -123,12 +139,14 @@ public String toString() {
temp = temp.next;
}
return "[" + String.join(", ", elements) + "]";
-
} else {
return "[]";
}
}
+ /**
+ * Node represents an element in the sorted linked list.
+ */
public final class Node {
public final int value;
public Node next;
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java
index c67817a6f2d4..74ee06ca92e4 100644
--- a/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java
+++ b/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java
@@ -1,7 +1,27 @@
package com.thealgorithms.datastructures.queues;
-// This program implements the concept of CircularQueue in Java
-// Link to the concept: (https://en.wikipedia.org/wiki/Circular_buffer)
+/**
+ * The CircularQueue class represents a generic circular queue data structure that uses an array to
+ * store elements. This queue allows efficient utilization of space by wrapping around the array,
+ * thus avoiding the need to shift elements during enqueue and dequeue operations.
+ *
+ * When the queue reaches its maximum capacity, further enqueues will raise an exception.
+ * Similarly, attempts to dequeue or peek from an empty queue will also result in an exception.
+ *
+ *
Reference: Circular Buffer
+ *
+ *
Usage Example:
+ *
+ * CircularQueue queue = new CircularQueue<>(3);
+ * queue.enQueue(1);
+ * queue.enQueue(2);
+ * queue.enQueue(3);
+ * queue.deQueue(); // Removes 1
+ * queue.enQueue(4); // Wraps around and places 4 at the position of removed 1
+ *
+ *
+ * @param the type of elements in this queue
+ */
public class CircularQueue {
private T[] array;
private int topOfQueue;
@@ -9,8 +29,17 @@ public class CircularQueue {
private final int size;
private int currentSize;
+ /**
+ * Constructs a CircularQueue with a specified capacity.
+ *
+ * @param size the maximum number of elements this queue can hold
+ * @throws IllegalArgumentException if the size is less than 1
+ */
@SuppressWarnings("unchecked")
public CircularQueue(int size) {
+ if (size < 1) {
+ throw new IllegalArgumentException("Size must be greater than 0");
+ }
this.array = (T[]) new Object[size];
this.topOfQueue = -1;
this.beginningOfQueue = -1;
@@ -18,14 +47,30 @@ public CircularQueue(int size) {
this.currentSize = 0;
}
+ /**
+ * Checks if the queue is empty.
+ *
+ * @return {@code true} if the queue is empty; {@code false} otherwise
+ */
public boolean isEmpty() {
return currentSize == 0;
}
+ /**
+ * Checks if the queue is full.
+ *
+ * @return {@code true} if the queue has reached its maximum capacity; {@code false} otherwise
+ */
public boolean isFull() {
return currentSize == size;
}
+ /**
+ * Adds a new element to the queue. If the queue is full, an exception is thrown.
+ *
+ * @param value the element to be added to the queue
+ * @throws IllegalStateException if the queue is already full
+ */
public void enQueue(T value) {
if (isFull()) {
throw new IllegalStateException("Queue is full");
@@ -38,12 +83,18 @@ public void enQueue(T value) {
currentSize++;
}
+ /**
+ * Removes and returns the element at the front of the queue.
+ *
+ * @return the element at the front of the queue
+ * @throws IllegalStateException if the queue is empty
+ */
public T deQueue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
T removedValue = array[beginningOfQueue];
- array[beginningOfQueue] = null; // Optional: Help GC
+ array[beginningOfQueue] = null; // Optional: Nullify to help garbage collection
beginningOfQueue = (beginningOfQueue + 1) % size;
currentSize--;
if (isEmpty()) {
@@ -53,6 +104,12 @@ public T deQueue() {
return removedValue;
}
+ /**
+ * Returns the element at the front of the queue without removing it.
+ *
+ * @return the element at the front of the queue
+ * @throws IllegalStateException if the queue is empty
+ */
public T peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
@@ -60,6 +117,9 @@ public T peek() {
return array[beginningOfQueue];
}
+ /**
+ * Deletes the entire queue by resetting all elements and pointers.
+ */
public void deleteQueue() {
array = null;
beginningOfQueue = -1;
@@ -67,35 +127,12 @@ public void deleteQueue() {
currentSize = 0;
}
+ /**
+ * Returns the current number of elements in the queue.
+ *
+ * @return the number of elements currently in the queue
+ */
public int size() {
return currentSize;
}
-
- public static void main(String[] args) {
- CircularQueue cq = new CircularQueue<>(5);
- System.out.println(cq.isEmpty()); // true
- System.out.println(cq.isFull()); // false
- cq.enQueue(1);
- cq.enQueue(2);
- cq.enQueue(3);
- cq.enQueue(4);
- cq.enQueue(5);
-
- System.out.println(cq.deQueue()); // 1
- System.out.println(cq.deQueue()); // 2
- System.out.println(cq.deQueue()); // 3
- System.out.println(cq.deQueue()); // 4
- System.out.println(cq.deQueue()); // 5
-
- System.out.println(cq.isFull()); // false
- System.out.println(cq.isEmpty()); // true
- cq.enQueue(6);
- cq.enQueue(7);
- cq.enQueue(8);
-
- System.out.println(cq.peek()); // 6
- System.out.println(cq.peek()); // 6
-
- cq.deleteQueue();
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java
index 2a3a5a2c38e2..865da7bffc9f 100644
--- a/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java
+++ b/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java
@@ -4,43 +4,46 @@
import java.util.List;
/**
- * This class implements a GenericArrayListQueue.
+ * This class implements a GenericArrayListQueue, a queue data structure that
+ * holds elements of any type specified at runtime, allowing flexibility in the type
+ * of elements it stores.
*
- * A GenericArrayListQueue data structure functions the same as any
- * specific-typed queue. The GenericArrayListQueue holds elements of types
- * to-be-specified at runtime. The elements that are added first are the first
- * to be removed (FIFO). New elements are added to the back/rear of the queue.
+ * The GenericArrayListQueue operates on a First-In-First-Out (FIFO) basis, where
+ * elements added first are the first to be removed. New elements are added to the back
+ * (or rear) of the queue, while removal of elements occurs from the front.
+ *
+ * @param The type of elements held in this queue.
*/
public class GenericArrayListQueue {
/**
- * The generic List for the queue. T is the generic element type.
+ * A list that stores the queue's elements in insertion order.
*/
private final List elementList = new ArrayList<>();
/**
* Checks if the queue is empty.
*
- * @return True if the queue is empty, false otherwise.
+ * @return {@code true} if the queue has no elements; {@code false} otherwise.
*/
- private boolean isEmpty() {
+ public boolean isEmpty() {
return elementList.isEmpty();
}
/**
- * Returns the element at the front of the queue without removing it.
+ * Retrieves, but does not remove, the element at the front of the queue.
*
- * @return The element at the front of the queue, or null if the queue is empty.
+ * @return The element at the front of the queue, or {@code null} if the queue is empty.
*/
public T peek() {
return isEmpty() ? null : elementList.getFirst();
}
/**
- * Inserts an element of type T to the back of the queue.
+ * Inserts an element at the back of the queue.
*
- * @param element the element to be added to the queue.
- * @return True if the element was added successfully.
+ * @param element The element to be added to the queue.
+ * @return {@code true} if the element was successfully added.
*/
public boolean add(T element) {
return elementList.add(element);
@@ -49,7 +52,7 @@ public boolean add(T element) {
/**
* Retrieves and removes the element at the front of the queue.
*
- * @return The element removed from the front of the queue, or null if the queue is empty.
+ * @return The element removed from the front of the queue, or {@code null} if the queue is empty.
*/
public T poll() {
return isEmpty() ? null : elementList.removeFirst();
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java
new file mode 100644
index 000000000000..11e5e9b83892
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java
@@ -0,0 +1,87 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.NoSuchElementException;
+import java.util.Stack;
+
+/**
+ * A queue implementation using two stacks. This class provides methods to
+ * enqueue (add) elements to the end of the queue and dequeue (remove)
+ * elements from the front, while utilizing two internal stacks to manage
+ * the order of elements.
+ *
+ * @param The type of elements held in this queue.
+ */
+public class QueueByTwoStacks {
+
+ private final Stack enqueueStk;
+ private final Stack dequeueStk;
+
+ /**
+ * Constructor that initializes two empty stacks for the queue.
+ * The `enqueueStk` is used to push elements when enqueuing, and
+ * the `dequeueStk` is used to pop elements when dequeuing.
+ */
+ public QueueByTwoStacks() {
+ enqueueStk = new Stack<>();
+ dequeueStk = new Stack<>();
+ }
+
+ /**
+ * Adds an element to the end of the queue. This method pushes the element
+ * onto the `enqueueStk`.
+ *
+ * @param item The element to be added to the queue.
+ */
+ public void put(T item) {
+ enqueueStk.push(item);
+ }
+
+ /**
+ * Removes and returns the element at the front of the queue.
+ * If `dequeueStk` is empty, it transfers all elements from
+ * `enqueueStk` to `dequeueStk` to maintain the correct FIFO
+ * (First-In-First-Out) order before popping.
+ *
+ * @return The element at the front of the queue.
+ * @throws NoSuchElementException If the queue is empty.
+ */
+ public T get() {
+ if (dequeueStk.isEmpty()) {
+ while (!enqueueStk.isEmpty()) {
+ dequeueStk.push(enqueueStk.pop());
+ }
+ }
+ if (dequeueStk.isEmpty()) {
+ throw new NoSuchElementException("Queue is empty");
+ }
+ return dequeueStk.pop();
+ }
+
+ /**
+ * Returns the total number of elements currently in the queue.
+ * This is the sum of the sizes of both stacks.
+ *
+ * @return The number of elements in the queue.
+ */
+ public int size() {
+ return enqueueStk.size() + dequeueStk.size();
+ }
+
+ /**
+ * Returns a string representation of the queue, showing the elements
+ * in the correct order (from front to back).
+ * The `dequeueStk` is first cloned, and then all elements from the
+ * `enqueueStk` are added to the cloned stack in reverse order to
+ * represent the queue accurately.
+ *
+ * @return A string representation of the queue.
+ */
+ @Override
+ public String toString() {
+ Stack tempStack = (Stack) dequeueStk.clone();
+ while (!enqueueStk.isEmpty()) {
+ tempStack.push(enqueueStk.pop());
+ }
+ return "Queue(" + tempStack + ")";
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java b/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java
new file mode 100644
index 000000000000..d6720dd01e29
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java
@@ -0,0 +1,63 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * The {@code SlidingWindowMaximum} class provides a method to efficiently compute
+ * the maximum element within every sliding window of size {@code k} in a given array.
+ *
+ * The algorithm uses a deque to maintain the indices of useful elements within
+ * the current sliding window. The time complexity of this approach is O(n) since
+ * each element is processed at most twice.
+ *
+ * @author Hardvan
+ */
+public final class SlidingWindowMaximum {
+ private SlidingWindowMaximum() {
+ }
+
+ /**
+ * Returns an array of the maximum values for each sliding window of size {@code k}.
+ *
If {@code nums} has fewer elements than {@code k}, the result will be an empty array.
+ *
Example:
+ *
+ * Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
+ * Output: [3, 3, 5, 5, 6, 7]
+ *
+ *
+ * @param nums the input array of integers
+ * @param k the size of the sliding window
+ * @return an array containing the maximum element for each sliding window
+ */
+ public static int[] maxSlidingWindow(int[] nums, int k) {
+ int n = nums.length;
+ if (n < k || k == 0) {
+ return new int[0];
+ }
+
+ int[] result = new int[n - k + 1];
+ Deque deque = new LinkedList<>();
+ for (int i = 0; i < n; i++) {
+ // Remove elements from the front of the deque if they are out of the current window
+ if (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
+ deque.pollFirst();
+ }
+
+ // Remove elements from the back if they are smaller than the current element
+ while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
+ deque.pollLast();
+ }
+
+ // Add the current element's index to the deque
+ deque.offerLast(i);
+
+ // Store the maximum element for the current window (starting from the k-1th element)
+ if (i >= k - 1) {
+ result[i - k + 1] = nums[deque.peekFirst()];
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java b/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java
new file mode 100644
index 000000000000..999c963fab93
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java
@@ -0,0 +1,61 @@
+package com.thealgorithms.datastructures.queues;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * TokenBucket implements a token bucket rate limiter algorithm.
+ * This class is used to control the rate of requests in a distributed system.
+ * It allows a certain number of requests (tokens) to be processed in a time frame,
+ * based on the defined refill rate.
+ *
+ * Applications: Computer networks, API rate limiting, distributed systems, etc.
+ *
+ * @author Hardvan
+ */
+public final class TokenBucket {
+ private final int maxTokens;
+ private final int refillRate; // tokens per second
+ private int tokens;
+ private long lastRefill; // Timestamp in nanoseconds
+
+ /**
+ * Constructs a TokenBucket instance.
+ *
+ * @param maxTokens Maximum number of tokens the bucket can hold.
+ * @param refillRate The rate at which tokens are refilled (tokens per second).
+ */
+ public TokenBucket(int maxTokens, int refillRate) {
+ this.maxTokens = maxTokens;
+ this.refillRate = refillRate;
+ this.tokens = maxTokens;
+ this.lastRefill = System.nanoTime();
+ }
+
+ /**
+ * Attempts to allow a request based on the available tokens.
+ * If a token is available, it decrements the token count and allows the request.
+ * Otherwise, the request is denied.
+ *
+ * @return true if the request is allowed, false if the request is denied.
+ */
+ public synchronized boolean allowRequest() {
+ refillTokens();
+ if (tokens > 0) {
+ tokens--;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Refills the tokens based on the time elapsed since the last refill.
+ * The number of tokens to be added is calculated based on the elapsed time
+ * and the refill rate, ensuring the total does not exceed maxTokens.
+ */
+ private void refillTokens() {
+ long now = System.nanoTime();
+ long tokensToAdd = (now - lastRefill) / TimeUnit.SECONDS.toNanos(1) * refillRate;
+ tokens = Math.min(maxTokens, tokens + (int) tokensToAdd);
+ lastRefill = now;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java b/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java
index 7c4f334cd617..bbcdfe1cc2a8 100644
--- a/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java
+++ b/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java
@@ -1,161 +1,98 @@
package com.thealgorithms.datastructures.stacks;
/**
- * Implementation of a stack using nodes. Unlimited size, no arraylist.
+ * A stack implementation using linked nodes, supporting unlimited size without an ArrayList.
*
- * @author Kyler Smith, 2017
+ * Each node in the stack contains data of generic type {@code Item}, along with references
+ * to the next and previous nodes, supporting typical stack operations.
+ *
+ *
The stack follows a Last-In-First-Out (LIFO) order where elements added last are
+ * removed first. Supported operations include push, pop, and peek.
+ *
+ * @param - the type of elements held in this stack
*/
public class NodeStack
- {
/**
- * Entry point for the program.
+ * Node class representing each element in the stack.
*/
- public static void main(String[] args) {
- NodeStack stack = new NodeStack();
-
- stack.push(3);
- stack.push(4);
- stack.push(5);
- System.out.println("Testing :");
- stack.print(); // prints : 5 4 3
+ private class Node {
+ Item data;
+ Node previous;
- Integer x = stack.pop(); // x = 5
- stack.push(1);
- stack.push(8);
- Integer y = stack.peek(); // y = 8
- System.out.println("Testing :");
- stack.print(); // prints : 8 1 4 3
-
- System.out.println("Testing :");
- System.out.println("x : " + x);
- System.out.println("y : " + y);
+ Node(Item data) {
+ this.data = data;
+ this.previous = null;
+ }
}
- /**
- * Information each node should contain.
- *
- * @value data : information of the value in the node
- * @value head : the head of the stack
- * @value next : the next value from this node
- * @value previous : the last value from this node
- * @value size : size of the stack
- */
- private Item data;
-
- private static NodeStack> head;
- private NodeStack> previous;
- private static int size = 0;
+ private Node head; // Top node in the stack
+ private int size; // Number of elements in the stack
/**
- * Constructors for the NodeStack.
+ * Constructs an empty NodeStack.
*/
public NodeStack() {
- }
-
- private NodeStack(Item item) {
- this.data = item;
+ head = null;
+ size = 0;
}
/**
- * Put a value onto the stack.
+ * Pushes an item onto the stack.
*
- * @param item : value to be put on the stack.
+ * @param item the item to be pushed onto the stack
*/
public void push(Item item) {
- NodeStack
- newNs = new NodeStack
- (item);
-
- if (this.isEmpty()) {
- NodeStack.setHead(new NodeStack<>(item));
- newNs.setNext(null);
- newNs.setPrevious(null);
- } else {
- newNs.setPrevious(NodeStack.head);
- NodeStack.head.setNext(newNs);
- NodeStack.setHead(newNs);
- }
-
- NodeStack.setSize(NodeStack.getSize() + 1);
+ Node newNode = new Node(item);
+ newNode.previous = head;
+ head = newNode;
+ size++;
}
/**
- * Value to be taken off the stack.
+ * Removes and returns the item at the top of the stack.
*
- * @return item : value that is returned.
+ * @return the item at the top of the stack, or {@code null} if the stack is empty
+ * @throws IllegalStateException if the stack is empty
*/
public Item pop() {
- Item item = (Item) NodeStack.head.getData();
-
- NodeStack.setHead(NodeStack.head.getPrevious());
- NodeStack.head.setNext(null);
-
- NodeStack.setSize(NodeStack.getSize() - 1);
-
- return item;
+ if (isEmpty()) {
+ throw new IllegalStateException("Cannot pop from an empty stack.");
+ }
+ Item data = head.data;
+ head = head.previous;
+ size--;
+ return data;
}
/**
- * Value that is next to be taken off the stack.
+ * Returns the item at the top of the stack without removing it.
*
- * @return item : the next value that would be popped off the stack.
+ * @return the item at the top of the stack, or {@code null} if the stack is empty
+ * @throws IllegalStateException if the stack is empty
*/
public Item peek() {
- return (Item) NodeStack.head.getData();
+ if (isEmpty()) {
+ throw new IllegalStateException("Cannot peek from an empty stack.");
+ }
+ return head.data;
}
/**
- * If the stack is empty or there is a value in.
+ * Checks whether the stack is empty.
*
- * @return boolean : whether or not the stack has anything in it.
+ * @return {@code true} if the stack has no elements, {@code false} otherwise
*/
public boolean isEmpty() {
- return NodeStack.getSize() == 0;
+ return head == null;
}
/**
- * Returns the size of the stack.
+ * Returns the number of elements currently in the stack.
*
- * @return int : number of values in the stack.
+ * @return the size of the stack
*/
public int size() {
- return NodeStack.getSize();
- }
-
- /**
- * Print the contents of the stack in the following format.
- *
- *
- * x <- head (next out) y z <- tail (first in) . . .
- */
- public void print() {
- for (NodeStack> n = NodeStack.head; n != null; n = n.previous) {
- System.out.println(n.getData().toString());
- }
- }
-
- private static void setHead(NodeStack> ns) {
- NodeStack.head = ns;
- }
-
- private void setNext(NodeStack> next) {
- }
-
- private NodeStack> getPrevious() {
- return previous;
- }
-
- private void setPrevious(NodeStack> previous) {
- this.previous = previous;
- }
-
- private static int getSize() {
return size;
}
-
- private static void setSize(int size) {
- NodeStack.size = size;
- }
-
- private Item getData() {
- return this.data;
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java
index 84e9df96e0d9..e9de14b53302 100644
--- a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java
+++ b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java
@@ -1,10 +1,25 @@
package com.thealgorithms.datastructures.stacks;
-import java.util.Scanner;
import java.util.Stack;
/**
- * Reversal of a stack using recursion.
+ * Provides methods to reverse a stack using recursion.
+ *
+ *
This class includes methods to reverse the order of elements in a stack
+ * without using additional data structures. Elements are inserted at the bottom
+ * of the stack to achieve the reverse order.
+ *
+ *
Example usage:
+ *
+ * Stack stack = new Stack<>();
+ * stack.push(1);
+ * stack.push(2);
+ * stack.push(3);
+ * ReverseStack.reverseStack(stack);
+ *
+ * After calling {@code reverseStack(stack)}, the stack's order is reversed.
+ *
+ * This class is final and has a private constructor to prevent instantiation.
*
* @author Ishika Agarwal, 2021
*/
@@ -12,56 +27,48 @@ public final class ReverseStack {
private ReverseStack() {
}
- public static void main(String[] args) {
- try (Scanner sc = new Scanner(System.in)) {
- System.out.println("Enter the number of elements you wish to insert in the stack");
- int n = sc.nextInt();
- int i;
- Stack stack = new Stack();
- System.out.println("Enter the stack elements");
- for (i = 0; i < n; i++) {
- stack.push(sc.nextInt());
- }
- reverseStack(stack);
- System.out.println("The reversed stack is:");
- while (!stack.isEmpty()) {
- System.out.print(stack.peek() + ",");
- stack.pop();
- }
- }
- }
-
- private static void reverseStack(Stack stack) {
+ /**
+ * Reverses the order of elements in the given stack using recursion.
+ * Steps:
+ * 1. Check if the stack is empty. If so, return.
+ * 2. Pop the top element from the stack.
+ * 3. Recursively reverse the remaining stack.
+ * 4. Insert the originally popped element at the bottom of the reversed stack.
+ *
+ * @param stack the stack to reverse; should not be null
+ */
+ public static void reverseStack(Stack stack) {
if (stack.isEmpty()) {
return;
}
- // Store the topmost element
- int element = stack.peek();
- // Remove the topmost element
- stack.pop();
-
- // Reverse the stack for the leftover elements
+ int element = stack.pop();
reverseStack(stack);
-
- // Insert the topmost element to the bottom of the stack
insertAtBottom(stack, element);
}
+ /**
+ * Inserts the specified element at the bottom of the stack.
+ *
+ * This method is a helper for {@link #reverseStack(Stack)}.
+ *
+ * Steps:
+ * 1. If the stack is empty, push the element and return.
+ * 2. Remove the top element from the stack.
+ * 3. Recursively insert the new element at the bottom of the stack.
+ * 4. Push the removed element back onto the stack.
+ *
+ * @param stack the stack in which to insert the element; should not be null
+ * @param element the element to insert at the bottom of the stack
+ */
private static void insertAtBottom(Stack stack, int element) {
if (stack.isEmpty()) {
- // When stack is empty, insert the element so it will be present at
- // the bottom of the stack
stack.push(element);
return;
}
- int ele = stack.peek();
- // Keep popping elements till stack becomes empty. Push the elements
- // once the topmost element has moved to the bottom of the stack.
- stack.pop();
+ int topElement = stack.pop();
insertAtBottom(stack, element);
-
- stack.push(ele);
+ stack.push(topElement);
}
}
diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java
index f98db7cc1550..9369b3fc9a64 100644
--- a/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java
+++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java
@@ -1,7 +1,13 @@
package com.thealgorithms.datastructures.stacks;
/**
- * This class implements a Stack using a regular array.
+ * Implements a generic stack using an array.
+ *
+ * This stack automatically resizes when necessary, growing to accommodate additional elements and
+ * shrinking to conserve memory when its size significantly decreases.
+ *
+ *
Elements are pushed and popped in LIFO (last-in, first-out) order, where the last element added
+ * is the first to be removed.
*
* @param the type of elements in this stack
*/
@@ -13,11 +19,20 @@ public class StackArray implements Stack {
private T[] stackArray;
private int top;
+ /**
+ * Creates a stack with a default capacity.
+ */
@SuppressWarnings("unchecked")
public StackArray() {
this(DEFAULT_CAPACITY);
}
+ /**
+ * Creates a stack with a specified initial capacity.
+ *
+ * @param size the initial capacity of the stack, must be greater than 0
+ * @throws IllegalArgumentException if size is less than or equal to 0
+ */
@SuppressWarnings("unchecked")
public StackArray(int size) {
if (size <= 0) {
@@ -28,6 +43,11 @@ public StackArray(int size) {
this.top = -1;
}
+ /**
+ * Pushes an element onto the top of the stack. Resizes the stack if it is full.
+ *
+ * @param value the element to push
+ */
@Override
public void push(T value) {
if (isFull()) {
@@ -36,6 +56,13 @@ public void push(T value) {
stackArray[++top] = value;
}
+ /**
+ * Removes and returns the element from the top of the stack. Shrinks the stack if
+ * its size is below a quarter of its capacity, but not below the default capacity.
+ *
+ * @return the element removed from the top of the stack
+ * @throws IllegalStateException if the stack is empty
+ */
@Override
public T pop() {
if (isEmpty()) {
@@ -48,6 +75,12 @@ public T pop() {
return value;
}
+ /**
+ * Returns the element at the top of the stack without removing it.
+ *
+ * @return the top element of the stack
+ * @throws IllegalStateException if the stack is empty
+ */
@Override
public T peek() {
if (isEmpty()) {
@@ -56,6 +89,11 @@ public T peek() {
return stackArray[top];
}
+ /**
+ * Resizes the internal array to a new capacity.
+ *
+ * @param newSize the new size of the stack array
+ */
private void resize(int newSize) {
@SuppressWarnings("unchecked") T[] newArray = (T[]) new Object[newSize];
System.arraycopy(stackArray, 0, newArray, 0, top + 1);
@@ -63,21 +101,60 @@ private void resize(int newSize) {
maxSize = newSize;
}
+ /**
+ * Checks if the stack is full.
+ *
+ * @return true if the stack is full, false otherwise
+ */
public boolean isFull() {
return top + 1 == maxSize;
}
+ /**
+ * Checks if the stack is empty.
+ *
+ * @return true if the stack is empty, false otherwise
+ */
@Override
public boolean isEmpty() {
return top == -1;
}
- @Override public void makeEmpty() { // Doesn't delete elements in the array but if you call
- top = -1; // push method after calling makeEmpty it will overwrite previous values
+ /**
+ * Empties the stack, marking it as empty without deleting elements. Elements are
+ * overwritten on subsequent pushes.
+ */
+ @Override
+ public void makeEmpty() {
+ top = -1;
}
+ /**
+ * Returns the number of elements currently in the stack.
+ *
+ * @return the size of the stack
+ */
@Override
public int size() {
return top + 1;
}
+
+ /**
+ * Returns a string representation of the stack.
+ *
+ * @return a string representation of the stack
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("StackArray [");
+ for (int i = 0; i <= top; i++) {
+ sb.append(stackArray[i]);
+ if (i < top) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java
index 088156a98f78..bd400adea317 100644
--- a/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java
+++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java
@@ -4,23 +4,41 @@
import java.util.EmptyStackException;
/**
- * This class implements a Stack using an ArrayList.
+ * A stack implementation backed by an {@link ArrayList}, offering dynamic resizing
+ * and LIFO (Last-In-First-Out) behavior.
*
- * @param the type of elements in this stack
+ * The stack grows dynamically as elements are added, and elements are removed
+ * in reverse order of their addition.
+ *
+ * @param the type of elements stored in this stack
*/
public class StackArrayList implements Stack {
private final ArrayList stack;
+ /**
+ * Constructs an empty stack.
+ */
public StackArrayList() {
stack = new ArrayList<>();
}
+ /**
+ * Adds an element to the top of the stack.
+ *
+ * @param value the element to be added
+ */
@Override
public void push(T value) {
stack.add(value);
}
+ /**
+ * Removes and returns the element from the top of the stack.
+ *
+ * @return the element removed from the top of the stack
+ * @throws EmptyStackException if the stack is empty
+ */
@Override
public T pop() {
if (isEmpty()) {
@@ -29,6 +47,12 @@ public T pop() {
return stack.removeLast();
}
+ /**
+ * Returns the element at the top of the stack without removing it.
+ *
+ * @return the top element of the stack
+ * @throws EmptyStackException if the stack is empty
+ */
@Override
public T peek() {
if (isEmpty()) {
@@ -37,16 +61,29 @@ public T peek() {
return stack.getLast();
}
+ /**
+ * Checks if the stack is empty.
+ *
+ * @return {@code true} if the stack is empty, {@code false} otherwise
+ */
@Override
public boolean isEmpty() {
return stack.isEmpty();
}
+ /**
+ * Empties the stack, removing all elements.
+ */
@Override
public void makeEmpty() {
stack.clear();
}
+ /**
+ * Returns the number of elements in the stack.
+ *
+ * @return the current size of the stack
+ */
@Override
public int size() {
return stack.size();
diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java
index 52b1c1d86c94..c12097dfa28c 100644
--- a/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java
+++ b/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java
@@ -3,35 +3,20 @@
import java.util.NoSuchElementException;
/**
- * @author Varun Upadhyay (https://github.com/varunu28)
+ * A stack implementation using a singly linked list.
+ *
+ * This class provides methods to push, pop, and peek elements in a Last-In-First-Out (LIFO) manner.
+ * It keeps track of the number of elements in the stack and allows checking if the stack is empty.
+ *
+ *
This implementation does not allow null elements to be pushed onto the stack.
*/
-// An implementation of a Stack using a Linked List
final class StackOfLinkedList {
private StackOfLinkedList() {
}
-
- public static void main(String[] args) {
- LinkedListStack stack = new LinkedListStack();
- stack.push(1);
- stack.push(2);
- stack.push(3);
- stack.push(4);
- stack.push(5);
-
- System.out.println(stack);
-
- System.out.println("Size of stack currently is: " + stack.getSize());
-
- assert stack.pop() == 5;
- assert stack.pop() == 4;
-
- System.out.println("Top element of stack currently is: " + stack.peek());
- }
}
-// A node class
+// A node class for the linked list
class Node {
-
public int data;
public Node next;
@@ -42,25 +27,24 @@ class Node {
}
/**
- * A class which implements a stack using a linked list
+ * A class that implements a stack using a linked list.
*
- *
- * Contains all the stack methods : push, pop, printStack, isEmpty
+ *
This stack supports basic operations:
+ *
+ * - push: Adds an element to the top of the stack
+ * - pop: Removes and returns the top element of the stack
+ * - peek: Returns the top element without removing it
+ * - isEmpty: Checks if the stack is empty
+ * - getSize: Returns the current size of the stack
+ *
*/
class LinkedListStack {
- /**
- * Top of stack
- */
- Node head;
+ private Node head; // Top of the stack
+ private int size; // Number of elements in the stack
/**
- * Size of stack
- */
- private int size;
-
- /**
- * Init properties
+ * Initializes an empty stack.
*/
LinkedListStack() {
head = null;
@@ -68,10 +52,10 @@ class LinkedListStack {
}
/**
- * Add element at top
+ * Adds an element to the top of the stack.
*
- * @param x to be added
- * @return true if add successfully
+ * @param x the element to be added
+ * @return true if the element is added successfully
*/
public boolean push(int x) {
Node newNode = new Node(x);
@@ -82,10 +66,10 @@ public boolean push(int x) {
}
/**
- * Pop element at top of stack
+ * Removes and returns the top element of the stack.
*
- * @return element at top of stack
- * @throws NoSuchElementException if stack is empty
+ * @return the element at the top of the stack
+ * @throws NoSuchElementException if the stack is empty
*/
public int pop() {
if (size == 0) {
@@ -94,20 +78,20 @@ public int pop() {
Node destroy = head;
head = head.next;
int retValue = destroy.data;
- destroy = null; // clear to let GC do it's work
+ destroy = null; // Help garbage collection
size--;
return retValue;
}
/**
- * Peek element at top of stack
+ * Returns the top element of the stack without removing it.
*
- * @return element at top of stack
- * @throws NoSuchElementException if stack is empty
+ * @return the element at the top of the stack
+ * @throws NoSuchElementException if the stack is empty
*/
public int peek() {
if (size == 0) {
- throw new NoSuchElementException("Empty stack. Nothing to pop");
+ throw new NoSuchElementException("Empty stack. Nothing to peek");
}
return head.data;
}
@@ -120,24 +104,32 @@ public String toString() {
builder.append(cur.data).append("->");
cur = cur.next;
}
- return builder.replace(builder.length() - 2, builder.length(), "").toString();
+ return builder.replace(builder.length() - 2, builder.length(), "").toString(); // Remove the last "->"
}
/**
- * Check if stack is empty
+ * Checks if the stack is empty.
*
- * @return true if stack is empty, otherwise false
+ * @return true if the stack is empty, false otherwise
*/
public boolean isEmpty() {
return size == 0;
}
/**
- * Return size of stack
+ * Returns the current size of the stack.
*
- * @return size of stack
+ * @return the number of elements in the stack
*/
public int getSize() {
return size;
}
+
+ /**
+ * Removes all elements from the stack.
+ */
+ public void makeEmpty() {
+ head = null;
+ size = 0;
+ }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java
index 7b959b085353..77ee5d5fa23e 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java
@@ -1,11 +1,19 @@
package com.thealgorithms.datastructures.trees;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an AVL Tree, a self-balancing binary search tree.
+ * In an AVL tree, the heights of the two child subtrees of any node
+ * differ by at most one. If they differ by more than one at any time,
+ * rebalancing is performed to restore this property.
+ */
public class AVLTree {
private Node root;
- private class Node {
-
+ private static class Node {
private int key;
private int balance;
private int height;
@@ -17,8 +25,18 @@ private class Node {
key = k;
parent = p;
}
+
+ public Integer getBalance() {
+ return balance;
+ }
}
+ /**
+ * Inserts a new key into the AVL tree.
+ *
+ * @param key the key to be inserted
+ * @return {@code true} if the key was inserted, {@code false} if the key already exists
+ */
public boolean insert(int key) {
if (root == null) {
root = new Node(key, null);
@@ -31,7 +49,6 @@ public boolean insert(int key) {
}
parent = n;
-
boolean goLeft = n.key > key;
n = goLeft ? n.left : n.right;
@@ -49,8 +66,32 @@ public boolean insert(int key) {
return true;
}
+ /**
+ * Deletes a key from the AVL tree.
+ *
+ * @param delKey the key to be deleted
+ */
+ public void delete(int delKey) {
+ if (root == null) {
+ return;
+ }
+
+ // Find the node to be deleted
+ Node node = root;
+ Node child = root;
+ while (child != null) {
+ node = child;
+ child = delKey >= node.key ? node.right : node.left;
+ if (delKey == node.key) {
+ delete(node);
+ return;
+ }
+ }
+ }
+
private void delete(Node node) {
if (node.left == null && node.right == null) {
+ // Leaf node
if (node.parent == null) {
root = null;
} else {
@@ -64,6 +105,8 @@ private void delete(Node node) {
}
return;
}
+
+ // Node has one or two children
Node child;
if (node.left != null) {
child = node.left;
@@ -80,26 +123,49 @@ private void delete(Node node) {
delete(child);
}
- public void delete(int delKey) {
- if (root == null) {
- return;
+ /**
+ * Returns a list of balance factors for each node in the tree.
+ *
+ * @return a list of integers representing the balance factors of the nodes
+ */
+ public List returnBalance() {
+ List balances = new ArrayList<>();
+ returnBalance(root, balances);
+ return balances;
+ }
+
+ private void returnBalance(Node n, List balances) {
+ if (n != null) {
+ returnBalance(n.left, balances);
+ balances.add(n.getBalance());
+ returnBalance(n.right, balances);
}
- Node node = root;
- Node child = root;
+ }
- while (child != null) {
- node = child;
- child = delKey >= node.key ? node.right : node.left;
- if (delKey == node.key) {
- delete(node);
- return;
- }
+ /**
+ * Searches for a key in the AVL tree.
+ *
+ * @param key the key to be searched
+ * @return true if the key is found, false otherwise
+ */
+ public boolean search(int key) {
+ Node result = searchHelper(this.root, key);
+ return result != null;
+ }
+
+ private Node searchHelper(Node root, int key) {
+ if (root == null || root.key == key) {
+ return root;
}
+
+ if (root.key > key) {
+ return searchHelper(root.left, key);
+ }
+ return searchHelper(root.right, key);
}
private void rebalance(Node n) {
setBalance(n);
-
if (n.balance == -2) {
if (height(n.left.left) >= height(n.left.right)) {
n = rotateRight(n);
@@ -143,7 +209,6 @@ private Node rotateLeft(Node a) {
}
setBalance(a, b);
-
return b;
}
@@ -169,7 +234,6 @@ private Node rotateRight(Node a) {
}
setBalance(a, b);
-
return b;
}
@@ -197,53 +261,9 @@ private void setBalance(Node... nodes) {
}
}
- public void printBalance() {
- printBalance(root);
- }
-
- private void printBalance(Node n) {
- if (n != null) {
- printBalance(n.left);
- System.out.printf("%s ", n.balance);
- printBalance(n.right);
- }
- }
-
private void reheight(Node node) {
if (node != null) {
node.height = 1 + Math.max(height(node.left), height(node.right));
}
}
-
- public boolean search(int key) {
- Node result = searchHelper(this.root, key);
- return result != null;
- }
-
- private Node searchHelper(Node root, int key) {
- // root is null or key is present at root
- if (root == null || root.key == key) {
- return root;
- }
-
- // key is greater than root's key
- if (root.key > key) {
- return searchHelper(root.left, key); // call the function on the node's left child
- }
- // key is less than root's key then
- // call the function on the node's right child as it is greater
- return searchHelper(root.right, key);
- }
-
- public static void main(String[] args) {
- AVLTree tree = new AVLTree();
-
- System.out.println("Inserting values 1 to 10");
- for (int i = 1; i < 10; i++) {
- tree.insert(i);
- }
-
- System.out.print("Printing balance: ");
- tree.printBalance();
- }
}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java
index d4d677a4cda0..ff02fe38970b 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java
@@ -144,7 +144,7 @@ public boolean remove(int value) {
if (temp == root) {
root = null;
} // This if/else assigns the new node to be either the left or right child of the
- // parent
+ // parent
else if (temp.parent.data < temp.data) {
temp.parent.right = null;
} else {
@@ -281,7 +281,7 @@ public void preOrder(Node localRoot) {
}
/**
- * Prints rightChild - leftChild - root
+ * Prints leftChild - rightChild - root
*
* @param localRoot The local root of the binary tree
*/
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java b/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java
new file mode 100644
index 000000000000..9a79902cc598
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java
@@ -0,0 +1,168 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * BoundaryTraversal
+ *
+ * Start with the Root:
+ * Add the root node to the boundary list.
+ * Traverse the Left Boundary (Excluding Leaf Nodes):
+ * Move down the left side of the tree, adding each non-leaf node to the boundary list.
+ * If a node has a left child, go left; otherwise, go right.
+ * Visit All Leaf Nodes:
+ * Traverse the tree and add all leaf nodes to the boundary list, from left to right.
+ * Traverse the Right Boundary (Excluding Leaf Nodes) in Reverse Order:
+ * Move up the right side of the tree, adding each non-leaf node to a temporary list.
+ * If a node has a right child, go right; otherwise, go left.
+ * Reverse the temporary list and add it to the boundary list.
+ * Combine and Output:
+ * The final boundary list contains the root, left boundary, leaf nodes, and reversed right boundary in that order.
+ */
+public final class BoundaryTraversal {
+ private BoundaryTraversal() {
+ }
+
+ // Main function for boundary traversal, returns a list of boundary nodes in order
+ public static List boundaryTraversal(BinaryTree.Node root) {
+ List result = new ArrayList<>();
+ if (root == null) {
+ return result;
+ }
+
+ // Add root node if it's not a leaf node
+ if (!isLeaf(root)) {
+ result.add(root.data);
+ }
+
+ // Add left boundary
+ addLeftBoundary(root, result);
+
+ // Add leaf nodes
+ addLeaves(root, result);
+
+ // Add right boundary
+ addRightBoundary(root, result);
+
+ return result;
+ }
+
+ // Adds the left boundary, including nodes that have no left child but have a right child
+ private static void addLeftBoundary(BinaryTree.Node node, List result) {
+ BinaryTree.Node cur = node.left;
+
+ // If there is no left child but there is a right child, treat the right child as part of the left boundary
+ if (cur == null && node.right != null) {
+ cur = node.right;
+ }
+
+ while (cur != null) {
+ if (!isLeaf(cur)) {
+ result.add(cur.data); // Add non-leaf nodes to result
+ }
+ if (cur.left != null) {
+ cur = cur.left; // Move to the left child
+ } else if (cur.right != null) {
+ cur = cur.right; // If left child is null, move to the right child
+ } else {
+ break; // Stop if there are no children
+ }
+ }
+ }
+
+ // Adds leaf nodes (nodes without children)
+ private static void addLeaves(BinaryTree.Node node, List result) {
+ if (node == null) {
+ return;
+ }
+ if (isLeaf(node)) {
+ result.add(node.data); // Add leaf node
+ } else {
+ addLeaves(node.left, result); // Recur for left subtree
+ addLeaves(node.right, result); // Recur for right subtree
+ }
+ }
+
+ // Adds the right boundary, excluding leaf nodes
+ private static void addRightBoundary(BinaryTree.Node node, List result) {
+ BinaryTree.Node cur = node.right;
+ List temp = new ArrayList<>();
+
+ // If no right boundary is present and there is no left subtree, skip
+ if (cur != null && node.left == null) {
+ return;
+ }
+ while (cur != null) {
+ if (!isLeaf(cur)) {
+ temp.add(cur.data); // Store non-leaf nodes temporarily
+ }
+ if (cur.right != null) {
+ cur = cur.right; // Move to the right child
+ } else if (cur.left != null) {
+ cur = cur.left; // If right child is null, move to the left child
+ } else {
+ break; // Stop if there are no children
+ }
+ }
+
+ // Add the right boundary nodes in reverse order
+ for (int i = temp.size() - 1; i >= 0; i--) {
+ result.add(temp.get(i));
+ }
+ }
+
+ // Checks if a node is a leaf node
+ private static boolean isLeaf(BinaryTree.Node node) {
+ return node.left == null && node.right == null;
+ }
+
+ // Iterative boundary traversal
+ public static List iterativeBoundaryTraversal(BinaryTree.Node root) {
+ List result = new ArrayList<>();
+ if (root == null) {
+ return result;
+ }
+
+ // Add root node if it's not a leaf node
+ if (!isLeaf(root)) {
+ result.add(root.data);
+ }
+
+ // Handle the left boundary
+ BinaryTree.Node cur = root.left;
+ if (cur == null && root.right != null) {
+ cur = root.right;
+ }
+ while (cur != null) {
+ if (!isLeaf(cur)) {
+ result.add(cur.data); // Add non-leaf nodes to result
+ }
+ cur = (cur.left != null) ? cur.left : cur.right; // Prioritize left child, move to right if left is null
+ }
+
+ // Add leaf nodes
+ addLeaves(root, result);
+
+ // Handle the right boundary using a stack (reverse order)
+ cur = root.right;
+ Deque stack = new LinkedList<>();
+ if (cur != null && root.left == null) {
+ return result;
+ }
+ while (cur != null) {
+ if (!isLeaf(cur)) {
+ stack.push(cur.data); // Temporarily store right boundary nodes in a stack
+ }
+ cur = (cur.right != null) ? cur.right : cur.left; // Prioritize right child, move to left if right is null
+ }
+
+ // Add the right boundary nodes from the stack to maintain the correct order
+ while (!stack.isEmpty()) {
+ result.add(stack.pop());
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java b/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java
index a2e36f33dd4b..7d969a59def0 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java
@@ -23,6 +23,7 @@ private static final class Node {
}
private final Node root;
+
public GenericTree() { // Constructor
Scanner scn = new Scanner(System.in);
root = createTreeG(null, 0, scn);
@@ -225,8 +226,6 @@ private void removeleaves(Node node) {
for (int i = 0; i < node.child.size(); i++) {
if (node.child.get(i).child.size() == 0) {
arr.add(i);
- // node.child.remove(i);
- // i--;
} else {
removeleaves(node.child.get(i));
}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java b/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java
new file mode 100644
index 000000000000..e0d255b1e784
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java
@@ -0,0 +1,176 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Point is a simple class that represents a point in 2D space.
+ *
+ * @see Point
+ * @author Sailok Chinta
+ */
+class Point {
+ public double x;
+ public double y;
+
+ Point(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+}
+
+/**
+ * BoundingBox is a simple class that represents a bounding box in 2D space.
+ *
+ * @see Bounding Box
+ * @author Sailok Chinta
+ */
+class BoundingBox {
+ public Point center;
+ public double halfWidth;
+
+ BoundingBox(Point center, double halfWidth) {
+ this.center = center;
+ this.halfWidth = halfWidth;
+ }
+
+ /**
+ * Checks if the point is inside the bounding box
+ *
+ * @param point The point to check
+ * @return true if the point is inside the bounding box, false otherwise
+ */
+ public boolean containsPoint(Point point) {
+ return point.x >= center.x - halfWidth && point.x <= center.x + halfWidth && point.y >= center.y - halfWidth && point.y <= center.y + halfWidth;
+ }
+
+ /**
+ * Checks if the bounding box intersects with the other bounding box
+ *
+ * @param otherBoundingBox The other bounding box
+ * @return true if the bounding box intersects with the other bounding box, false otherwise
+ */
+ public boolean intersectsBoundingBox(BoundingBox otherBoundingBox) {
+ return otherBoundingBox.center.x - otherBoundingBox.halfWidth <= center.x + halfWidth && otherBoundingBox.center.x + otherBoundingBox.halfWidth >= center.x - halfWidth && otherBoundingBox.center.y - otherBoundingBox.halfWidth <= center.y + halfWidth
+ && otherBoundingBox.center.y + otherBoundingBox.halfWidth >= center.y - halfWidth;
+ }
+}
+
+/**
+ * QuadTree is a tree data structure that is used to store spatial information
+ * in an efficient way.
+ *
+ * This implementation is specific to Point QuadTrees
+ *
+ * @see Quad Tree
+ * @author Sailok Chinta
+ */
+public class QuadTree {
+ private final BoundingBox boundary;
+ private final int capacity;
+
+ private List pointList;
+ private boolean divided;
+ private QuadTree northWest;
+ private QuadTree northEast;
+ private QuadTree southWest;
+ private QuadTree southEast;
+
+ public QuadTree(BoundingBox boundary, int capacity) {
+ this.boundary = boundary;
+ this.capacity = capacity;
+
+ this.pointList = new ArrayList<>();
+ this.divided = false;
+ this.northWest = null;
+ this.northEast = null;
+ this.southWest = null;
+ this.southEast = null;
+ }
+
+ /**
+ * Inserts a point into the tree
+ *
+ * @param point The point to insert
+ * @return true if the point is successfully inserted, false otherwise
+ */
+ public boolean insert(Point point) {
+ if (point == null) {
+ return false;
+ }
+
+ // Ignore points that don't belong to this quad tree
+ if (!boundary.containsPoint(point)) {
+ return false;
+ }
+
+ // if the space is not already occupied, add it to the list
+ if (pointList.size() < capacity) {
+ pointList.add(point);
+ return true;
+ }
+
+ // if subdivision hasn't happened, divide the tree
+ if (!divided) {
+ subDivide();
+ }
+
+ // try to add the point in one of the four quadrants
+ if (northWest.insert(point)) {
+ return true;
+ }
+
+ if (northEast.insert(point)) {
+ return true;
+ }
+
+ if (southWest.insert(point)) {
+ return true;
+ }
+
+ if (southEast.insert(point)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Create four children that fully divide this quad into four quads of equal area
+ */
+ private void subDivide() {
+ double quadrantHalfWidth = boundary.halfWidth / 2;
+
+ northWest = new QuadTree(new BoundingBox(new Point(boundary.center.x - quadrantHalfWidth, boundary.center.y + quadrantHalfWidth), quadrantHalfWidth), this.capacity);
+ northEast = new QuadTree(new BoundingBox(new Point(boundary.center.x + quadrantHalfWidth, boundary.center.y + quadrantHalfWidth), quadrantHalfWidth), this.capacity);
+ southWest = new QuadTree(new BoundingBox(new Point(boundary.center.x - quadrantHalfWidth, boundary.center.y - quadrantHalfWidth), quadrantHalfWidth), this.capacity);
+ southEast = new QuadTree(new BoundingBox(new Point(boundary.center.x + quadrantHalfWidth, boundary.center.y - quadrantHalfWidth), quadrantHalfWidth), this.capacity);
+ divided = true;
+ }
+
+ /**
+ * Queries all the points that intersect with the other bounding box
+ *
+ * @param otherBoundingBox The other bounding box
+ * @return List of points that intersect with the other bounding box
+ */
+ public List query(BoundingBox otherBoundingBox) {
+ List points = new ArrayList<>();
+
+ if (!boundary.intersectsBoundingBox(otherBoundingBox)) {
+ return points;
+ }
+
+ // filter the points that intersect with the other bounding box
+ points.addAll(pointList.stream().filter(otherBoundingBox::containsPoint).toList());
+
+ if (divided) {
+ points.addAll(northWest.query(otherBoundingBox));
+ points.addAll(northEast.query(otherBoundingBox));
+ points.addAll(southWest.query(otherBoundingBox));
+ points.addAll(southEast.query(otherBoundingBox));
+ }
+
+ return points;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java
similarity index 50%
rename from src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
rename to src/main/java/com/thealgorithms/datastructures/trees/Trie.java
index a43a454146cb..02f28d4d83ad 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java
@@ -1,5 +1,28 @@
package com.thealgorithms.datastructures.trees;
+import java.util.HashMap;
+
+/**
+ * Represents a Trie Node that stores a character and pointers to its children.
+ * Each node has a hashmap which can point to all possible characters.
+ * Each node also has a boolean value to indicate if it is the end of a word.
+ */
+class TrieNode {
+ char value;
+ HashMap child;
+ boolean end;
+
+ /**
+ * Constructor to initialize a TrieNode with an empty hashmap
+ * and set end to false.
+ */
+ TrieNode(char value) {
+ this.value = value;
+ this.child = new HashMap<>();
+ this.end = false;
+ }
+}
+
/**
* Trie Data structure implementation without any libraries.
*
@@ -14,27 +37,11 @@
* possible character.
*
* @author Dheeraj Kumar Barnwal
+ * @author Sailok Chinta
*/
-public class TrieImp {
- /**
- * Represents a Trie Node that stores a character and pointers to its children.
- * Each node has an array of 26 children (one for each letter from 'a' to 'z').
- */
- public class TrieNode {
-
- TrieNode[] child;
- boolean end;
-
- /**
- * Constructor to initialize a TrieNode with an empty child array
- * and set end to false.
- */
- public TrieNode() {
- child = new TrieNode[26];
- end = false;
- }
- }
+public class Trie {
+ private static final char ROOT_NODE_VALUE = '*';
private final TrieNode root;
@@ -42,8 +49,8 @@ public TrieNode() {
* Constructor to initialize the Trie.
* The root node is created but doesn't represent any character.
*/
- public TrieImp() {
- root = new TrieNode();
+ public Trie() {
+ root = new TrieNode(ROOT_NODE_VALUE);
}
/**
@@ -57,13 +64,15 @@ public TrieImp() {
public void insert(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- TrieNode node = currentNode.child[word.charAt(i) - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
+
if (node == null) {
- node = new TrieNode();
- currentNode.child[word.charAt(i) - 'a'] = node;
+ node = new TrieNode(word.charAt(i));
+ currentNode.child.put(word.charAt(i), node);
}
currentNode = node;
}
+
currentNode.end = true;
}
@@ -80,13 +89,14 @@ public void insert(String word) {
public boolean search(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- char ch = word.charAt(i);
- TrieNode node = currentNode.child[ch - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
+
if (node == null) {
return false;
}
currentNode = node;
}
+
return currentNode.end;
}
@@ -104,40 +114,89 @@ public boolean search(String word) {
public boolean delete(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- char ch = word.charAt(i);
- TrieNode node = currentNode.child[ch - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
if (node == null) {
return false;
}
+
currentNode = node;
}
+
if (currentNode.end) {
currentNode.end = false;
return true;
}
+
return false;
}
/**
- * Helper method to print a string to the console.
+ * Counts the number of words in the trie
+ *
+ * The method traverses the Trie and counts the number of words.
*
- * @param print The string to be printed.
+ * @return count of words
*/
- public static void sop(String print) {
- System.out.println(print);
+ public int countWords() {
+ return countWords(root);
+ }
+
+ private int countWords(TrieNode node) {
+ if (node == null) {
+ return 0;
+ }
+
+ int count = 0;
+ if (node.end) {
+ count++;
+ }
+
+ for (TrieNode child : node.child.values()) {
+ count += countWords(child);
+ }
+
+ return count;
}
/**
- * Validates if a given word contains only lowercase alphabetic characters
- * (a-z).
- *
- * The method uses a regular expression to check if the word matches the pattern
- * of only lowercase letters.
+ * Check if the prefix exists in the trie
*
- * @param word The word to be validated.
- * @return true if the word is valid (only a-z), false otherwise.
+ * @param prefix the prefix to be checked in the Trie
+ * @return true / false depending on the prefix if exists in the Trie
*/
- public static boolean isValid(String word) {
- return word.matches("^[a-z]+$");
+ public boolean startsWithPrefix(String prefix) {
+ TrieNode currentNode = root;
+
+ for (int i = 0; i < prefix.length(); i++) {
+ TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null);
+ if (node == null) {
+ return false;
+ }
+
+ currentNode = node;
+ }
+
+ return true;
+ }
+
+ /**
+ * Count the number of words starting with the given prefix in the trie
+ *
+ * @param prefix the prefix to be checked in the Trie
+ * @return count of words
+ */
+ public int countWordsWithPrefix(String prefix) {
+ TrieNode currentNode = root;
+
+ for (int i = 0; i < prefix.length(); i++) {
+ TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null);
+ if (node == null) {
+ return 0;
+ }
+
+ currentNode = node;
+ }
+
+ return countWords(currentNode);
}
}
diff --git a/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java b/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java
new file mode 100644
index 000000000000..15c8bc33b58f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.divideandconquer;
+
+/**
+ * A utility class for counting the number of inversions in an array.
+ *
+ * An inversion is a pair (i, j) such that i < j and arr[i] > arr[j].
+ * This class implements a divide-and-conquer approach, similar to merge sort,
+ * to count the number of inversions efficiently.
+ *
+ * Time Complexity: O(n log n)
+ * Space Complexity: O(n) (due to temporary arrays during merge step)
+ *
+ *
Applications:
+ * - Used in algorithms related to sorting and permutation analysis.
+ * - Helps in determining how far an array is from being sorted.
+ * - Applicable in bioinformatics and signal processing.
+ *
+ *
This class cannot be instantiated, as it is intended to provide
+ * only static utility methods.
+ *
+ * @author Hardvan
+ */
+public final class CountingInversions {
+ private CountingInversions() {
+ }
+
+ /**
+ * Counts the number of inversions in the given array.
+ *
+ * @param arr The input array of integers.
+ * @return The total number of inversions in the array.
+ */
+ public static int countInversions(int[] arr) {
+ return mergeSortAndCount(arr, 0, arr.length - 1);
+ }
+
+ /**
+ * Recursively divides the array into two halves, sorts them, and counts
+ * the number of inversions. Uses a modified merge sort approach.
+ *
+ * @param arr The input array.
+ * @param left The starting index of the current segment.
+ * @param right The ending index of the current segment.
+ * @return The number of inversions within the segment [left, right].
+ */
+ private static int mergeSortAndCount(int[] arr, int left, int right) {
+ if (left >= right) {
+ return 0;
+ }
+
+ int mid = left + (right - left) / 2;
+ int inversions = 0;
+
+ inversions += mergeSortAndCount(arr, left, mid);
+ inversions += mergeSortAndCount(arr, mid + 1, right);
+ inversions += mergeAndCount(arr, left, mid, right);
+ return inversions;
+ }
+
+ /**
+ * Merges two sorted subarrays and counts the cross-inversions between them.
+ * A cross-inversion occurs when an element from the right subarray is
+ * smaller than an element from the left subarray.
+ *
+ * @param arr The input array.
+ * @param left The starting index of the first subarray.
+ * @param mid The ending index of the first subarray and midpoint of the segment.
+ * @param right The ending index of the second subarray.
+ * @return The number of cross-inversions between the two subarrays.
+ */
+ private static int mergeAndCount(int[] arr, int left, int mid, int right) {
+ int[] leftArr = new int[mid - left + 1];
+ int[] rightArr = new int[right - mid];
+
+ System.arraycopy(arr, left, leftArr, 0, mid - left + 1);
+ System.arraycopy(arr, mid + 1, rightArr, 0, right - mid);
+
+ int i = 0;
+ int j = 0;
+ int k = left;
+ int inversions = 0;
+
+ while (i < leftArr.length && j < rightArr.length) {
+ if (leftArr[i] <= rightArr[j]) {
+ arr[k++] = leftArr[i++];
+ } else {
+ arr[k++] = rightArr[j++];
+ inversions += mid + 1 - left - i;
+ }
+ }
+
+ while (i < leftArr.length) {
+ arr[k++] = leftArr[i++];
+ }
+ while (j < rightArr.length) {
+ arr[k++] = rightArr[j++];
+ }
+
+ return inversions;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java b/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java
new file mode 100644
index 000000000000..d9e51442253c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.divideandconquer;
+
+public final class MedianOfTwoSortedArrays {
+
+ private MedianOfTwoSortedArrays() {
+ }
+
+ /**
+ * Finds the median of two sorted arrays in logarithmic time.
+ *
+ * @param nums1 the first sorted array
+ * @param nums2 the second sorted array
+ * @return the median of the combined sorted array
+ * @throws IllegalArgumentException if the input arrays are not sorted
+ */
+ public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
+ if (nums1.length > nums2.length) {
+ return findMedianSortedArrays(nums2, nums1); // Ensure nums1 is the smaller array
+ }
+
+ int m = nums1.length;
+ int n = nums2.length;
+ int low = 0;
+ int high = m;
+ while (low <= high) {
+ int partition1 = (low + high) / 2; // Partition in the first array
+ int partition2 = (m + n + 1) / 2 - partition1; // Partition in the second array
+
+ int maxLeft1 = (partition1 == 0) ? Integer.MIN_VALUE : nums1[partition1 - 1];
+ int minRight1 = (partition1 == m) ? Integer.MAX_VALUE : nums1[partition1];
+ int maxLeft2 = (partition2 == 0) ? Integer.MIN_VALUE : nums2[partition2 - 1];
+ int minRight2 = (partition2 == n) ? Integer.MAX_VALUE : nums2[partition2];
+
+ // Check if partition is valid
+ if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
+ // If combined array length is odd
+ if (((m + n) & 1) == 1) {
+ return Math.max(maxLeft1, maxLeft2);
+ }
+ // If combined array length is even
+ else {
+ return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2.0;
+ }
+ } else if (maxLeft1 > minRight2) {
+ high = partition1 - 1;
+ } else {
+ low = partition1 + 1;
+ }
+ }
+
+ throw new IllegalArgumentException("Input arrays are not sorted");
+ }
+}
diff --git a/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java b/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java
new file mode 100644
index 000000000000..ff2e1678ab48
--- /dev/null
+++ b/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java
@@ -0,0 +1,99 @@
+package com.thealgorithms.divideandconquer;
+
+/**
+ * This class provides a solution to the Tiling Problem using divide-and-conquer.
+ *
+ * The Tiling Problem involves filling a 2^n x 2^n board with a single missing
+ * square using L-shaped tiles (each tile covers exactly three squares).
+ * The algorithm recursively divides the board into four quadrants, places an
+ * L-shaped tile in the appropriate quadrant, and fills the remaining areas.
+ *
+ *
Applications:
+ * - Used in graphics and image processing.
+ * - Helpful in solving puzzles and tiling problems in competitive programming.
+ *
+ * @author Hardvan
+ */
+public final class TilingProblem {
+ private TilingProblem() {
+ }
+
+ /**
+ * A counter used to label the L-shaped tiles placed on the board.
+ */
+ private static int tile = 1;
+
+ /**
+ * A 2D array representing the board to be tiled.
+ */
+ private static int[][] board;
+
+ /**
+ * Solves the tiling problem for a 2^n x 2^n board with one missing square.
+ *
+ * @param size The size of the board (must be a power of 2).
+ * @param missingRow The row index of the missing square.
+ * @param missingCol The column index of the missing square.
+ * @return A 2D array representing the tiled board with L-shaped tiles.
+ */
+ public static int[][] solveTiling(int size, int missingRow, int missingCol) {
+ board = new int[size][size];
+ fillBoard(size, 0, 0, missingRow, missingCol);
+ return board;
+ }
+
+ /**
+ * Recursively fills the board with L-shaped tiles.
+ *
+ *
The board is divided into four quadrants. Depending on the location of
+ * the missing square, an L-shaped tile is placed at the center of the board
+ * to cover three of the four quadrants. The process is then repeated for
+ * each quadrant until the entire board is filled.
+ *
+ * @param size The current size of the sub-board.
+ * @param row The starting row index of the current sub-board.
+ * @param col The starting column index of the current sub-board.
+ * @param missingRow The row index of the missing square within the board.
+ * @param missingCol The column index of the missing square within the board.
+ */
+ private static void fillBoard(int size, int row, int col, int missingRow, int missingCol) {
+ if (size == 1) {
+ return;
+ }
+
+ int half = size / 2;
+ int t = tile++;
+
+ // Top-left quadrant
+ if (missingRow < row + half && missingCol < col + half) {
+ fillBoard(half, row, col, missingRow, missingCol);
+ } else {
+ board[row + half - 1][col + half - 1] = t;
+ fillBoard(half, row, col, row + half - 1, col + half - 1);
+ }
+
+ // Top-right quadrant
+ if (missingRow < row + half && missingCol >= col + half) {
+ fillBoard(half, row, col + half, missingRow, missingCol);
+ } else {
+ board[row + half - 1][col + half] = t;
+ fillBoard(half, row, col + half, row + half - 1, col + half);
+ }
+
+ // Bottom-left quadrant
+ if (missingRow >= row + half && missingCol < col + half) {
+ fillBoard(half, row + half, col, missingRow, missingCol);
+ } else {
+ board[row + half][col + half - 1] = t;
+ fillBoard(half, row + half, col, row + half, col + half - 1);
+ }
+
+ // Bottom-right quadrant
+ if (missingRow >= row + half && missingCol >= col + half) {
+ fillBoard(half, row + half, col + half, missingRow, missingCol);
+ } else {
+ board[row + half][col + half] = t;
+ fillBoard(half, row + half, col + half, row + half, col + half);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java
new file mode 100644
index 000000000000..60c0fd0a3cde
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java
@@ -0,0 +1,56 @@
+package com.thealgorithms.dynamicprogramming;
+
+/**
+ * A class that provides a solution to the abbreviation problem.
+ *
+ * Problem: Given two strings, `a` and `b`, determine if string `a` can be
+ * transformed into string `b` by performing the following operations:
+ * 1. Capitalize zero or more of `a`'s lowercase letters (i.e., convert them to uppercase).
+ * 2. Delete any of the remaining lowercase letters from `a`.
+ *
+ * The task is to determine whether it is possible to make string `a` equal to string `b`.
+ *
+ * @author Hardvan
+ */
+public final class Abbreviation {
+ private Abbreviation() {
+ }
+
+ /**
+ * Determines if string `a` can be transformed into string `b` by capitalizing
+ * some of its lowercase letters and deleting the rest.
+ *
+ * @param a The input string which may contain both uppercase and lowercase letters.
+ * @param b The target string containing only uppercase letters.
+ * @return {@code true} if string `a` can be transformed into string `b`,
+ * {@code false} otherwise.
+ *
+ * Time Complexity: O(n * m) where n = length of string `a` and m = length of string `b`.
+ * Space Complexity: O(n * m) due to the dynamic programming table.
+ */
+ public static boolean abbr(String a, String b) {
+ int n = a.length();
+ int m = b.length();
+
+ boolean[][] dp = new boolean[n + 1][m + 1];
+
+ dp[0][0] = true;
+
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j <= m; j++) {
+ if (dp[i][j]) {
+ // Case 1: If the current characters match (or can be capitalized to match)
+ if (j < m && Character.toUpperCase(a.charAt(i)) == b.charAt(j)) {
+ dp[i + 1][j + 1] = true;
+ }
+ // Case 2: If the character in `a` is lowercase, we can skip it
+ if (Character.isLowerCase(a.charAt(i))) {
+ dp[i + 1][j] = true;
+ }
+ }
+ }
+ }
+
+ return dp[n][m];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java b/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java
new file mode 100644
index 000000000000..e7712b13a2b7
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java
@@ -0,0 +1,61 @@
+package com.thealgorithms.dynamicprogramming;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides a solution to the "All Construct" problem.
+ *
+ * The problem is to determine all the ways a target string can be constructed
+ * from a given list of substrings. Each substring in the word bank can be used
+ * multiple times, and the order of substrings matters.
+ *
+ * @author Hardvan
+ */
+public final class AllConstruct {
+ private AllConstruct() {
+ }
+
+ /**
+ * Finds all possible ways to construct the target string using substrings
+ * from the given word bank.
+ * Time Complexity: O(n * m * k), where n = length of the target,
+ * m = number of words in wordBank, and k = average length of a word.
+ *
+ * Space Complexity: O(n * m) due to the size of the table storing combinations.
+ *
+ * @param target The target string to construct.
+ * @param wordBank An iterable collection of substrings that can be used to construct the target.
+ * @return A list of lists, where each inner list represents one possible
+ * way of constructing the target string using the given word bank.
+ */
+ public static List> allConstruct(String target, Iterable wordBank) {
+ List>> table = new ArrayList<>(target.length() + 1);
+
+ for (int i = 0; i <= target.length(); i++) {
+ table.add(new ArrayList<>());
+ }
+
+ table.get(0).add(new ArrayList<>());
+
+ for (int i = 0; i <= target.length(); i++) {
+ if (!table.get(i).isEmpty()) {
+ for (String word : wordBank) {
+ if (i + word.length() <= target.length() && target.substring(i, i + word.length()).equals(word)) {
+
+ List> newCombinations = new ArrayList<>();
+ for (List combination : table.get(i)) {
+ List newCombination = new ArrayList<>(combination);
+ newCombination.add(word);
+ newCombinations.add(newCombination);
+ }
+
+ table.get(i + word.length()).addAll(newCombinations);
+ }
+ }
+ }
+ }
+
+ return table.get(target.length());
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java
new file mode 100644
index 000000000000..5a894ca004b7
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java
@@ -0,0 +1,91 @@
+package com.thealgorithms.dynamicprogramming;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The AssignmentUsingBitmask class is used to calculate the total number of ways
+ * tasks can be distributed among people, given specific constraints on who can perform which tasks.
+ * The approach uses bitmasking and dynamic programming to efficiently solve the problem.
+ *
+ * @author Hardvan
+ */
+public final class AssignmentUsingBitmask {
+
+ private final int totalTasks;
+ private final int[][] dp;
+ private final List> task;
+ private final int finalMask;
+
+ /**
+ * Constructor for the AssignmentUsingBitmask class.
+ *
+ * @param taskPerformed a list of lists, where each inner list contains the tasks that a person can perform.
+ * @param total the total number of tasks.
+ */
+ public AssignmentUsingBitmask(List> taskPerformed, int total) {
+ this.totalTasks = total;
+ this.dp = new int[1 << taskPerformed.size()][total + 1];
+ for (int[] row : dp) {
+ Arrays.fill(row, -1);
+ }
+
+ this.task = new ArrayList<>(totalTasks + 1);
+ for (int i = 0; i <= totalTasks; i++) {
+ this.task.add(new ArrayList<>());
+ }
+
+ // Final mask to check if all persons are included
+ this.finalMask = (1 << taskPerformed.size()) - 1;
+
+ // Fill the task list
+ for (int i = 0; i < taskPerformed.size(); i++) {
+ for (int j : taskPerformed.get(i)) {
+ this.task.get(j).add(i);
+ }
+ }
+ }
+
+ /**
+ * Counts the ways to assign tasks until the given task number with the specified mask.
+ *
+ * @param mask the bitmask representing the current state of assignments.
+ * @param taskNo the current task number being processed.
+ * @return the number of ways to assign tasks.
+ */
+ private int countWaysUntil(int mask, int taskNo) {
+ if (mask == finalMask) {
+ return 1;
+ }
+ if (taskNo > totalTasks) {
+ return 0;
+ }
+ if (dp[mask][taskNo] != -1) {
+ return dp[mask][taskNo];
+ }
+
+ int totalWays = countWaysUntil(mask, taskNo + 1);
+
+ // Assign tasks to all possible persons
+ for (int p : task.get(taskNo)) {
+ // If the person is already assigned a task
+ if ((mask & (1 << p)) != 0) {
+ continue;
+ }
+ totalWays += countWaysUntil(mask | (1 << p), taskNo + 1);
+ }
+
+ dp[mask][taskNo] = totalWays;
+ return dp[mask][taskNo];
+ }
+
+ /**
+ * Counts the total number of ways to distribute tasks among persons.
+ *
+ * @return the total number of ways to distribute tasks.
+ */
+ public int countNoOfWays() {
+ return countWaysUntil(0, 1);
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
index 3fa8728930cb..8494492f293f 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java
@@ -52,48 +52,4 @@ public static void boundaryFill(int[][] image, int xCoordinate, int yCoordinate,
boundaryFill(image, xCoordinate - 1, yCoordinate - 1, newColor, boundaryColor);
}
}
-
- /**
- * This method will print the 2D image matrix
- *
- * @param image The image to be printed on the console
- */
- public static void printImageArray(int[][] image) {
- for (int i = 0; i < image.length; i++) {
- for (int j = 0; j < image[0].length; j++) {
- System.out.print(image[i][j] + " ");
- }
-
- System.out.println();
- }
- }
-
- // Driver Program
- public static void main(String[] args) {
- // Input 2D image matrix
- int[][] image = {
- {0, 0, 0, 0, 0, 0, 0},
- {0, 3, 3, 3, 3, 0, 0},
- {0, 3, 0, 0, 3, 0, 0},
- {0, 3, 0, 0, 3, 3, 3},
- {0, 3, 3, 3, 0, 0, 3},
- {0, 0, 0, 3, 0, 0, 3},
- {0, 0, 0, 3, 3, 3, 3},
- };
-
- boundaryFill(image, 2, 2, 5, 3);
-
- /* Output ==>
- * 0 0 0 0 0 0 0
- 0 3 3 3 3 0 0
- 0 3 5 5 3 0 0
- 0 3 5 5 3 3 3
- 0 3 3 3 5 5 3
- 0 0 0 3 5 5 3
- 0 0 0 3 3 3 3
- * */
-
- // print 2D image matrix
- printImageArray(image);
- }
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java
index b433c44b9077..3c1851a8c46c 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java
@@ -1,39 +1,67 @@
package com.thealgorithms.dynamicprogramming;
-/* A Naive recursive implementation
-of 0-1 Knapsack problem */
+/**
+ * A naive recursive implementation of the 0-1 Knapsack problem.
+ *
+ * The 0-1 Knapsack problem is a classic optimization problem where you are
+ * given a set of items, each with a weight and a value, and a knapsack with a
+ * fixed capacity. The goal is to determine the maximum value that can be
+ * obtained by selecting a subset of the items such that the total weight does
+ * not exceed the knapsack's capacity. Each item can either be included (1) or
+ * excluded (0), hence the name "0-1" Knapsack.
+ *
+ * This class provides a brute-force recursive approach to solving the
+ * problem. It evaluates all possible combinations of items to find the optimal
+ * solution, but this approach has exponential time complexity and is not
+ * suitable for large input sizes.
+ *
+ * Time Complexity: O(2^n), where n is the number of items.
+ *
+ * Space Complexity: O(n), due to the recursive function call stack.
+ */
public final class BruteForceKnapsack {
private BruteForceKnapsack() {
}
- // Returns the maximum value that
- // can be put in a knapsack of
- // capacity W
+
+ /**
+ * Solves the 0-1 Knapsack problem using a recursive brute-force approach.
+ *
+ * @param w the total capacity of the knapsack
+ * @param wt an array where wt[i] represents the weight of the i-th item
+ * @param val an array where val[i] represents the value of the i-th item
+ * @param n the number of items available for selection
+ * @return the maximum value that can be obtained with the given capacity
+ *
+ * The function uses recursion to explore all possible subsets of items.
+ * For each item, it has two choices: either include it in the knapsack
+ * (if it fits) or exclude it. It returns the maximum value obtainable
+ * through these two choices.
+ *
+ * Base Cases:
+ *
+ * - If no items are left (n == 0), the maximum value is 0.
+ * - If the knapsack's remaining capacity is 0 (w == 0), no more items can
+ * be included, and the value is 0.
+ *
+ *
+ * Recursive Steps:
+ *
+ * - If the weight of the n-th item exceeds the current capacity, it is
+ * excluded from the solution, and the function proceeds with the remaining
+ * items.
+ * - Otherwise, the function considers two possibilities: include the n-th
+ * item or exclude it, and returns the maximum value of these two scenarios.
+ *
+ */
static int knapSack(int w, int[] wt, int[] val, int n) {
- // Base Case
if (n == 0 || w == 0) {
return 0;
}
- // If weight of the nth item is
- // more than Knapsack capacity W,
- // then this item cannot be included
- // in the optimal solution
if (wt[n - 1] > w) {
return knapSack(w, wt, val, n - 1);
- } // Return the maximum of two cases:
- // (1) nth item included
- // (2) not included
- else {
- return Math.max(val[n - 1] + knapSack(w - wt[n - 1], wt, val, n - 1), knapSack(w, wt, val, n - 1));
+ } else {
+ return Math.max(knapSack(w, wt, val, n - 1), val[n - 1] + knapSack(w - wt[n - 1], wt, val, n - 1));
}
}
-
- // Driver code
- public static void main(String[] args) {
- int[] val = new int[] {60, 100, 120};
- int[] wt = new int[] {10, 20, 30};
- int w = 50;
- int n = val.length;
- System.out.println(knapSack(w, wt, val, n));
- }
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java b/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java
index 12cc29faa923..7edc9603dc8b 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java
@@ -7,15 +7,6 @@ public final class CoinChange {
private CoinChange() {
}
- // Driver Program
- public static void main(String[] args) {
- int amount = 12;
- int[] coins = {2, 4, 5};
-
- System.out.println("Number of combinations of getting change for " + amount + " is: " + change(coins, amount));
- System.out.println("Minimum number of coins required for amount :" + amount + " is: " + minimumCoins(coins, amount));
- }
-
/**
* This method finds the number of combinations of getting change for a
* given amount and change coins
@@ -32,8 +23,6 @@ public static int change(int[] coins, int amount) {
for (int i = coin; i < amount + 1; i++) {
combinations[i] += combinations[i - coin];
}
- // Uncomment the below line to see the state of combinations for each coin
- // printAmount(combinations);
}
return combinations[amount];
@@ -65,16 +54,7 @@ public static int minimumCoins(int[] coins, int amount) {
}
}
}
- // Uncomment the below line to see the state of combinations for each coin
- // printAmount(minimumCoins);
- return minimumCoins[amount];
- }
- // A basic print method which prints all the contents of the array
- public static void printAmount(int[] arr) {
- for (int i = 0; i < arr.length; i++) {
- System.out.print(arr[i] + " ");
- }
- System.out.println();
+ return minimumCoins[amount];
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java b/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java
index e1be3ead5895..c3dc3f32ff7c 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java
@@ -9,6 +9,7 @@
/* Hence, storing the results of the solved sub-problems saves time.
And it can be done using Dynamic Programming(DP).
+// Time Complexity: O(m * n * x) where m is number of faces, n is number of dice and x is given sum.
Following is implementation of Dynamic Programming approach. */
// Code ---->
// Java program to find number of ways to get sum 'x' with 'n'
@@ -43,21 +44,4 @@ public static long findWays(int m, int n, int x) {
return table[n][x];
}
-
- public static void main(String[] args) {
- System.out.println(findWays(4, 2, 1));
- System.out.println(findWays(2, 2, 3));
- System.out.println(findWays(6, 3, 8));
- System.out.println(findWays(4, 2, 5));
- System.out.println(findWays(4, 3, 5));
- }
}
-/*
-OUTPUT:
-0
-2
-21
-4
-6
- */
-// Time Complexity: O(m * n * x) where m is number of faces, n is number of dice and x is given sum.
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
index 5855030fc65c..0d6aff2bbef3 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java
@@ -2,7 +2,6 @@
import java.util.HashMap;
import java.util.Map;
-import java.util.Scanner;
/**
* @author Varun Upadhyay (https://github.com/varunu28)
@@ -11,27 +10,19 @@ public final class Fibonacci {
private Fibonacci() {
}
- private static final Map CACHE = new HashMap<>();
-
- public static void main(String[] args) {
- // Methods all returning [0, 1, 1, 2, 3, 5, ...] for n = [0, 1, 2, 3, 4, 5, ...]
- Scanner sc = new Scanner(System.in);
- int n = sc.nextInt();
-
- System.out.println(fibMemo(n));
- System.out.println(fibBotUp(n));
- System.out.println(fibOptimized(n));
- System.out.println(fibBinet(n));
- sc.close();
- }
+ static final Map CACHE = new HashMap<>();
/**
* This method finds the nth fibonacci number using memoization technique
*
* @param n The input n for which we have to determine the fibonacci number
* Outputs the nth fibonacci number
+ * @throws IllegalArgumentException if n is negative
*/
public static int fibMemo(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input n must be non-negative");
+ }
if (CACHE.containsKey(n)) {
return CACHE.get(n);
}
@@ -52,8 +43,12 @@ public static int fibMemo(int n) {
*
* @param n The input n for which we have to determine the fibonacci number
* Outputs the nth fibonacci number
+ * @throws IllegalArgumentException if n is negative
*/
public static int fibBotUp(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input n must be non-negative");
+ }
Map fib = new HashMap<>();
for (int i = 0; i <= n; i++) {
@@ -80,9 +75,13 @@ public static int fibBotUp(int n) {
* Time Complexity will be O(n)
*
* Whereas , the above functions will take O(n) Space.
+ * @throws IllegalArgumentException if n is negative
* @author Shoaib Rayeen (https://github.com/shoaibrayeen)
*/
public static int fibOptimized(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input n must be non-negative");
+ }
if (n == 0) {
return 0;
}
@@ -105,9 +104,14 @@ public static int fibOptimized(int n) {
* = 1.6180339887... Now, let's look at Binet's formula: Sn = Φⁿ–(– Φ⁻ⁿ)/√5 We first calculate
* the squareRootof5 and phi and store them in variables. Later, we apply Binet's formula to get
* the required term. Time Complexity will be O(1)
+ * @param n The input n for which we have to determine the fibonacci number
+ * Outputs the nth fibonacci number
+ * @throws IllegalArgumentException if n is negative
*/
-
public static int fibBinet(int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Input n must be non-negative");
+ }
double squareRootOf5 = Math.sqrt(5);
double phi = (1 + squareRootOf5) / 2;
return (int) ((Math.pow(phi, n) - Math.pow(-phi, -n)) / squareRootOf5);
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java b/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java
index 905815d10a29..7a0a3da94c1e 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java
@@ -1,36 +1,52 @@
package com.thealgorithms.dynamicprogramming;
/**
- * @author Siddhant Swarup Mallick
- * Program description - To find the maximum subarray sum
+ * This class implements Kadane's Algorithm to find the maximum subarray sum
+ * within a given array of integers. The algorithm efficiently computes the maximum
+ * sum of a contiguous subarray in linear time.
+ *
+ * Author: Siddhant Swarup Mallick
*/
public final class KadaneAlgorithm {
private KadaneAlgorithm() {
}
/**
- * OUTPUT :
- * Input - {89,56,98,123,26,75,12,40,39,68,91}
- * Output: it returns either true or false
- * 1st approach Time Complexity : O(n)
- * Auxiliary Space Complexity : O(1)
+ * Computes the maximum subarray sum using Kadane's Algorithm and checks
+ * if it matches a predicted answer.
+ *
+ * @param a The input array of integers for which the maximum
+ * subarray sum is to be calculated.
+ * @param predictedAnswer The expected maximum subarray sum to be verified
+ * against the computed sum.
+ * @return true if the computed maximum subarray sum equals the predicted
+ * answer, false otherwise.
+ *
+ *
Example:
+ *
+ * Input: {89, 56, 98, 123, 26, 75, 12, 40, 39, 68, 91}
+ * Output: true if the maximum subarray sum is equal to the
+ * predicted answer.
+ *
+ *
+ * Algorithmic Complexity:
+ *
+ * - Time Complexity: O(n) - the algorithm iterates through the array once.
+ * - Auxiliary Space Complexity: O(1) - only a constant amount of additional space is used.
+ *
*/
public static boolean maxSum(int[] a, int predictedAnswer) {
int sum = a[0];
int runningSum = 0;
+
for (int k : a) {
- runningSum = runningSum + k;
- // running sum of all the indexs are stored
+ runningSum += k;
sum = Math.max(sum, runningSum);
- // the max is stored inorder to the get the maximum sum
if (runningSum < 0) {
runningSum = 0;
}
- // if running sum is negative then it is initialized to zero
}
- // for-each loop is used to iterate over the array and find the maximum subarray sum
+
return sum == predictedAnswer;
- // It returns true if sum and predicted answer matches
- // The predicted answer is the answer itself. So it always return true
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java
index 396efb1a7893..3501e302a6ef 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java
@@ -1,5 +1,7 @@
package com.thealgorithms.dynamicprogramming;
+import java.util.Arrays;
+
/**
* Recursive Solution for 0-1 knapsack with memoization
* This method is basically an extension to the recursive approach so that we
@@ -15,10 +17,8 @@ int knapSack(int capacity, int[] weights, int[] profits, int numOfItems) {
int[][] dpTable = new int[numOfItems + 1][capacity + 1];
// Loop to initially fill the table with -1
- for (int i = 0; i < numOfItems + 1; i++) {
- for (int j = 0; j < capacity + 1; j++) {
- dpTable[i][j] = -1;
- }
+ for (int[] table : dpTable) {
+ Arrays.fill(table, -1);
}
return solveKnapsackRecursive(capacity, weights, profits, numOfItems, dpTable);
@@ -38,7 +38,6 @@ int solveKnapsackRecursive(int capacity, int[] weights, int[] profits, int numOf
if (weights[numOfItems - 1] > capacity) {
// Store the value of function call stack in table
dpTable[numOfItems][capacity] = solveKnapsackRecursive(capacity, weights, profits, numOfItems - 1, dpTable);
- return dpTable[numOfItems][capacity];
} else {
// case 1. include the item, if it is less than the capacity
final int includeCurrentItem = profits[numOfItems - 1] + solveKnapsackRecursive(capacity - weights[numOfItems - 1], weights, profits, numOfItems - 1, dpTable);
@@ -48,7 +47,7 @@ int solveKnapsackRecursive(int capacity, int[] weights, int[] profits, int numOf
// Store the value of function call stack in table and return
dpTable[numOfItems][capacity] = Math.max(includeCurrentItem, excludeCurrentItem);
- return dpTable[numOfItems][capacity];
}
+ return dpTable[numOfItems][capacity];
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java
new file mode 100644
index 000000000000..7bc0855e0566
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.dynamicprogramming;
+
+/**
+ * Implementation of the Longest Increasing Subsequence (LIS) problem using
+ * an O(n log n) dynamic programming solution enhanced with binary search.
+ *
+ * @author Vusal Huseynov (https://github.com/huseynovvusal)
+ */
+public final class LongestIncreasingSubsequenceNLogN {
+ private LongestIncreasingSubsequenceNLogN() {
+ }
+
+ /**
+ * Finds the index of the smallest element in the array that is greater than
+ * or equal to the target using binary search. The search is restricted to
+ * the first `size` elements of the array.
+ *
+ * @param arr The array to search in (assumed to be sorted up to `size`).
+ * @param size The number of valid elements in the array.
+ * @param target The target value to find the lower bound for.
+ * @return The index of the lower bound.
+ */
+ private static int lowerBound(int[] arr, int target, int size) {
+ int l = 0;
+ int r = size;
+
+ while (l < r) {
+ int mid = l + (r - l) / 2;
+
+ if (target > arr[mid]) {
+ // Move right if target is greater than mid element
+ l = mid + 1;
+ } else {
+ // Move left if target is less than or equal to mid element
+ r = mid;
+ }
+ }
+
+ // Return the index where the target can be inserted
+ return l;
+ }
+
+ /**
+ * Calculates the length of the Longest Increasing Subsequence (LIS) in the given array.
+ *
+ * @param arr The input array of integers.
+ * @return The length of the LIS.
+ */
+ public static int lengthOfLIS(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ return 0; // Return 0 for empty or null arrays
+ }
+
+ // tails[i] - the smallest end element of an increasing subsequence of length i+1
+ int[] tails = new int[arr.length];
+ // size - the length of the longest increasing subsequence found so far
+ int size = 0;
+
+ for (int x : arr) {
+ // Find the position to replace or extend the subsequence
+ int index = lowerBound(tails, x, size);
+
+ // Update the tails array with the current element
+ tails[index] = x;
+
+ // If the element extends the subsequence, increase the size
+ if (index == size) {
+ size++;
+ }
+ }
+
+ // Return the length of the LIS
+ return size;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java
index 45568d21f295..1edb7207dee2 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java
@@ -2,38 +2,32 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Scanner;
+/**
+ * The MatrixChainMultiplication class provides functionality to compute the
+ * optimal way to multiply a sequence of matrices. The optimal multiplication
+ * order is determined using dynamic programming, which minimizes the total
+ * number of scalar multiplications required.
+ */
public final class MatrixChainMultiplication {
private MatrixChainMultiplication() {
}
- private static final Scanner SCANNER = new Scanner(System.in);
- private static final ArrayList MATRICES = new ArrayList<>();
- private static int size;
+ // Matrices to store minimum multiplication costs and split points
private static int[][] m;
private static int[][] s;
private static int[] p;
- public static void main(String[] args) {
- int count = 1;
- while (true) {
- String[] mSize = input("input size of matrix A(" + count + ") ( ex. 10 20 ) : ");
- int col = Integer.parseInt(mSize[0]);
- if (col == 0) {
- break;
- }
- int row = Integer.parseInt(mSize[1]);
-
- Matrix matrix = new Matrix(count, col, row);
- MATRICES.add(matrix);
- count++;
- }
- for (Matrix m : MATRICES) {
- System.out.format("A(%d) = %2d x %2d%n", m.count(), m.col(), m.row());
- }
-
- size = MATRICES.size();
+ /**
+ * Calculates the optimal order for multiplying a given list of matrices.
+ *
+ * @param matrices an ArrayList of Matrix objects representing the matrices
+ * to be multiplied.
+ * @return a Result object containing the matrices of minimum costs and
+ * optimal splits.
+ */
+ public static Result calculateMatrixChainOrder(ArrayList matrices) {
+ int size = matrices.size();
m = new int[size + 1][size + 1];
s = new int[size + 1][size + 1];
p = new int[size + 1];
@@ -44,51 +38,20 @@ public static void main(String[] args) {
}
for (int i = 0; i < p.length; i++) {
- p[i] = i == 0 ? MATRICES.get(i).col() : MATRICES.get(i - 1).row();
+ p[i] = i == 0 ? matrices.get(i).col() : matrices.get(i - 1).row();
}
- matrixChainOrder();
- for (int i = 0; i < size; i++) {
- System.out.print("-------");
- }
- System.out.println();
- printArray(m);
- for (int i = 0; i < size; i++) {
- System.out.print("-------");
- }
- System.out.println();
- printArray(s);
- for (int i = 0; i < size; i++) {
- System.out.print("-------");
- }
- System.out.println();
-
- System.out.println("Optimal solution : " + m[1][size]);
- System.out.print("Optimal parens : ");
- printOptimalParens(1, size);
- }
-
- private static void printOptimalParens(int i, int j) {
- if (i == j) {
- System.out.print("A" + i);
- } else {
- System.out.print("(");
- printOptimalParens(i, s[i][j]);
- printOptimalParens(s[i][j] + 1, j);
- System.out.print(")");
- }
- }
-
- private static void printArray(int[][] array) {
- for (int i = 1; i < size + 1; i++) {
- for (int j = 1; j < size + 1; j++) {
- System.out.printf("%7d", array[i][j]);
- }
- System.out.println();
- }
+ matrixChainOrder(size);
+ return new Result(m, s);
}
- private static void matrixChainOrder() {
+ /**
+ * A helper method that computes the minimum cost of multiplying
+ * the matrices using dynamic programming.
+ *
+ * @param size the number of matrices in the multiplication sequence.
+ */
+ private static void matrixChainOrder(int size) {
for (int i = 1; i < size + 1; i++) {
m[i][i] = 0;
}
@@ -109,33 +72,92 @@ private static void matrixChainOrder() {
}
}
- private static String[] input(String string) {
- System.out.print(string);
- return (SCANNER.nextLine().split(" "));
- }
-}
-
-class Matrix {
+ /**
+ * The Result class holds the results of the matrix chain multiplication
+ * calculation, including the matrix of minimum costs and split points.
+ */
+ public static class Result {
+ private final int[][] m;
+ private final int[][] s;
+
+ /**
+ * Constructs a Result object with the specified matrices of minimum
+ * costs and split points.
+ *
+ * @param m the matrix of minimum multiplication costs.
+ * @param s the matrix of optimal split points.
+ */
+ public Result(int[][] m, int[][] s) {
+ this.m = m;
+ this.s = s;
+ }
- private final int count;
- private final int col;
- private final int row;
+ /**
+ * Returns the matrix of minimum multiplication costs.
+ *
+ * @return the matrix of minimum multiplication costs.
+ */
+ public int[][] getM() {
+ return m;
+ }
- Matrix(int count, int col, int row) {
- this.count = count;
- this.col = col;
- this.row = row;
+ /**
+ * Returns the matrix of optimal split points.
+ *
+ * @return the matrix of optimal split points.
+ */
+ public int[][] getS() {
+ return s;
+ }
}
- int count() {
- return count;
- }
+ /**
+ * The Matrix class represents a matrix with its dimensions and count.
+ */
+ public static class Matrix {
+ private final int count;
+ private final int col;
+ private final int row;
+
+ /**
+ * Constructs a Matrix object with the specified count, number of columns,
+ * and number of rows.
+ *
+ * @param count the identifier for the matrix.
+ * @param col the number of columns in the matrix.
+ * @param row the number of rows in the matrix.
+ */
+ public Matrix(int count, int col, int row) {
+ this.count = count;
+ this.col = col;
+ this.row = row;
+ }
- int col() {
- return col;
- }
+ /**
+ * Returns the identifier of the matrix.
+ *
+ * @return the identifier of the matrix.
+ */
+ public int count() {
+ return count;
+ }
- int row() {
- return row;
+ /**
+ * Returns the number of columns in the matrix.
+ *
+ * @return the number of columns in the matrix.
+ */
+ public int col() {
+ return col;
+ }
+
+ /**
+ * Returns the number of rows in the matrix.
+ *
+ * @return the number of rows in the matrix.
+ */
+ public int row() {
+ return row;
+ }
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java
index 6c1c4cf54ffc..0c1031c6805c 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java
@@ -1,15 +1,31 @@
package com.thealgorithms.dynamicprogramming;
-// Matrix-chain Multiplication
-// Problem Statement
-// we have given a chain A1,A2,...,Ani of n matrices, where for i = 1,2,...,n,
-// matrix Ai has dimension pi−1 ×pi
-// , fully parenthesize the product A1A2 ···An in a way that
-// minimizes the number of scalar multiplications.
+/**
+ * The MatrixChainRecursiveTopDownMemoisation class implements the matrix-chain
+ * multiplication problem using a top-down recursive approach with memoization.
+ *
+ * Given a chain of matrices A1, A2, ..., An, where matrix Ai has dimensions
+ * pi-1 × pi, this algorithm finds the optimal way to fully parenthesize the
+ * product A1A2...An in a way that minimizes the total number of scalar
+ * multiplications required.
+ *
+ * This implementation uses a memoization technique to store the results of
+ * subproblems, which significantly reduces the number of recursive calls and
+ * improves performance compared to a naive recursive approach.
+ */
public final class MatrixChainRecursiveTopDownMemoisation {
private MatrixChainRecursiveTopDownMemoisation() {
}
+ /**
+ * Calculates the minimum number of scalar multiplications needed to multiply
+ * a chain of matrices.
+ *
+ * @param p an array of integers representing the dimensions of the matrices.
+ * The length of the array is n + 1, where n is the number of matrices.
+ * @return the minimum number of multiplications required to multiply the chain
+ * of matrices.
+ */
static int memoizedMatrixChain(int[] p) {
int n = p.length;
int[][] m = new int[n][n];
@@ -21,6 +37,17 @@ static int memoizedMatrixChain(int[] p) {
return lookupChain(m, p, 1, n - 1);
}
+ /**
+ * A recursive helper method to lookup the minimum number of multiplications
+ * for multiplying matrices from index i to index j.
+ *
+ * @param m the memoization table storing the results of subproblems.
+ * @param p an array of integers representing the dimensions of the matrices.
+ * @param i the starting index of the matrix chain.
+ * @param j the ending index of the matrix chain.
+ * @return the minimum number of multiplications needed to multiply matrices
+ * from i to j.
+ */
static int lookupChain(int[][] m, int[] p, int i, int j) {
if (i == j) {
m[i][j] = 0;
@@ -38,11 +65,4 @@ static int lookupChain(int[][] m, int[] p, int i, int j) {
}
return m[i][j];
}
-
- // in this code we are taking the example of 4 matrixes whose orders are 1x2,2x3,3x4,4x5
- // respectively output should be Minimum number of multiplications is 38
- public static void main(String[] args) {
- int[] arr = {1, 2, 3, 4, 5};
- System.out.println("Minimum number of multiplications is " + memoizedMatrixChain(arr));
- }
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java
new file mode 100644
index 000000000000..49af3ae3db88
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java
@@ -0,0 +1,95 @@
+package com.thealgorithms.dynamicprogramming;
+
+/**
+ * Class to find the maximum sum of non-adjacent elements in an array. This
+ * class contains two approaches: one with O(n) space complexity and another
+ * with O(1) space optimization. For more information, refer to
+ * https://takeuforward.org/data-structure/maximum-sum-of-non-adjacent-elements-dp-5/
+ */
+final class MaximumSumOfNonAdjacentElements {
+
+ private MaximumSumOfNonAdjacentElements() {
+ }
+
+ /**
+ * Approach 1: Uses a dynamic programming array to store the maximum sum at
+ * each index. Time Complexity: O(n) - where n is the length of the input
+ * array. Space Complexity: O(n) - due to the additional dp array.
+ * @param arr The input array of integers.
+ * @return The maximum sum of non-adjacent elements.
+ */
+ public static int getMaxSumApproach1(int[] arr) {
+ if (arr.length == 0) {
+ return 0; // Check for empty array
+ }
+
+ int n = arr.length;
+ int[] dp = new int[n];
+
+ // Base case: Maximum sum if only one element is present.
+ dp[0] = arr[0];
+
+ for (int ind = 1; ind < n; ind++) {
+
+ // Case 1: Do not take the current element, carry forward the previous max
+ // sum.
+ int notTake = dp[ind - 1];
+
+ // Case 2: Take the current element, add it to the max sum up to two
+ // indices before.
+ int take = arr[ind];
+ if (ind > 1) {
+ take += dp[ind - 2];
+ }
+
+ // Store the maximum of both choices in the dp array.
+ dp[ind] = Math.max(take, notTake);
+ }
+
+ return dp[n - 1];
+ }
+
+ /**
+ * Approach 2: Optimized space complexity approach using two variables instead
+ * of an array. Time Complexity: O(n) - where n is the length of the input
+ * array. Space Complexity: O(1) - as it only uses constant space for two
+ * variables.
+ * @param arr The input array of integers.
+ * @return The maximum sum of non-adjacent elements.
+ */
+ public static int getMaxSumApproach2(int[] arr) {
+ if (arr.length == 0) {
+ return 0; // Check for empty array
+ }
+
+ int n = arr.length;
+
+ // Two variables to keep track of previous two results:
+ // prev1 = max sum up to the last element (n-1)
+ // prev2 = max sum up to the element before last (n-2)
+
+ int prev1 = arr[0]; // Base case: Maximum sum for the first element.
+ int prev2 = 0;
+
+ for (int ind = 1; ind < n; ind++) {
+ // Case 1: Do not take the current element, keep the last max sum.
+ int notTake = prev1;
+
+ // Case 2: Take the current element and add it to the result from two
+ // steps back.
+ int take = arr[ind];
+ if (ind > 1) {
+ take += prev2;
+ }
+
+ // Calculate the current maximum sum and update previous values.
+ int current = Math.max(take, notTake);
+
+ // Shift prev1 and prev2 for the next iteration.
+ prev2 = prev1;
+ prev1 = current;
+ }
+
+ return prev1;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java b/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java
index d15ea1f78a78..3db40148f1c2 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java
@@ -1,25 +1,48 @@
package com.thealgorithms.dynamicprogramming;
/**
+ * The NewManShanksPrime class provides a method to determine whether the nth
+ * New Man Shanks prime matches an expected answer.
+ *
+ * This is based on the New Man Shanks prime sequence defined by the recurrence
+ * relation:
+ *
+ *
+ * a(n) = 2 * a(n-1) + a(n-2) for n >= 2
+ * a(0) = 1
+ * a(1) = 1
+ *
+ *
+ * For more information on New Man Shanks primes, please refer to the
+ *
+ * Wikipedia article.
+ *
+ * Note: The class is designed to be non-instantiable.
+ *
* @author Siddhant Swarup Mallick
- * Program description - To find the New Man Shanks Prime.
- * Wikipedia
*/
public final class NewManShanksPrime {
private NewManShanksPrime() {
}
+ /**
+ * Calculates the nth New Man Shanks prime and checks if it equals the
+ * expected answer.
+ *
+ * @param n the index of the New Man Shanks prime to calculate (0-based).
+ * @param expectedAnswer the expected value of the nth New Man Shanks prime.
+ * @return true if the calculated nth New Man Shanks prime matches the
+ * expected answer; false otherwise.
+ */
public static boolean nthManShanksPrime(int n, int expectedAnswer) {
int[] a = new int[n + 1];
- // array of n+1 size is initialized
a[0] = 1;
a[1] = 1;
- // The 0th and 1st index position values are fixed. They are initialized as 1
+
for (int i = 2; i <= n; i++) {
a[i] = 2 * a[i - 1] + a[i - 2];
}
- // The loop is continued till n
+
return a[n] == expectedAnswer;
- // returns true if calculated answer matches with expected answer
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java
index f56fc4ff5641..6d33826b6b17 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java
@@ -15,9 +15,17 @@ private RodCutting() {
* @param price An array representing the prices of different pieces, where price[i-1]
* represents the price of a piece of length i.
* @param n The length of the rod to be cut.
+ * @throws IllegalArgumentException if the price array is null or empty, or if n is less than 0.
* @return The maximum obtainable value.
*/
public static int cutRod(int[] price, int n) {
+ if (price == null || price.length == 0) {
+ throw new IllegalArgumentException("Price array cannot be null or empty.");
+ }
+ if (n < 0) {
+ throw new IllegalArgumentException("Rod length cannot be negative.");
+ }
+
// Create an array to store the maximum obtainable values for each rod length.
int[] val = new int[n + 1];
val[0] = 0;
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java
index 3dd41d2fdc0f..da8667afd0ce 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java
@@ -9,28 +9,25 @@ private SubsetSum() {
*
* @param arr the array containing integers.
* @param sum the target sum of the subset.
- * @return {@code true} if a subset exists that sums to the given value, otherwise {@code false}.
+ * @return {@code true} if a subset exists that sums to the given value,
+ * otherwise {@code false}.
*/
public static boolean subsetSum(int[] arr, int sum) {
int n = arr.length;
- boolean[][] isSum = new boolean[n + 1][sum + 1];
- // Initialize the first column to true since a sum of 0 can always be achieved with an empty subset.
- for (int i = 0; i <= n; i++) {
- isSum[i][0] = true;
- }
+ // Initialize a single array to store the possible sums
+ boolean[] isSum = new boolean[sum + 1];
+
+ // Mark isSum[0] = true since a sum of 0 is always possible with 0 elements
+ isSum[0] = true;
- // Fill the subset sum matrix
- for (int i = 1; i <= n; i++) {
- for (int j = 1; j <= sum; j++) {
- if (arr[i - 1] <= j) {
- isSum[i][j] = isSum[i - 1][j] || isSum[i - 1][j - arr[i - 1]];
- } else {
- isSum[i][j] = isSum[i - 1][j];
- }
+ // Iterate through each Element in the array
+ for (int i = 0; i < n; i++) {
+ // Traverse the isSum array backwards to prevent overwriting values
+ for (int j = sum; j >= arr[i]; j--) {
+ isSum[j] = isSum[j] || isSum[j - arr[i]];
}
}
-
- return isSum[n][sum];
+ return isSum[sum];
}
}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java
new file mode 100644
index 000000000000..946da46cb292
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java
@@ -0,0 +1,35 @@
+package com.thealgorithms.dynamicprogramming;
+/*
+The Sum of Subset problem determines whether a subset of elements from a
+given array sums up to a specific target value.
+*/
+public final class SubsetSumSpaceOptimized {
+ private SubsetSumSpaceOptimized() {
+ }
+ /**
+ * This method checks whether the subset of an array
+ * contains a given sum or not. This is an space
+ * optimized solution using 1D boolean array
+ * Time Complexity: O(n * sum), Space complexity: O(sum)
+ *
+ * @param arr An array containing integers
+ * @param sum The target sum of the subset
+ * @return True or False
+ */
+ public static boolean isSubsetSum(int[] arr, int sum) {
+ int n = arr.length;
+ // Declare the boolean array with size sum + 1
+ boolean[] dp = new boolean[sum + 1];
+
+ // Initialize the first element as true
+ dp[0] = true;
+
+ // Find the subset sum using 1D array
+ for (int i = 0; i < n; i++) {
+ for (int j = sum; j >= arr[i]; j--) {
+ dp[j] = dp[j] || dp[j - arr[i]];
+ }
+ }
+ return dp[sum];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java
new file mode 100644
index 000000000000..9fd82ccaf078
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java
@@ -0,0 +1,78 @@
+package com.thealgorithms.dynamicprogramming;
+
+import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph;
+
+/**
+ * This class implements the algorithm for calculating the maximum weighted matching in a tree.
+ * The tree is represented as an undirected graph with weighted edges.
+ *
+ * Problem Description:
+ * Given an undirected tree G = (V, E) with edge weights γ: E → N and a root r ∈ V,
+ * the goal is to find a maximum weight matching M ⊆ E such that no two edges in M
+ * share a common vertex. The sum of the weights of the edges in M, ∑ e∈M γ(e), should be maximized.
+ * For more Information: Matching (graph theory)
+ *
+ * @author Deniz Altunkapan
+ */
+public class TreeMatching {
+
+ private UndirectedAdjacencyListGraph graph;
+ private int[][] dp;
+
+ /**
+ * Constructor that initializes the graph and the DP table.
+ *
+ * @param graph The graph that represents the tree and is used for the matching algorithm.
+ */
+ public TreeMatching(UndirectedAdjacencyListGraph graph) {
+ this.graph = graph;
+ this.dp = new int[graph.size()][2];
+ }
+
+ /**
+ * Calculates the maximum weighted matching for the tree, starting from the given root node.
+ *
+ * @param root The index of the root node of the tree.
+ * @param parent The index of the parent node (used for recursion).
+ * @return The maximum weighted matching for the tree, starting from the root node.
+ *
+ */
+ public int getMaxMatching(int root, int parent) {
+ if (root < 0 || root >= graph.size()) {
+ throw new IllegalArgumentException("Invalid root: " + root);
+ }
+ maxMatching(root, parent);
+ return Math.max(dp[root][0], dp[root][1]);
+ }
+
+ /**
+ * Recursively computes the maximum weighted matching for a node, assuming that the node
+ * can either be included or excluded from the matching.
+ *
+ * @param node The index of the current node for which the matching is calculated.
+ * @param parent The index of the parent node (to avoid revisiting the parent node during recursion).
+ */
+ private void maxMatching(int node, int parent) {
+ dp[node][0] = 0;
+ dp[node][1] = 0;
+
+ int sumWithoutEdge = 0;
+ for (int adjNode : graph.getNeighbors(node)) {
+ if (adjNode == parent) {
+ continue;
+ }
+ maxMatching(adjNode, node);
+ sumWithoutEdge += Math.max(dp[adjNode][0], dp[adjNode][1]);
+ }
+
+ dp[node][0] = sumWithoutEdge;
+
+ for (int adjNode : graph.getNeighbors(node)) {
+ if (adjNode == parent) {
+ continue;
+ }
+ int weight = graph.getEdgeWeight(node, adjNode);
+ dp[node][1] = Math.max(dp[node][1], sumWithoutEdge - Math.max(dp[adjNode][0], dp[adjNode][1]) + dp[adjNode][0] + weight);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java
new file mode 100755
index 000000000000..8c7ea6179e3f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java
@@ -0,0 +1,98 @@
+package com.thealgorithms.dynamicprogramming;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utility class to find the number of unique subsequences that can be
+ * produced from a given string.
+ *
+ * This class contains static methods to compute the unique subsequence count
+ * using dynamic programming and recursion. It ensures that duplicate characters
+ * are not counted multiple times in the subsequences.
+ *
+ * Author: https://github.com/Tuhinm2002
+ */
+public final class UniqueSubsequencesCount {
+
+ /**
+ * Private constructor to prevent instantiation of this utility class.
+ * This class should only be used in a static context.
+ *
+ * @throws UnsupportedOperationException if attempted to instantiate.
+ */
+ private UniqueSubsequencesCount() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Finds the number of unique subsequences that can be generated from
+ * the given string.
+ *
+ * This method initializes a dynamic programming (DP) array and invokes
+ * the recursive helper function to compute the subsequence count.
+ *
+ * @param str the input string from which subsequences are generated
+ * @return the total count of unique subsequences
+ */
+ public static int countSubseq(String str) {
+
+ // DP array initialized to store intermediate results
+ int[] dp = new int[str.length() + 1];
+ Arrays.fill(dp, -1);
+
+ // Calls the recursive function to compute the result
+ return countSubsequences(str, 0, dp);
+ }
+
+ /**
+ * Recursive helper function to count the number of unique subsequences
+ * starting from the given index.
+ *
+ * Uses a HashSet to avoid counting duplicate characters within
+ * a single subsequence.
+ *
+ * @param st the input string
+ * @param idx the current index from which to calculate subsequences
+ * @param dp dynamic programming array used to memoize results
+ * @return the total number of unique subsequences starting from the
+ * current index
+ */
+ public static int countSubsequences(String st, int idx, int[] dp) {
+
+ // Base case: when index exceeds the string length
+ if (idx >= st.length()) {
+ return 0;
+ }
+
+ // If result is already calculated, return the memoized value
+ if (dp[idx] != -1) {
+ return dp[idx];
+ }
+
+ // Set to store characters to avoid duplicates
+ Set set = new HashSet<>();
+
+ int res = 0;
+
+ // Iterate over the string starting from current index
+ for (int j = idx; j < st.length(); j++) {
+
+ // If character is already in the set, skip it
+ if (set.contains(st.charAt(j))) {
+ continue;
+ }
+
+ // Add character to set and recursively calculate subsequences
+ set.add(st.charAt(j));
+
+ // 1 for the current subsequence + recursive call for the rest of the string
+ res = 1 + countSubsequences(st, j + 1, dp) + res;
+ }
+
+ // Memoize the result
+ dp[idx] = res;
+ return dp[idx];
+ }
+}
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java
index 0f5359f4d95e..022b43184a88 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java
@@ -1,21 +1,40 @@
package com.thealgorithms.dynamicprogramming;
/**
- * Imagine you have a collection of N wines placed next to each other on the
- * shelf. The price of ith wine is pi(Prices of different wines are different).
- * Because wine gets better every year supposing today is year 1, on year y the
- * price would be y*pi i.e y times the value of the initial year. You want to
- * sell all wines but you have to sell one wine per year. One more constraint on
- * each year you are allowed to sell either leftmost or rightmost wine on the
- * shelf. You are not allowed to reorder. You have to find the maximum profit
+ * The WineProblem class provides a solution to the wine selling problem.
+ * Given a collection of N wines with different prices, the objective is to maximize profit by selling
+ * one wine each year, considering the constraint that only the leftmost or rightmost wine can be sold
+ * at any given time.
*
+ * The price of the ith wine is pi, and the selling price increases by a factor of the year in which
+ * it is sold. This class implements three approaches to solve the problem:
+ *
+ * 1. **Recursion**: A straightforward recursive method that computes the maximum profit.
+ * - Time Complexity: O(2^N)
+ * - Space Complexity: O(N) due to recursive calls.
+ *
+ * 2. **Top-Down Dynamic Programming (Memoization)**: This approach caches the results of subproblems
+ * to avoid redundant computations.
+ * - Time Complexity: O(N^2)
+ * - Space Complexity: O(N^2) for the storage of results and O(N) for recursion stack.
+ *
+ * 3. **Bottom-Up Dynamic Programming (Tabulation)**: This method builds a table iteratively to
+ * compute the maximum profit for all possible subproblems.
+ * - Time Complexity: O(N^2)
+ * - Space Complexity: O(N^2) for the table.
*/
public final class WineProblem {
private WineProblem() {
}
- // Method 1: Using Recursion
- // Time Complexity=0(2^N) Space Complexity=Recursion extra space
+ /**
+ * Calculate maximum profit using recursion.
+ *
+ * @param arr Array of wine prices.
+ * @param si Start index of the wine to consider.
+ * @param ei End index of the wine to consider.
+ * @return Maximum profit obtainable by selling the wines.
+ */
public static int wpRecursion(int[] arr, int si, int ei) {
int n = arr.length;
int year = (n - (ei - si + 1)) + 1;
@@ -29,8 +48,15 @@ public static int wpRecursion(int[] arr, int si, int ei) {
return Math.max(start, end);
}
- // Method 2: Top-Down DP(Memoization)
- // Time Complexity=0(N*N) Space Complexity=0(N*N)+Recursion extra space
+ /**
+ * Calculate maximum profit using top-down dynamic programming with memoization.
+ *
+ * @param arr Array of wine prices.
+ * @param si Start index of the wine to consider.
+ * @param ei End index of the wine to consider.
+ * @param strg 2D array to store results of subproblems.
+ * @return Maximum profit obtainable by selling the wines.
+ */
public static int wptd(int[] arr, int si, int ei, int[][] strg) {
int n = arr.length;
int year = (n - (ei - si + 1)) + 1;
@@ -45,15 +71,22 @@ public static int wptd(int[] arr, int si, int ei, int[][] strg) {
int end = wptd(arr, si, ei - 1, strg) + arr[ei] * year;
int ans = Math.max(start, end);
-
strg[si][ei] = ans;
return ans;
}
- // Method 3: Bottom-Up DP(Tabulation)
- // Time Complexity=0(N*N/2)->0(N*N) Space Complexity=0(N*N)
+ /**
+ * Calculate maximum profit using bottom-up dynamic programming with tabulation.
+ *
+ * @param arr Array of wine prices.
+ * @throws IllegalArgumentException if the input array is null or empty.
+ * @return Maximum profit obtainable by selling the wines.
+ */
public static int wpbu(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ throw new IllegalArgumentException("Input array cannot be null or empty.");
+ }
int n = arr.length;
int[][] strg = new int[n][n];
@@ -73,13 +106,4 @@ public static int wpbu(int[] arr) {
}
return strg[0][n - 1];
}
-
- public static void main(String[] args) {
- int[] arr = {2, 3, 5, 1, 4};
- System.out.println("Method 1: " + wpRecursion(arr, 0, arr.length - 1));
- System.out.println("Method 2: " + wptd(arr, 0, arr.length - 1, new int[arr.length][arr.length]));
- System.out.println("Method 3: " + wpbu(arr));
- }
}
-// Memoization vs Tabulation : https://www.geeksforgeeks.org/tabulation-vs-memoization/
-// Question Link : https://www.geeksforgeeks.org/maximum-profit-sale-wines/
diff --git a/src/main/java/com/thealgorithms/geometry/BresenhamLine.java b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java
new file mode 100644
index 000000000000..51d9930c0250
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/BresenhamLine.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.geometry;
+
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@code BresenhamLine} class implements the Bresenham's line algorithm,
+ * which is an efficient way to determine the points of a straight line
+ * between two given points in a 2D space.
+ *
+ * This algorithm uses integer arithmetic to calculate the points,
+ * making it suitable for rasterization in computer graphics.
+ *
+ * For more information, please visit {@link https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm}
+ */
+public final class BresenhamLine {
+
+ private BresenhamLine() {
+ // Private constructor to prevent instantiation.
+ }
+
+ /**
+ * Finds the list of points that form a straight line between two endpoints.
+ *
+ * @param x0 the x-coordinate of the starting point
+ * @param y0 the y-coordinate of the starting point
+ * @param x1 the x-coordinate of the ending point
+ * @param y1 the y-coordinate of the ending point
+ * @return a {@code List} containing all points on the line
+ */
+ public static List findLine(int x0, int y0, int x1, int y1) {
+ List line = new ArrayList<>();
+
+ // Calculate differences and steps for each axis
+ int dx = Math.abs(x1 - x0); // Change in x
+ int dy = Math.abs(y1 - y0); // Change in y
+ int sx = (x0 < x1) ? 1 : -1; // Step in x direction
+ int sy = (y0 < y1) ? 1 : -1; // Step in y direction
+ int err = dx - dy; // Initial error term
+
+ // Loop until we reach the endpoint
+ while (true) {
+ line.add(new Point(x0, y0)); // Add current point to the line
+
+ // Check if we've reached the endpoint
+ if (x0 == x1 && y0 == y1) {
+ break; // Exit loop if endpoint is reached
+ }
+
+ // Calculate error term doubled for decision making
+ final int e2 = err * 2;
+
+ // Adjust x coordinate if necessary
+ if (e2 > -dy) {
+ err -= dy; // Update error term
+ x0 += sx; // Move to next point in x direction
+ }
+
+ // Adjust y coordinate if necessary
+ if (e2 < dx) {
+ err += dx; // Update error term
+ y0 += sy; // Move to next point in y direction
+ }
+ }
+
+ return line; // Return the list of points forming the line
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/ConvexHull.java b/src/main/java/com/thealgorithms/geometry/ConvexHull.java
new file mode 100644
index 000000000000..17f400854c64
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/ConvexHull.java
@@ -0,0 +1,117 @@
+package com.thealgorithms.geometry;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * A class providing algorithms to compute the convex hull of a set of points
+ * using brute-force and recursive methods.
+ *
+ * Convex hull: The smallest convex polygon that contains all the given points.
+ *
+ * Algorithms provided:
+ * 1. Brute-Force Method
+ * 2. Recursive (Divide-and-Conquer) Method
+ *
+ * @author Hardvan
+ */
+public final class ConvexHull {
+ private ConvexHull() {
+ }
+
+ private static boolean checkPointOrientation(Point i, Point j, Point k) {
+ int detK = Point.orientation(i, j, k);
+ if (detK > 0) {
+ return true; // pointsLeftOfIJ
+ } else if (detK < 0) {
+ return false; // pointsRightOfIJ
+ } else {
+ return k.compareTo(i) >= 0 && k.compareTo(j) <= 0;
+ }
+ }
+
+ public static List convexHullBruteForce(List points) {
+ Set convexSet = new TreeSet<>(Comparator.naturalOrder());
+
+ for (int i = 0; i < points.size() - 1; i++) {
+ for (int j = i + 1; j < points.size(); j++) {
+ boolean allPointsOnOneSide = true;
+ boolean leftSide = checkPointOrientation(points.get(i), points.get(j), points.get((i + 1) % points.size()));
+
+ for (int k = 0; k < points.size(); k++) {
+ if (k != i && k != j && checkPointOrientation(points.get(i), points.get(j), points.get(k)) != leftSide) {
+ allPointsOnOneSide = false;
+ break;
+ }
+ }
+
+ if (allPointsOnOneSide) {
+ convexSet.add(points.get(i));
+ convexSet.add(points.get(j));
+ }
+ }
+ }
+
+ return new ArrayList<>(convexSet);
+ }
+
+ public static List convexHullRecursive(List points) {
+ Collections.sort(points);
+ Set convexSet = new HashSet<>();
+ Point leftMostPoint = points.get(0);
+ Point rightMostPoint = points.get(points.size() - 1);
+
+ convexSet.add(leftMostPoint);
+ convexSet.add(rightMostPoint);
+
+ List upperHull = new ArrayList<>();
+ List lowerHull = new ArrayList<>();
+
+ for (int i = 1; i < points.size() - 1; i++) {
+ int det = Point.orientation(leftMostPoint, rightMostPoint, points.get(i));
+ if (det > 0) {
+ upperHull.add(points.get(i));
+ } else if (det < 0) {
+ lowerHull.add(points.get(i));
+ }
+ }
+
+ constructHull(upperHull, leftMostPoint, rightMostPoint, convexSet);
+ constructHull(lowerHull, rightMostPoint, leftMostPoint, convexSet);
+
+ List result = new ArrayList<>(convexSet);
+ Collections.sort(result);
+ return result;
+ }
+
+ private static void constructHull(Collection points, Point left, Point right, Set convexSet) {
+ if (!points.isEmpty()) {
+ Point extremePoint = null;
+ int extremePointDistance = Integer.MIN_VALUE;
+ List candidatePoints = new ArrayList<>();
+
+ for (Point p : points) {
+ int det = Point.orientation(left, right, p);
+ if (det > 0) {
+ candidatePoints.add(p);
+ if (det > extremePointDistance) {
+ extremePointDistance = det;
+ extremePoint = p;
+ }
+ }
+ }
+
+ if (extremePoint != null) {
+ constructHull(candidatePoints, left, extremePoint, convexSet);
+ convexSet.add(extremePoint);
+ constructHull(candidatePoints, extremePoint, right, convexSet);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/GrahamScan.java b/src/main/java/com/thealgorithms/geometry/GrahamScan.java
index 1a36137895e0..1a373cf315a2 100644
--- a/src/main/java/com/thealgorithms/geometry/GrahamScan.java
+++ b/src/main/java/com/thealgorithms/geometry/GrahamScan.java
@@ -2,7 +2,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Comparator;
import java.util.Stack;
/**
@@ -66,93 +65,4 @@ public GrahamScan(Point[] points) {
public Iterable hull() {
return new ArrayList<>(hull);
}
-
- public record Point(int x, int y) implements Comparable {
-
- /**
- * Default constructor
- * @param x x-coordinate
- * @param y y-coordinate
- */
- public Point {
- }
-
- /**
- * @return the x-coordinate
- */
- @Override
- public int x() {
- return x;
- }
-
- /**
- * @return the y-coordinate
- */
- @Override
- public int y() {
- return y;
- }
-
- /**
- * Determines the orientation of the triplet (a, b, c).
- *
- * @param a The first point
- * @param b The second point
- * @param c The third point
- * @return -1 if (a, b, c) is clockwise, 0 if collinear, +1 if counterclockwise
- */
- public static int orientation(Point a, Point b, Point c) {
- int val = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
- return Integer.compare(val, 0);
- }
-
- /**
- * Compares this point with another point.
- *
- * @param p2 The point to compare to
- * @return A positive integer if this point is greater, a negative integer if less, or 0 if equal
- */
- @Override
- public int compareTo(Point p2) {
- int cmpY = Integer.compare(this.y, p2.y);
- return cmpY != 0 ? cmpY : Integer.compare(this.x, p2.x);
- }
-
- /**
- * Returns a comparator to sort points by their polar order relative to this point.
- *
- * @return A polar order comparator
- */
- public Comparator polarOrder() {
- return new PolarOrder();
- }
-
- private final class PolarOrder implements Comparator {
- @Override
- public int compare(Point p1, Point p2) {
- int dx1 = p1.x - x;
- int dy1 = p1.y - y;
- int dx2 = p2.x - x;
- int dy2 = p2.y - y;
-
- if (dy1 >= 0 && dy2 < 0) {
- return -1; // p1 above p2
- } else if (dy2 >= 0 && dy1 < 0) {
- return 1; // p1 below p2
- } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal
- return Integer.compare(dx2, dx1);
- } else {
- return -orientation(Point.this, p1, p2); // Compare orientation
- }
- }
- }
-
- /**
- * @return A string representation of this point in the format (x, y)
- */
- @Override
- public String toString() {
- return String.format("(%d, %d)", x, y);
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/geometry/MidpointCircle.java b/src/main/java/com/thealgorithms/geometry/MidpointCircle.java
new file mode 100644
index 000000000000..803e8bb42b53
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/MidpointCircle.java
@@ -0,0 +1,85 @@
+package com.thealgorithms.geometry;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Class to represent the Midpoint Circle Algorithm.
+ * This algorithm calculates points on the circumference of a circle
+ * using integer arithmetic for efficient computation.
+ */
+public final class MidpointCircle {
+
+ private MidpointCircle() {
+ // Private Constructor to prevent instantiation.
+ }
+
+ /**
+ * Generates points on the circumference of a circle using the midpoint circle algorithm.
+ *
+ * @param centerX The x-coordinate of the circle's center.
+ * @param centerY The y-coordinate of the circle's center.
+ * @param radius The radius of the circle.
+ * @return A list of points on the circle, each represented as an int[] with 2 elements [x, y].
+ */
+ public static List generateCirclePoints(int centerX, int centerY, int radius) {
+ List points = new ArrayList<>();
+
+ // Special case for radius 0, only the center point should be added.
+ if (radius == 0) {
+ points.add(new int[] {centerX, centerY});
+ return points;
+ }
+
+ // Start at (radius, 0)
+ int x = radius;
+ int y = 0;
+
+ // Decision parameter
+ int p = 1 - radius;
+
+ // Add the initial points in all octants
+ addSymmetricPoints(points, centerX, centerY, x, y);
+
+ // Iterate while x > y
+ while (x > y) {
+ y++;
+
+ if (p <= 0) {
+ // Midpoint is inside or on the circle
+ p = p + 2 * y + 1;
+ } else {
+ // Midpoint is outside the circle
+ x--;
+ p = p + 2 * y - 2 * x + 1;
+ }
+
+ // Add points for this (x, y)
+ addSymmetricPoints(points, centerX, centerY, x, y);
+ }
+
+ return points;
+ }
+
+ /**
+ * Adds the symmetric points in all octants of the circle based on the current x and y values.
+ *
+ * @param points The list to which symmetric points will be added.
+ * @param centerX The x-coordinate of the circle's center.
+ * @param centerY The y-coordinate of the circle's center.
+ * @param x The current x-coordinate on the circumference.
+ * @param y The current y-coordinate on the circumference.
+ */
+ private static void addSymmetricPoints(Collection points, int centerX, int centerY, int x, int y) {
+ // Octant symmetry points
+ points.add(new int[] {centerX + x, centerY + y});
+ points.add(new int[] {centerX - x, centerY + y});
+ points.add(new int[] {centerX + x, centerY - y});
+ points.add(new int[] {centerX - x, centerY - y});
+ points.add(new int[] {centerX + y, centerY + x});
+ points.add(new int[] {centerX - y, centerY + x});
+ points.add(new int[] {centerX + y, centerY - x});
+ points.add(new int[] {centerX - y, centerY - x});
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java b/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java
new file mode 100644
index 000000000000..fbd53c679960
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java
@@ -0,0 +1,131 @@
+package com.thealgorithms.geometry;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The MidpointEllipse class implements the Midpoint Ellipse Drawing Algorithm.
+ * This algorithm efficiently computes the points on an ellipse by dividing it into two regions
+ * and using decision parameters to determine the next point to plot.
+ */
+public final class MidpointEllipse {
+
+ private MidpointEllipse() {
+ // Private constructor to prevent instantiation
+ }
+
+ /**
+ * Draws an ellipse using the Midpoint Ellipse Algorithm.
+ *
+ * @param centerX the x-coordinate of the center of the ellipse
+ * @param centerY the y-coordinate of the center of the ellipse
+ * @param a the length of the semi-major axis (horizontal radius)
+ * @param b the length of the semi-minor axis (vertical radius)
+ * @return a list of points (represented as int arrays) that form the ellipse
+ */
+ public static List drawEllipse(int centerX, int centerY, int a, int b) {
+ List points = new ArrayList<>();
+
+ // Handle degenerate cases with early returns
+ if (a == 0 && b == 0) {
+ points.add(new int[] {centerX, centerY}); // Only the center point
+ return points;
+ }
+
+ if (a == 0) {
+ // Semi-major axis is zero, create a vertical line
+ for (int y = centerY - b; y <= centerY + b; y++) {
+ points.add(new int[] {centerX, y});
+ }
+ return points; // Early return
+ }
+
+ if (b == 0) {
+ // Semi-minor axis is zero, create a horizontal line
+ for (int x = centerX - a; x <= centerX + a; x++) {
+ points.add(new int[] {x, centerY});
+ }
+ return points; // Early return
+ }
+
+ // Normal case: Non-degenerate ellipse
+ computeEllipsePoints(points, centerX, centerY, a, b);
+
+ return points; // Return all calculated points of the ellipse
+ }
+
+ /**
+ * Computes points of a non-degenerate ellipse using the Midpoint Ellipse Algorithm.
+ *
+ * @param points the list to which points will be added
+ * @param centerX the x-coordinate of the center of the ellipse
+ * @param centerY the y-coordinate of the center of the ellipse
+ * @param a the length of the semi-major axis (horizontal radius)
+ * @param b the length of the semi-minor axis (vertical radius)
+ */
+ private static void computeEllipsePoints(Collection points, int centerX, int centerY, int a, int b) {
+ int x = 0; // Initial x-coordinate
+ int y = b; // Initial y-coordinate
+
+ // Region 1: Initial decision parameter
+ double d1 = (b * b) - (a * a * b) + (0.25 * a * a); // Decision variable for region 1
+ double dx = 2.0 * b * b * x; // Change in x
+ double dy = 2.0 * a * a * y; // Change in y
+
+ // Region 1: When the slope is less than 1
+ while (dx < dy) {
+ addEllipsePoints(points, centerX, centerY, x, y);
+
+ // Update decision parameter and variables
+ if (d1 < 0) {
+ x++;
+ dx += (2 * b * b); // Update x change
+ d1 += dx + (b * b); // Update decision parameter
+ } else {
+ x++;
+ y--;
+ dx += (2 * b * b); // Update x change
+ dy -= (2 * a * a); // Update y change
+ d1 += dx - dy + (b * b); // Update decision parameter
+ }
+ }
+
+ // Region 2: Initial decision parameter for the second region
+ double d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;
+
+ // Region 2: When the slope is greater than or equal to 1
+ while (y >= 0) {
+ addEllipsePoints(points, centerX, centerY, x, y);
+
+ // Update decision parameter and variables
+ if (d2 > 0) {
+ y--;
+ dy -= (2 * a * a); // Update y change
+ d2 += (a * a) - dy; // Update decision parameter
+ } else {
+ y--;
+ x++;
+ dx += (2 * b * b); // Update x change
+ dy -= (2 * a * a); // Update y change
+ d2 += dx - dy + (a * a); // Update decision parameter
+ }
+ }
+ }
+
+ /**
+ * Adds points for all four quadrants of the ellipse based on symmetry.
+ *
+ * @param points the list to which points will be added
+ * @param centerX the x-coordinate of the center of the ellipse
+ * @param centerY the y-coordinate of the center of the ellipse
+ * @param x the x-coordinate relative to the center
+ * @param y the y-coordinate relative to the center
+ */
+ private static void addEllipsePoints(Collection points, int centerX, int centerY, int x, int y) {
+ points.add(new int[] {centerX + x, centerY + y});
+ points.add(new int[] {centerX - x, centerY + y});
+ points.add(new int[] {centerX + x, centerY - y});
+ points.add(new int[] {centerX - x, centerY - y});
+ }
+}
diff --git a/src/main/java/com/thealgorithms/geometry/Point.java b/src/main/java/com/thealgorithms/geometry/Point.java
new file mode 100644
index 000000000000..564edb4ba7b6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/geometry/Point.java
@@ -0,0 +1,45 @@
+package com.thealgorithms.geometry;
+
+import java.util.Comparator;
+
+public record Point(int x, int y) implements Comparable {
+
+ @Override
+ public int compareTo(Point other) {
+ int cmpY = Integer.compare(this.y, other.y);
+ return cmpY != 0 ? cmpY : Integer.compare(this.x, other.x);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%d, %d)", x, y);
+ }
+
+ public Comparator polarOrder() {
+ return new PolarOrder();
+ }
+
+ public static int orientation(Point a, Point b, Point c) {
+ return Integer.compare((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x), 0);
+ }
+
+ private final class PolarOrder implements Comparator {
+ @Override
+ public int compare(Point p1, Point p2) {
+ int dx1 = p1.x - x;
+ int dy1 = p1.y - y;
+ int dx2 = p2.x - x;
+ int dy2 = p2.y - y;
+
+ if (dy1 >= 0 && dy2 < 0) {
+ return -1; // p1 above p2
+ } else if (dy2 >= 0 && dy1 < 0) {
+ return 1; // p1 below p2
+ } else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal
+ return Integer.compare(dx2, dx1);
+ } else {
+ return -orientation(Point.this, p1, p2); // Compare orientation
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java b/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java
new file mode 100644
index 000000000000..f397989911d9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java
@@ -0,0 +1,123 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class implements a solution for the Constrained Shortest Path Problem (CSPP).
+ * also known as Shortest Path Problem with Resource Constraints (SPPRC).
+ * The goal is to find the shortest path between two nodes while ensuring that
+ * the resource constraint is not exceeded.
+ *
+ * @author Deniz Altunkapan
+ */
+public class ConstrainedShortestPath {
+
+ /**
+ * Represents a graph using an adjacency list.
+ * This graph is designed for the Constrained Shortest Path Problem (CSPP).
+ */
+ public static class Graph {
+
+ private List> adjacencyList;
+
+ public Graph(int numNodes) {
+ adjacencyList = new ArrayList<>();
+ for (int i = 0; i < numNodes; i++) {
+ adjacencyList.add(new ArrayList<>());
+ }
+ }
+
+ /**
+ * Adds an edge to the graph.
+ * @param from the starting node
+ * @param to the ending node
+ * @param cost the cost of the edge
+ * @param resource the resource required to traverse the edge
+ */
+ public void addEdge(int from, int to, int cost, int resource) {
+ adjacencyList.get(from).add(new Edge(from, to, cost, resource));
+ }
+
+ /**
+ * Gets the edges that are adjacent to a given node.
+ * @param node the node to get the edges for
+ * @return the list of edges adjacent to the node
+ */
+ public List getEdges(int node) {
+ return adjacencyList.get(node);
+ }
+
+ /**
+ * Gets the number of nodes in the graph.
+ * @return the number of nodes
+ */
+ public int getNumNodes() {
+ return adjacencyList.size();
+ }
+
+ public record Edge(int from, int to, int cost, int resource) {
+ }
+ }
+
+ private Graph graph;
+ private int maxResource;
+
+ /**
+ * Constructs a CSPSolver with the given graph and maximum resource constraint.
+ *
+ * @param graph the graph representing the problem
+ * @param maxResource the maximum allowable resource
+ */
+ public ConstrainedShortestPath(Graph graph, int maxResource) {
+ this.graph = graph;
+ this.maxResource = maxResource;
+ }
+
+ /**
+ * Solves the CSP to find the shortest path from the start node to the target node
+ * without exceeding the resource constraint.
+ *
+ * @param start the starting node
+ * @param target the target node
+ * @return the minimum cost to reach the target node within the resource constraint,
+ * or -1 if no valid path exists
+ */
+ public int solve(int start, int target) {
+ int numNodes = graph.getNumNodes();
+ int[][] dp = new int[maxResource + 1][numNodes];
+
+ // Initialize dp table with maximum values
+ for (int i = 0; i <= maxResource; i++) {
+ Arrays.fill(dp[i], Integer.MAX_VALUE);
+ }
+ dp[0][start] = 0;
+
+ // Dynamic Programming: Iterate over resources and nodes
+ for (int r = 0; r <= maxResource; r++) {
+ for (int u = 0; u < numNodes; u++) {
+ if (dp[r][u] == Integer.MAX_VALUE) {
+ continue;
+ }
+ for (Graph.Edge edge : graph.getEdges(u)) {
+ int v = edge.to();
+ int cost = edge.cost();
+ int resource = edge.resource();
+
+ if (r + resource <= maxResource) {
+ dp[r + resource][v] = Math.min(dp[r + resource][v], dp[r][u] + cost);
+ }
+ }
+ }
+ }
+
+ // Find the minimum cost to reach the target node
+ int minCost = Integer.MAX_VALUE;
+ for (int r = 0; r <= maxResource; r++) {
+ minCost = Math.min(minCost, dp[r][target]);
+ }
+
+ return minCost == Integer.MAX_VALUE ? -1 : minCost;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java
new file mode 100644
index 000000000000..ba75b2f4b1b8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java
@@ -0,0 +1,86 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Finds the strongly connected components in a directed graph.
+ *
+ * @param adjList The adjacency list representation of the graph.
+ * @param n The number of nodes in the graph.
+ * @return The number of strongly connected components.
+ */
+public class StronglyConnectedComponentOptimized {
+
+ public void btrack(HashMap> adjList, int[] visited, Stack dfsCallsNodes, int currentNode) {
+ visited[currentNode] = 1;
+ List neighbors = adjList.get(currentNode);
+ // Check for null before iterating
+ if (neighbors != null) {
+ for (int neighbor : neighbors) {
+ if (visited[neighbor] == -1) {
+ btrack(adjList, visited, dfsCallsNodes, neighbor);
+ }
+ }
+ }
+ dfsCallsNodes.add(currentNode);
+ }
+
+ public void btrack2(HashMap> adjRevList, int[] visited, int currentNode, List newScc) {
+ visited[currentNode] = 1;
+ newScc.add(currentNode);
+ List neighbors = adjRevList.get(currentNode);
+ // Check for null before iterating
+ if (neighbors != null) {
+ for (int neighbor : neighbors) {
+ if (visited[neighbor] == -1) {
+ btrack2(adjRevList, visited, neighbor, newScc);
+ }
+ }
+ }
+ }
+
+ public int getOutput(HashMap> adjList, int n) {
+ int[] visited = new int[n];
+ Arrays.fill(visited, -1);
+ Stack dfsCallsNodes = new Stack<>();
+
+ for (int i = 0; i < n; i++) {
+ if (visited[i] == -1) {
+ btrack(adjList, visited, dfsCallsNodes, i);
+ }
+ }
+
+ HashMap> adjRevList = new HashMap<>();
+ for (int i = 0; i < n; i++) {
+ adjRevList.put(i, new ArrayList<>());
+ }
+
+ for (int i = 0; i < n; i++) {
+ List neighbors = adjList.get(i);
+ // Check for null before iterating
+ if (neighbors != null) {
+ for (int neighbor : neighbors) {
+ adjRevList.get(neighbor).add(i);
+ }
+ }
+ }
+
+ Arrays.fill(visited, -1);
+ int stronglyConnectedComponents = 0;
+
+ while (!dfsCallsNodes.isEmpty()) {
+ int node = dfsCallsNodes.pop();
+ if (visited[node] == -1) {
+ List newScc = new ArrayList<>();
+ btrack2(adjRevList, visited, node, newScc);
+ stronglyConnectedComponents++;
+ }
+ }
+
+ return stronglyConnectedComponents;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/graph/TravelingSalesman.java b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java
new file mode 100644
index 000000000000..14bf89f57cf3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java
@@ -0,0 +1,155 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class provides solutions to the Traveling Salesman Problem (TSP) using both brute-force and dynamic programming approaches.
+ * For more information, see Wikipedia.
+ * @author Deniz Altunkapan
+ */
+
+public final class TravelingSalesman {
+
+ // Private constructor to prevent instantiation
+ private TravelingSalesman() {
+ }
+
+ /**
+ * Solves the Traveling Salesman Problem (TSP) using brute-force approach.
+ * This method generates all possible permutations of cities, calculates the total distance for each route, and returns the shortest distance found.
+ *
+ * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j.
+ * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city.
+ */
+ public static int bruteForce(int[][] distanceMatrix) {
+ if (distanceMatrix.length <= 1) {
+ return 0;
+ }
+
+ List cities = new ArrayList<>();
+ for (int i = 1; i < distanceMatrix.length; i++) {
+ cities.add(i);
+ }
+
+ List> permutations = generatePermutations(cities);
+ int minDistance = Integer.MAX_VALUE;
+
+ for (List permutation : permutations) {
+ List route = new ArrayList<>();
+ route.add(0);
+ route.addAll(permutation);
+ int currentDistance = calculateDistance(distanceMatrix, route);
+ if (currentDistance < minDistance) {
+ minDistance = currentDistance;
+ }
+ }
+
+ return minDistance;
+ }
+
+ /**
+ * Computes the total distance of a given route.
+ *
+ * @param distanceMatrix A square matrix where element [i][j] represents the
+ * distance from city i to city j.
+ * @param route A list representing the order in which the cities are visited.
+ * @return The total distance of the route, or Integer.MAX_VALUE if the route is invalid.
+ */
+ public static int calculateDistance(int[][] distanceMatrix, List route) {
+ int distance = 0;
+ for (int i = 0; i < route.size() - 1; i++) {
+ int d = distanceMatrix[route.get(i)][route.get(i + 1)];
+ if (d == Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ distance += d;
+ }
+ int returnDist = distanceMatrix[route.get(route.size() - 1)][route.get(0)];
+ return (returnDist == Integer.MAX_VALUE) ? Integer.MAX_VALUE : distance + returnDist;
+ }
+
+ /**
+ * Generates all permutations of a given list of cities.
+ *
+ * @param cities A list of cities to permute.
+ * @return A list of all possible permutations.
+ */
+ private static List> generatePermutations(List cities) {
+ List> permutations = new ArrayList<>();
+ permute(cities, 0, permutations);
+ return permutations;
+ }
+
+ /**
+ * Recursively generates permutations using backtracking.
+ *
+ * @param arr The list of cities.
+ * @param k The current index in the permutation process.
+ * @param output The list to store generated permutations.
+ */
+ private static void permute(List arr, int k, List> output) {
+ if (k == arr.size()) {
+ output.add(new ArrayList<>(arr));
+ return;
+ }
+ for (int i = k; i < arr.size(); i++) {
+ Collections.swap(arr, i, k);
+ permute(arr, k + 1, output);
+ Collections.swap(arr, i, k);
+ }
+ }
+
+ /**
+ * Solves the Traveling Salesman Problem (TSP) using dynamic programming with the Held-Karp algorithm.
+ *
+ * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j.
+ * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city.
+ * @throws IllegalArgumentException if the input matrix is not square.
+ */
+ public static int dynamicProgramming(int[][] distanceMatrix) {
+ if (distanceMatrix.length == 0) {
+ return 0;
+ }
+ int n = distanceMatrix.length;
+
+ for (int[] row : distanceMatrix) {
+ if (row.length != n) {
+ throw new IllegalArgumentException("Matrix must be square");
+ }
+ }
+
+ int[][] dp = new int[n][1 << n];
+ for (int[] row : dp) {
+ Arrays.fill(row, Integer.MAX_VALUE);
+ }
+ dp[0][1] = 0;
+
+ for (int mask = 1; mask < (1 << n); mask++) {
+ for (int u = 0; u < n; u++) {
+ if ((mask & (1 << u)) == 0 || dp[u][mask] == Integer.MAX_VALUE) {
+ continue;
+ }
+ for (int v = 0; v < n; v++) {
+ if ((mask & (1 << v)) != 0 || distanceMatrix[u][v] == Integer.MAX_VALUE) {
+ continue;
+ }
+ int newMask = mask | (1 << v);
+ dp[v][newMask] = Math.min(dp[v][newMask], dp[u][mask] + distanceMatrix[u][v]);
+ }
+ }
+ }
+
+ int minDistance = Integer.MAX_VALUE;
+ int fullMask = (1 << n) - 1;
+ for (int i = 1; i < n; i++) {
+ if (dp[i][fullMask] != Integer.MAX_VALUE && distanceMatrix[i][0] != Integer.MAX_VALUE) {
+ minDistance = Math.min(minDistance, dp[i][fullMask] + distanceMatrix[i][0]);
+ }
+ }
+
+ return minDistance == Integer.MAX_VALUE ? 0 : minDistance;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java b/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java
new file mode 100644
index 000000000000..602cdcd5ad40
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java
@@ -0,0 +1,58 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.Arrays;
+
+/**
+ * Class to solve the Bandwidth Allocation Problem.
+ * The goal is to maximize the value gained by allocating bandwidth to users.
+ * Example:
+ * Bandwidth = 10
+ * Users = [3, 5, 7]
+ * Values = [10, 20, 30]
+ * The maximum value achievable is 40 by allocating 3 units to user 0 and 7 units to user 2.
+ *
+ * @author Hardvan
+ */
+public final class BandwidthAllocation {
+ private BandwidthAllocation() {
+ }
+
+ /**
+ * Allocates bandwidth to maximize value.
+ * Steps:
+ * 1. Calculate the ratio of value/demand for each user.
+ * 2. Sort the users in descending order of the ratio.
+ * 3. Allocate bandwidth to users in order of the sorted list.
+ * 4. If the bandwidth is not enough to allocate the full demand of a user, allocate a fraction of the demand.
+ * 5. Return the maximum value achievable.
+ *
+ * @param bandwidth total available bandwidth to allocate
+ * @param users array of user demands
+ * @param values array of values associated with each user's demand
+ * @return the maximum value achievable
+ */
+ public static int maxValue(int bandwidth, int[] users, int[] values) {
+ int n = users.length;
+ double[][] ratio = new double[n][2]; // {index, ratio}
+
+ for (int i = 0; i < n; i++) {
+ ratio[i][0] = i;
+ ratio[i][1] = (double) values[i] / users[i];
+ }
+
+ Arrays.sort(ratio, (a, b) -> Double.compare(b[1], a[1]));
+
+ int maxValue = 0;
+ for (int i = 0; i < n; i++) {
+ int index = (int) ratio[i][0];
+ if (bandwidth >= users[index]) {
+ maxValue += values[index];
+ bandwidth -= users[index];
+ } else {
+ maxValue += (int) (ratio[i][1] * bandwidth);
+ break;
+ }
+ }
+ return maxValue;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java
new file mode 100644
index 000000000000..35cbfe876b05
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java
@@ -0,0 +1,35 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to represent a fraction as a sum of unique unit fractions.
+ * Example:
+ * 2/3 = 1/2 + 1/6
+ * 3/10 = 1/4 + 1/20
+ *
+ * @author Hardvan
+ */
+public final class EgyptianFraction {
+ private EgyptianFraction() {
+ }
+
+ /**
+ * Calculates the Egyptian Fraction representation of a given fraction.
+ *
+ * @param numerator the numerator of the fraction
+ * @param denominator the denominator of the fraction
+ * @return List of unit fractions represented as strings "1/x"
+ */
+ public static List getEgyptianFraction(int numerator, int denominator) {
+ List result = new ArrayList<>();
+ while (numerator != 0) {
+ int x = (int) Math.ceil((double) denominator / numerator);
+ result.add("1/" + x);
+ numerator = numerator * x - denominator;
+ denominator = denominator * x;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java
index 082bd9c68b32..9535a7c6190e 100644
--- a/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java
@@ -3,39 +3,50 @@
import java.util.Arrays;
import java.util.Comparator;
-// Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem
-
+/**
+ * The FractionalKnapsack class provides a method to solve the fractional knapsack problem
+ * using a greedy algorithm approach. It allows for selecting fractions of items to maximize
+ * the total value in a knapsack with a given weight capacity.
+ *
+ * The problem consists of a set of items, each with a weight and a value, and a knapsack
+ * that can carry a maximum weight. The goal is to maximize the value of items in the knapsack,
+ * allowing for the inclusion of fractions of items.
+ *
+ * Problem Link: https://en.wikipedia.org/wiki/Continuous_knapsack_problem
+ */
public final class FractionalKnapsack {
private FractionalKnapsack() {
}
- // Function to perform fractional knapsack
+
+ /**
+ * Computes the maximum value that can be accommodated in a knapsack of a given capacity.
+ *
+ * @param weight an array of integers representing the weights of the items
+ * @param value an array of integers representing the values of the items
+ * @param capacity an integer representing the maximum weight capacity of the knapsack
+ * @return the maximum value that can be obtained by including the items in the knapsack
+ */
public static int fractionalKnapsack(int[] weight, int[] value, int capacity) {
- // Create a 2D array to store item indices and their value-to-weight ratios.
double[][] ratio = new double[weight.length][2];
- // Populate the ratio array with item indices and their value-to-weight ratios.
for (int i = 0; i < weight.length; i++) {
- ratio[i][0] = i; // Assign item index.
- ratio[i][1] = value[i] / (double) weight[i]; // Calculate and assign value-to-weight ratio.
+ ratio[i][0] = i;
+ ratio[i][1] = value[i] / (double) weight[i];
}
- // Sort items by their value-to-weight ratios in descending order.
Arrays.sort(ratio, Comparator.comparingDouble(o -> o[1]));
- int finalValue = 0; // Variable to store the final knapsack value.
- double current = capacity; // Variable to track the remaining capacity of the knapsack.
+ int finalValue = 0;
+ double current = capacity;
- // Iterate through the sorted items to select items for the knapsack.
for (int i = ratio.length - 1; i >= 0; i--) {
- int index = (int) ratio[i][0]; // Get the item index.
+ int index = (int) ratio[i][0];
if (current >= weight[index]) {
- // If the entire item can fit in the knapsack, add its value.
finalValue += value[index];
current -= weight[index];
} else {
- // If only a fraction of the item can fit, add a proportionate value.
finalValue += (int) (ratio[i][1] * current);
- break; // Stop adding items to the knapsack since it's full.
+ break;
}
}
return finalValue;
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java b/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java
new file mode 100644
index 000000000000..152b36053345
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.Arrays;
+
+/**
+ * Given a set of points and a number k.
+ * The goal is to minimize the maximum distance between any point and its nearest center.
+ * Each point is assigned to the nearest center.
+ * The distance between two points is the Euclidean distance.
+ * The problem is NP-hard.
+ *
+ * @author Hardvan
+ */
+public final class KCenters {
+ private KCenters() {
+ }
+
+ /**
+ * Finds the maximum distance to the nearest center given k centers.
+ * Steps:
+ * 1. Initialize an array {@code selected} of size n and an array {@code maxDist} of size n.
+ * 2. Set the first node as selected and update the maxDist array.
+ * 3. For each center, find the farthest node from the selected centers.
+ * 4. Update the maxDist array.
+ * 5. Return the maximum distance to the nearest center.
+ *
+ * @param distances matrix representing distances between nodes
+ * @param k the number of centers
+ * @return the maximum distance to the nearest center
+ */
+ public static int findKCenters(int[][] distances, int k) {
+ int n = distances.length;
+ boolean[] selected = new boolean[n];
+ int[] maxDist = new int[n];
+
+ Arrays.fill(maxDist, Integer.MAX_VALUE);
+
+ selected[0] = true;
+ for (int i = 1; i < n; i++) {
+ maxDist[i] = Math.min(maxDist[i], distances[0][i]);
+ }
+
+ for (int centers = 1; centers < k; centers++) {
+ int farthest = -1;
+ for (int i = 0; i < n; i++) {
+ if (!selected[i] && (farthest == -1 || maxDist[i] > maxDist[farthest])) {
+ farthest = i;
+ }
+ }
+ selected[farthest] = true;
+ for (int i = 0; i < n; i++) {
+ maxDist[i] = Math.min(maxDist[i], distances[farthest][i]);
+ }
+ }
+
+ int result = 0;
+ for (int dist : maxDist) {
+ result = Math.max(result, dist);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java
new file mode 100644
index 000000000000..2341bcdee9f7
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java
@@ -0,0 +1,37 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.Arrays;
+
+/**
+ * The MinimumWaitingTime class provides a method to calculate the minimum
+ * waiting time for a list of queries using a greedy algorithm.
+ *
+ * @author Hardvan
+ */
+public final class MinimumWaitingTime {
+ private MinimumWaitingTime() {
+ }
+
+ /**
+ * Calculates the minimum waiting time for a list of queries.
+ * The function sorts the queries in non-decreasing order and then calculates
+ * the waiting time for each query based on its position in the sorted list.
+ *
+ * @param queries an array of integers representing the query times in picoseconds
+ * @return the minimum waiting time in picoseconds
+ */
+ public static int minimumWaitingTime(int[] queries) {
+ int n = queries.length;
+ if (n <= 1) {
+ return 0;
+ }
+
+ Arrays.sort(queries);
+
+ int totalWaitingTime = 0;
+ for (int i = 0; i < n; i++) {
+ totalWaitingTime += queries[i] * (n - i - 1);
+ }
+ return totalWaitingTime;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java b/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java
new file mode 100644
index 000000000000..7a1f2edf8180
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java
@@ -0,0 +1,52 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.PriorityQueue;
+
+/**
+ * Class to solve the Optimal File Merging Problem.
+ * The goal is to minimize the cost of merging files, where the cost of merging two files is the sum of their sizes.
+ * The cost of merging all files is the sum of the costs of merging each pair of files.
+ * Example:
+ * files = [4, 3, 2, 6]
+ * The minimum cost to merge all files is 29.
+ * Steps:
+ * 1. Merge files 2 and 3 (cost = 2 + 3 = 5). New files = [4, 5, 6]
+ * 2. Merge files 4 and 5 (cost = 4 + 5 = 9). New files = [6, 9]
+ * 3. Merge files 6 and 9 (cost = 6 + 9 = 15). New files = [15]
+ * Total cost = 5 + 9 + 15 = 29
+ *
+ * @author Hardvan
+ */
+public final class OptimalFileMerging {
+ private OptimalFileMerging() {
+ }
+
+ /**
+ * Calculates the minimum cost to merge all files.
+ * Steps:
+ * 1. Add all files to a min heap.
+ * 2. Remove the two smallest files from the heap, merge them, and add the result back to the heap.
+ * 3. Repeat step 2 until there is only one file left in the heap.
+ * 4. The total cost is the sum of all the costs of merging the files.
+ *
+ * @param files array of file sizes
+ * @return the minimum cost to merge the files
+ */
+ public static int minMergeCost(int[] files) {
+ PriorityQueue minHeap = new PriorityQueue<>();
+ for (int file : files) {
+ minHeap.add(file);
+ }
+
+ int totalCost = 0;
+ while (minHeap.size() > 1) {
+ int first = minHeap.poll();
+ int second = minHeap.poll();
+ int cost = first + second;
+ totalCost += cost;
+
+ minHeap.add(cost);
+ }
+ return totalCost;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java
new file mode 100644
index 000000000000..01950d902b4f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.greedyalgorithms;
+
+/**
+ * The StockProfitCalculator class provides a method to calculate the maximum profit
+ * that can be made from a single buy and sell of one share of stock.
+ * The approach uses a greedy algorithm to efficiently determine the maximum profit.
+ *
+ * @author Hardvan
+ */
+public final class StockProfitCalculator {
+ private StockProfitCalculator() {
+ }
+
+ /**
+ * Calculates the maximum profit from a list of stock prices.
+ *
+ * @param prices an array of integers representing the stock prices on different days
+ * @return the maximum profit that can be achieved from a single buy and sell
+ * transaction, or 0 if no profit can be made
+ */
+ public static int maxProfit(int[] prices) {
+ if (prices == null || prices.length == 0) {
+ return 0;
+ }
+
+ int minPrice = prices[0];
+ int maxProfit = 0;
+ for (int price : prices) {
+ minPrice = Math.min(price, minPrice);
+ maxProfit = Math.max(price - minPrice, maxProfit);
+ }
+ return maxProfit;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMax.java b/src/main/java/com/thealgorithms/maths/AbsoluteMax.java
index d0c3db3790a3..c32a408b6609 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteMax.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteMax.java
@@ -17,7 +17,7 @@ public static int getMaxValue(int... numbers) {
}
int absMax = numbers[0];
for (int i = 1; i < numbers.length; i++) {
- if (Math.abs(numbers[i]) > Math.abs(absMax)) {
+ if (Math.abs(numbers[i]) > Math.abs(absMax) || (Math.abs(numbers[i]) == Math.abs(absMax) && numbers[i] > absMax)) {
absMax = numbers[i];
}
}
diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
index 1ffe6d2e81bc..1b9575a330dd 100644
--- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
+++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java
@@ -19,7 +19,7 @@ public static int getMinValue(int... numbers) {
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 = number);
+ 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;
}
diff --git a/src/main/java/com/thealgorithms/maths/AliquotSum.java b/src/main/java/com/thealgorithms/maths/AliquotSum.java
index 0dbc58bed605..996843b56826 100644
--- a/src/main/java/com/thealgorithms/maths/AliquotSum.java
+++ b/src/main/java/com/thealgorithms/maths/AliquotSum.java
@@ -56,7 +56,7 @@ public static int getAliquotSum(int n) {
// if n is a perfect square then its root was added twice in above loop, so subtracting root
// from sum
if (root == (int) root) {
- sum -= root;
+ sum -= (int) root;
}
return sum;
}
diff --git a/src/main/java/com/thealgorithms/maths/Armstrong.java b/src/main/java/com/thealgorithms/maths/Armstrong.java
index ff4ae027a0b7..9a7a014ec99f 100644
--- a/src/main/java/com/thealgorithms/maths/Armstrong.java
+++ b/src/main/java/com/thealgorithms/maths/Armstrong.java
@@ -10,6 +10,7 @@
* An Armstrong number is often called a Narcissistic number.
*
* @author satyabarghav
+ * @modifier rahul katteda - (13/01/2025) - [updated the logic for getting total number of digits]
*/
public class Armstrong {
@@ -20,14 +21,16 @@ public class Armstrong {
* @return {@code true} if the given number is an Armstrong number, {@code false} otherwise
*/
public boolean isArmstrong(int number) {
+ if (number < 0) {
+ return false; // Negative numbers cannot be Armstrong numbers
+ }
long sum = 0;
- String temp = Integer.toString(number); // Convert the given number to a string
- int power = temp.length(); // Extract the length of the number (number of digits)
+ int totalDigits = (int) Math.log10(number) + 1; // get the length of the number (number of digits)
long originalNumber = number;
while (originalNumber > 0) {
long digit = originalNumber % 10;
- sum += (long) Math.pow(digit, power); // The digit raised to the power of the number of digits and added to the sum.
+ sum += (long) Math.pow(digit, totalDigits); // The digit raised to the power of total number of digits and added to the sum.
originalNumber /= 10;
}
diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java
index 6b9c20162da1..a550a7f6504d 100644
--- a/src/main/java/com/thealgorithms/maths/Average.java
+++ b/src/main/java/com/thealgorithms/maths/Average.java
@@ -37,7 +37,7 @@ public static double average(double[] numbers) {
* @return the average of the given numbers
* @throws IllegalArgumentException if the input array is {@code null} or empty
*/
- public static double average(int[] numbers) {
+ public static long average(int[] numbers) {
if (numbers == null || numbers.length == 0) {
throw new IllegalArgumentException("Numbers array cannot be empty or null");
}
@@ -45,6 +45,6 @@ public static double average(int[] numbers) {
for (int number : numbers) {
sum += number;
}
- return (double) (sum / numbers.length);
+ return sum / numbers.length;
}
}
diff --git a/src/main/java/com/thealgorithms/maths/CatalanNumbers.java b/src/main/java/com/thealgorithms/maths/CatalanNumbers.java
new file mode 100644
index 000000000000..387756bdfa0c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/CatalanNumbers.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.maths;
+
+/**
+ * Calculate Catalan Numbers
+ */
+public final class CatalanNumbers {
+ private CatalanNumbers() {
+ }
+
+ /**
+ * Calculate the nth Catalan number using a recursive formula.
+ *
+ * @param n the index of the Catalan number to compute
+ * @return the nth Catalan number
+ */
+ public static long catalan(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("Index must be non-negative");
+ }
+ return factorial(2 * n) / (factorial(n + 1) * factorial(n));
+ }
+
+ /**
+ * Calculate the factorial of a number.
+ *
+ * @param n the number to compute the factorial for
+ * @return the factorial of n
+ */
+ private static long factorial(final int n) {
+ if (n == 0 || n == 1) {
+ return 1;
+ }
+ long result = 1;
+ for (int i = 2; i <= n; i++) {
+ result *= i;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java
new file mode 100644
index 000000000000..c26e67cffb59
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java
@@ -0,0 +1,84 @@
+package com.thealgorithms.maths;
+
+import java.util.List;
+
+/**
+ * @brief Implementation of the Chinese Remainder Theorem (CRT) algorithm
+ * @details
+ * The Chinese Remainder Theorem (CRT) is used to solve systems of
+ * simultaneous congruences. Given several pairwise coprime moduli
+ * and corresponding remainders, the algorithm finds the smallest
+ * positive solution.
+ */
+public final class ChineseRemainderTheorem {
+ private ChineseRemainderTheorem() {
+ }
+
+ /**
+ * @brief Solves the Chinese Remainder Theorem problem.
+ * @param remainders The list of remainders.
+ * @param moduli The list of pairwise coprime moduli.
+ * @return The smallest positive solution that satisfies all the given congruences.
+ */
+ public static int solveCRT(List remainders, List moduli) {
+ int product = 1;
+ int result = 0;
+
+ // Calculate the product of all moduli
+ for (int mod : moduli) {
+ product *= mod;
+ }
+
+ // Apply the formula for each congruence
+ for (int i = 0; i < moduli.size(); i++) {
+ int partialProduct = product / moduli.get(i);
+ int inverse = modInverse(partialProduct, moduli.get(i));
+ result += remainders.get(i) * partialProduct * inverse;
+ }
+
+ // Adjust result to be the smallest positive solution
+ result = result % product;
+ if (result < 0) {
+ result += product;
+ }
+
+ return result;
+ }
+
+ /**
+ * @brief Computes the modular inverse of a number with respect to a modulus using
+ * the Extended Euclidean Algorithm.
+ * @param a The number for which to find the inverse.
+ * @param m The modulus.
+ * @return The modular inverse of a modulo m.
+ */
+ private static int modInverse(int a, int m) {
+ int m0 = m;
+ int x0 = 0;
+ int x1 = 1;
+
+ if (m == 1) {
+ return 0;
+ }
+
+ while (a > 1) {
+ int q = a / m;
+ int t = m;
+
+ // m is remainder now, process same as Euclid's algorithm
+ m = a % m;
+ a = t;
+ t = x0;
+
+ x0 = x1 - q * x0;
+ x1 = t;
+ }
+
+ // Make x1 positive
+ if (x1 < 0) {
+ x1 += m0;
+ }
+
+ return x1;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java
index f7010acf452d..87fc5af57b8d 100644
--- a/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java
+++ b/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java
@@ -1,6 +1,7 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
+import java.util.Collection;
/**
* Class for circular convolution of two discrete signals using the convolution
@@ -19,7 +20,7 @@ private CircularConvolutionFFT() {
* @param x The signal to be padded.
* @param newSize The new size of the signal.
*/
- private static void padding(ArrayList x, int newSize) {
+ private static void padding(Collection x, int newSize) {
if (x.size() < newSize) {
int diff = newSize - x.size();
for (int i = 0; i < diff; i++) {
diff --git a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java
index ce35b02ca13b..ed1ba1bbefc3 100644
--- a/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java
+++ b/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java
@@ -1,6 +1,7 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
+import java.util.Collection;
/**
* Class for linear convolution of two discrete signals using the convolution
@@ -19,7 +20,7 @@ private ConvolutionFFT() {
* @param x The signal to be padded.
* @param newSize The new size of the signal.
*/
- private static void padding(ArrayList x, int newSize) {
+ private static void padding(Collection x, int newSize) {
if (x.size() < newSize) {
int diff = newSize - x.size();
for (int i = 0; i < diff; i++) {
diff --git a/src/main/java/com/thealgorithms/maths/FFT.java b/src/main/java/com/thealgorithms/maths/FFT.java
index 7ca7543d7985..91754bd1a80b 100644
--- a/src/main/java/com/thealgorithms/maths/FFT.java
+++ b/src/main/java/com/thealgorithms/maths/FFT.java
@@ -1,6 +1,7 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
/**
@@ -164,6 +165,14 @@ public Complex divide(double n) {
temp.img = this.img / n;
return temp;
}
+
+ public double real() {
+ return real;
+ }
+
+ public double imaginary() {
+ return img;
+ }
}
/**
@@ -274,7 +283,7 @@ private static int reverseBits(int num, int log2n) {
*
* @param x The ArrayList to be padded.
*/
- private static void paddingPowerOfTwo(ArrayList x) {
+ private static void paddingPowerOfTwo(Collection x) {
int n = 1;
int oldSize = x.size();
while (n < oldSize) {
diff --git a/src/main/java/com/thealgorithms/maths/FFTBluestein.java b/src/main/java/com/thealgorithms/maths/FFTBluestein.java
index 388de6fed3eb..7a03c20cc642 100644
--- a/src/main/java/com/thealgorithms/maths/FFTBluestein.java
+++ b/src/main/java/com/thealgorithms/maths/FFTBluestein.java
@@ -1,6 +1,7 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
+import java.util.List;
/**
* Class for calculating the Fast Fourier Transform (FFT) of a discrete signal
@@ -25,7 +26,7 @@ private FFTBluestein() {
* IFFT of signal x.
* @param inverse True if you want to find the inverse FFT.
*/
- public static void fftBluestein(ArrayList x, boolean inverse) {
+ public static void fftBluestein(List x, boolean inverse) {
int n = x.size();
int bnSize = 2 * n - 1;
int direction = inverse ? -1 : 1;
diff --git a/src/main/java/com/thealgorithms/maths/FastExponentiation.java b/src/main/java/com/thealgorithms/maths/FastExponentiation.java
new file mode 100644
index 000000000000..27f49e27ff30
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/FastExponentiation.java
@@ -0,0 +1,67 @@
+package com.thealgorithms.maths;
+
+/**
+ * This class provides a method to perform fast exponentiation (exponentiation by squaring),
+ * which calculates (base^exp) % mod efficiently.
+ *
+ * The algorithm works by repeatedly squaring the base and reducing the exponent
+ * by half at each step. It exploits the fact that:
+ *
+ * - If exp is even, (base^exp) = (base^(exp/2))^2
+ * - If exp is odd, (base^exp) = base * (base^(exp-1))
+ *
+ * The result is computed modulo `mod` at each step to avoid overflow and keep the result within bounds.
+ *
+ *
+ * Time complexity: O(log(exp)) — much faster than naive exponentiation (O(exp)).
+ *
+ * For more information, please visit {@link https://en.wikipedia.org/wiki/Exponentiation_by_squaring}
+ */
+public final class FastExponentiation {
+
+ /**
+ * Private constructor to hide the implicit public one.
+ */
+ private FastExponentiation() {
+ }
+
+ /**
+ * Performs fast exponentiation to calculate (base^exp) % mod using the method
+ * of exponentiation by squaring.
+ *
+ * This method efficiently computes the result by squaring the base and halving
+ * the exponent at each step. It multiplies the base to the result when the exponent is odd.
+ *
+ * @param base the base number to be raised to the power of exp
+ * @param exp the exponent to which the base is raised
+ * @param mod the modulus to ensure the result does not overflow
+ * @return (base^exp) % mod
+ * @throws IllegalArgumentException if the modulus is less than or equal to 0
+ * @throws ArithmeticException if the exponent is negative (not supported in this implementation)
+ */
+ public static long fastExponentiation(long base, long exp, long mod) {
+ if (mod <= 0) {
+ throw new IllegalArgumentException("Modulus must be positive.");
+ }
+
+ if (exp < 0) {
+ throw new ArithmeticException("Negative exponent is not supported.");
+ }
+
+ long result = 1;
+ base = base % mod; // Take the modulus of the base to handle large base values
+
+ // Fast exponentiation by squaring algorithm
+ while (exp > 0) {
+ // If exp is odd, multiply the base to the result
+ if ((exp & 1) == 1) { // exp & 1 checks if exp is odd
+ result = result * base % mod;
+ }
+ // Square the base and halve the exponent
+ base = base * base % mod; // base^2 % mod to avoid overflow
+ exp >>= 1; // Right shift exp to divide it by 2
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/FindKthNumber.java b/src/main/java/com/thealgorithms/maths/FindKthNumber.java
index a9b267677eac..138d4b952be9 100644
--- a/src/main/java/com/thealgorithms/maths/FindKthNumber.java
+++ b/src/main/java/com/thealgorithms/maths/FindKthNumber.java
@@ -1,5 +1,7 @@
package com.thealgorithms.maths;
+import java.util.Collections;
+import java.util.PriorityQueue;
import java.util.Random;
/**
@@ -62,4 +64,19 @@ private static void swap(int[] array, int i, int j) {
array[i] = array[j];
array[j] = temp;
}
+
+ public static int findKthMaxUsingHeap(int[] array, int k) {
+ if (k <= 0 || k > array.length) {
+ throw new IllegalArgumentException("k must be between 1 and the size of the array");
+ }
+ PriorityQueue maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // using max-heap to store numbers.
+ for (int num : array) {
+ maxHeap.add(num);
+ }
+ while (k > 1) {
+ maxHeap.poll(); // removing max number from heap
+ k--;
+ }
+ return maxHeap.peek();
+ }
}
diff --git a/src/main/java/com/thealgorithms/maths/GCD.java b/src/main/java/com/thealgorithms/maths/GCD.java
index 5156e4ac881d..df27516367b2 100644
--- a/src/main/java/com/thealgorithms/maths/GCD.java
+++ b/src/main/java/com/thealgorithms/maths/GCD.java
@@ -1,9 +1,23 @@
package com.thealgorithms.maths;
/**
- * This is Euclid's algorithm, used to find the greatest common
- * denominator Override function name gcd
+ * This class provides methods to compute the Greatest Common Divisor (GCD) of two or more integers.
*
+ * The Greatest Common Divisor (GCD) of two or more integers is the largest positive integer that divides each of the integers without leaving a remainder.
+ *
+ * The GCD can be computed using the Euclidean algorithm, which is based on the principle that the GCD of two numbers also divides their difference.
+ *
+ * For more information, refer to the
+ * Greatest Common Divisor Wikipedia page.
+ *
+ * Example usage:
+ *
+ * int result1 = GCD.gcd(48, 18);
+ * System.out.println("GCD of 48 and 18: " + result1); // Output: 6
+ *
+ * int result2 = GCD.gcd(48, 18, 30);
+ * System.out.println("GCD of 48, 18, and 30: " + result2); // Output: 6
+ *
* @author Oskar Enmalm 3/10/17
*/
public final class GCD {
@@ -40,7 +54,7 @@ public static int gcd(int num1, int num2) {
* @param numbers the input array
* @return gcd of all of the numbers in the input array
*/
- public static int gcd(int[] numbers) {
+ public static int gcd(int... numbers) {
int result = 0;
for (final var number : numbers) {
result = gcd(result, number);
@@ -48,12 +62,4 @@ public static int gcd(int[] numbers) {
return result;
}
-
- public static void main(String[] args) {
- int[] myIntArray = {4, 16, 32};
-
- // call gcd function (input array)
- System.out.println(gcd(myIntArray)); // => 4
- System.out.printf("gcd(40,24)=%d gcd(24,40)=%d%n", gcd(40, 24), gcd(24, 40)); // => 8
- }
}
diff --git a/src/main/java/com/thealgorithms/maths/Gaussian.java b/src/main/java/com/thealgorithms/maths/Gaussian.java
index 255a84d13854..1e02579757cc 100644
--- a/src/main/java/com/thealgorithms/maths/Gaussian.java
+++ b/src/main/java/com/thealgorithms/maths/Gaussian.java
@@ -1,12 +1,13 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
+import java.util.List;
public final class Gaussian {
private Gaussian() {
}
- public static ArrayList gaussian(int matSize, ArrayList matrix) {
+ public static ArrayList gaussian(int matSize, List matrix) {
int i;
int j = 0;
diff --git a/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java b/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java
new file mode 100644
index 000000000000..4e962722ba88
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java
@@ -0,0 +1,30 @@
+package com.thealgorithms.maths;
+
+import static com.thealgorithms.maths.Prime.PrimeCheck.isPrime;
+
+/**
+ * This is a representation of the unsolved problem of Goldbach's Projection, according to which every
+ * even natural number greater than 2 can be written as the sum of 2 prime numbers
+ * More info: https://en.wikipedia.org/wiki/Goldbach%27s_conjecture
+ * @author Vasilis Sarantidis (https://github.com/BILLSARAN)
+ */
+
+public final class GoldbachConjecture {
+ private GoldbachConjecture() {
+ }
+ public record Result(int number1, int number2) {
+ }
+
+ public static Result getPrimeSum(int number) {
+ if (number <= 2 || number % 2 != 0) {
+ throw new IllegalArgumentException("Number must be even and greater than 2.");
+ }
+
+ for (int i = 0; i <= number / 2; i++) {
+ if (isPrime(i) && isPrime(number - i)) {
+ return new Result(i, number - i);
+ }
+ }
+ throw new IllegalStateException("No valid prime sum found."); // Should not occur
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java b/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java
new file mode 100644
index 000000000000..298fcb7e85f8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java
@@ -0,0 +1,93 @@
+package com.thealgorithms.maths;
+
+import java.math.BigInteger;
+
+/**
+ * This class provides an implementation of the Karatsuba multiplication algorithm.
+ *
+ *
+ * Karatsuba multiplication is a divide-and-conquer algorithm for multiplying two large
+ * numbers. It is faster than the classical multiplication algorithm and reduces the
+ * time complexity to O(n^1.585) by breaking the multiplication of two n-digit numbers
+ * into three multiplications of n/2-digit numbers.
+ *
+ *
+ *
+ * The main idea of the Karatsuba algorithm is based on the following observation:
+ *
+ *
+ *
+ * Let x and y be two numbers:
+ * x = a * 10^m + b
+ * y = c * 10^m + d
+ *
+ * Then, the product of x and y can be expressed as:
+ * x * y = (a * c) * 10^(2*m) + ((a * d) + (b * c)) * 10^m + (b * d)
+ *
+ *
+ * The Karatsuba algorithm calculates this more efficiently by reducing the number of
+ * multiplications from four to three by using the identity:
+ *
+ *
+ * (a + b)(c + d) = ac + ad + bc + bd
+ *
+ *
+ *
+ * The recursion continues until the numbers are small enough to multiply directly using
+ * the traditional method.
+ *
+ */
+public final class KaratsubaMultiplication {
+
+ /**
+ * Private constructor to hide the implicit public constructor
+ */
+ private KaratsubaMultiplication() {
+ }
+
+ /**
+ * Multiplies two large numbers using the Karatsuba algorithm.
+ *
+ *
+ * This method recursively splits the numbers into smaller parts until they are
+ * small enough to be multiplied directly using the traditional method.
+ *
+ *
+ * @param x The first large number to be multiplied (BigInteger).
+ * @param y The second large number to be multiplied (BigInteger).
+ * @return The product of the two numbers (BigInteger).
+ */
+ public static BigInteger karatsuba(BigInteger x, BigInteger y) {
+ // Base case: when numbers are small enough, use direct multiplication
+ // If the number is 4 bits or smaller, switch to the classical method
+ if (x.bitLength() <= 4 || y.bitLength() <= 4) {
+ return x.multiply(y);
+ }
+
+ // Find the maximum bit length of the two numbers
+ int n = Math.max(x.bitLength(), y.bitLength());
+
+ // Split the numbers in the middle
+ int m = n / 2;
+
+ // High and low parts of the first number x (x = a * 10^m + b)
+ BigInteger high1 = x.shiftRight(m); // a = x / 2^m (higher part)
+ BigInteger low1 = x.subtract(high1.shiftLeft(m)); // b = x - a * 2^m (lower part)
+
+ // High and low parts of the second number y (y = c * 10^m + d)
+ BigInteger high2 = y.shiftRight(m); // c = y / 2^m (higher part)
+ BigInteger low2 = y.subtract(high2.shiftLeft(m)); // d = y - c * 2^m (lower part)
+
+ // Recursively calculate three products
+ BigInteger z0 = karatsuba(low1, low2); // z0 = b * d (low1 * low2)
+ BigInteger z1 = karatsuba(low1.add(high1), low2.add(high2)); // z1 = (a + b) * (c + d)
+ BigInteger z2 = karatsuba(high1, high2); // z2 = a * c (high1 * high2)
+
+ // Combine the results using Karatsuba's formula
+ // z0 + ((z1 - z2 - z0) << m) + (z2 << 2m)
+ return z2
+ .shiftLeft(2 * m) // z2 * 10^(2*m)
+ .add(z1.subtract(z2).subtract(z0).shiftLeft(m)) // (z1 - z2 - z0) * 10^m
+ .add(z0); // z0
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
index f5ff50337bc7..03f18aca786f 100644
--- a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
+++ b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java
@@ -1,31 +1,38 @@
package com.thealgorithms.maths;
-/* This is a program to check if a number is a Krishnamurthy number or not.
-A number is a Krishnamurthy number if the sum of the factorials of the digits of the number is equal
-to the number itself. For example, 1, 2 and 145 are Krishnamurthy numbers. Krishnamurthy number is
-also referred to as a Strong number.
+/**
+ * Utility class for checking if a number is a Krishnamurthy number.
+ *
+ * A Krishnamurthy number (also known as a Strong number) is a number whose sum of the factorials of its digits is equal to the number itself.
+ *
+ * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 + 120 = 145.
+ * Example usage:
+ *
+ * boolean isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(145);
+ * System.out.println(isKrishnamurthy); // Output: true
+ *
+ * isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(123);
+ * System.out.println(isKrishnamurthy); // Output: false
+ *
*/
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
public final class KrishnamurthyNumber {
+
private KrishnamurthyNumber() {
}
- // returns True if the number is a Krishnamurthy number and False if it is not.
-
- public static boolean isKMurthy(int n) {
- // initialising the variable s that will store the sum of the factorials of the digits to 0
- int s = 0;
- // storing the number n in a temporary variable tmp
+ /**
+ * Checks if a number is a Krishnamurthy number.
+ *
+ * @param n The number to check
+ * @return true if the number is a Krishnamurthy number, false otherwise
+ */
+ public static boolean isKrishnamurthy(int n) {
int tmp = n;
+ int s = 0;
- // Krishnamurthy numbers are positive
if (n <= 0) {
return false;
- } // checking if the number is a Krishnamurthy number
- else {
+ } else {
while (n != 0) {
// initialising the variable fact that will store the factorials of the digits
int fact = 1;
@@ -43,15 +50,4 @@ public static boolean isKMurthy(int n) {
return tmp == s;
}
}
-
- public static void main(String[] args) throws IOException {
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- System.out.println("Enter a number to check if it is a Krishnamurthy number: ");
- int n = Integer.parseInt(br.readLine());
- if (isKMurthy(n)) {
- System.out.println(n + " is a Krishnamurthy number.");
- } else {
- System.out.println(n + " is NOT a Krishnamurthy number.");
- }
- }
}
diff --git a/src/main/java/com/thealgorithms/maths/MathBuilder.java b/src/main/java/com/thealgorithms/maths/MathBuilder.java
new file mode 100644
index 000000000000..1cf3d8b7fc9a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/MathBuilder.java
@@ -0,0 +1,503 @@
+package com.thealgorithms.maths;
+
+import java.text.DecimalFormat;
+import java.util.Random;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Author: Sadiul Hakim : https://github.com/sadiul-hakim
+ * Profession: Backend Engineer
+ * Date: Oct 20, 2024
+ */
+public final class MathBuilder {
+ private final double result;
+
+ private MathBuilder(Builder builder) {
+ this.result = builder.number;
+ }
+
+ // Returns final result
+ public double get() {
+ return result;
+ }
+
+ // Return result in long
+ public long toLong() {
+ try {
+ if (Double.isNaN(result)) {
+ throw new IllegalArgumentException("Cannot convert NaN to long!");
+ }
+ if (result == Double.POSITIVE_INFINITY) {
+ return Long.MAX_VALUE;
+ }
+ if (result == Double.NEGATIVE_INFINITY) {
+ return Long.MIN_VALUE;
+ }
+ if (result > Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ if (result < Long.MIN_VALUE) {
+ return Long.MIN_VALUE;
+ }
+ return Math.round(result);
+ } catch (Exception ex) {
+ return 0;
+ }
+ }
+
+ public static class Builder {
+ private double number;
+ private double sideNumber;
+ private boolean inParenthesis;
+ private double memory = 0;
+
+ public Builder() {
+ number = 0;
+ }
+
+ public Builder(double num) {
+ number = num;
+ }
+
+ public Builder add(double num) {
+ if (inParenthesis) {
+ sideNumber += num;
+ } else {
+ number += num;
+ }
+ return this;
+ }
+
+ // Takes a number and a condition, only does the operation if condition is true.
+ public Builder addIf(double num, BiFunction condition) {
+ if (!condition.apply(number, num)) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber += num;
+ } else {
+ number += num;
+ }
+ return this;
+ }
+
+ public Builder minus(double num) {
+ if (inParenthesis) {
+ sideNumber -= num;
+ } else {
+ number -= num;
+ }
+ return this;
+ }
+
+ // Takes a number and a condition, only does the operation if condition is true.
+ public Builder minusIf(double num, BiFunction condition) {
+ if (!condition.apply(number, num)) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber -= num;
+ } else {
+ number -= num;
+ }
+ return this;
+ }
+
+ // Generates a random number and sets to NUMBER
+ public Builder rand(long seed) {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero for random assignment!");
+ }
+ Random random = new Random();
+ number = random.nextDouble(seed);
+ return this;
+ }
+
+ // Takes PI value and sets to NUMBER
+ public Builder pi() {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero for PI assignment!");
+ }
+ number = Math.PI;
+ return this;
+ }
+
+ // Takes E value and sets to NUMBER
+ public Builder e() {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero for E assignment!");
+ }
+ number = Math.E;
+ return this;
+ }
+
+ public Builder randomInRange(double min, double max) {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero for random assignment!");
+ }
+ Random random = new Random();
+ number = min + (max - min) * random.nextDouble();
+ return this;
+ }
+
+ public Builder toDegrees() {
+ if (inParenthesis) {
+ sideNumber = Math.toDegrees(sideNumber);
+ } else {
+ number = Math.toDegrees(number);
+ }
+ return this;
+ }
+
+ public Builder max(double num) {
+ if (inParenthesis) {
+ sideNumber = Math.max(sideNumber, num);
+ } else {
+ number = Math.max(number, num);
+ }
+ return this;
+ }
+
+ public Builder min(double num) {
+ if (inParenthesis) {
+ sideNumber = Math.min(sideNumber, num);
+ } else {
+ number = Math.min(number, num);
+ }
+ return this;
+ }
+
+ public Builder multiply(double num) {
+ if (inParenthesis) {
+ sideNumber *= num;
+ } else {
+ number *= num;
+ }
+ return this;
+ }
+
+ // Takes a number and a condition, only does the operation if condition is true.
+ public Builder multiplyIf(double num, BiFunction condition) {
+ if (!condition.apply(number, num)) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber *= num;
+ } else {
+ number *= num;
+ }
+ return this;
+ }
+
+ public Builder divide(double num) {
+ if (num == 0) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber /= num;
+ } else {
+ number /= num;
+ }
+ return this;
+ }
+
+ // Takes a number and a condition, only does the operation if condition is true.
+ public Builder divideIf(double num, BiFunction condition) {
+ if (num == 0) {
+ return this;
+ }
+ if (!condition.apply(number, num)) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber /= num;
+ } else {
+ number /= num;
+ }
+ return this;
+ }
+
+ public Builder mod(double num) {
+ if (inParenthesis) {
+ sideNumber %= num;
+ } else {
+ number %= num;
+ }
+ return this;
+ }
+
+ // Takes a number and a condition, only does the operation if condition is true.
+ public Builder modIf(double num, BiFunction condition) {
+ if (!condition.apply(number, num)) {
+ return this;
+ }
+ if (inParenthesis) {
+ sideNumber %= num;
+ } else {
+ number %= num;
+ }
+ return this;
+ }
+
+ public Builder pow(double num) {
+ if (inParenthesis) {
+ sideNumber = Math.pow(sideNumber, num);
+ } else {
+ number = Math.pow(number, num);
+ }
+ return this;
+ }
+
+ public Builder sqrt() {
+ if (inParenthesis) {
+ sideNumber = Math.sqrt(sideNumber);
+ } else {
+ number = Math.sqrt(number);
+ }
+ return this;
+ }
+
+ public Builder round() {
+ if (inParenthesis) {
+ sideNumber = Math.round(sideNumber);
+ } else {
+ number = Math.round(number);
+ }
+ return this;
+ }
+
+ public Builder floor() {
+ if (inParenthesis) {
+ sideNumber = Math.floor(sideNumber);
+ } else {
+ number = Math.floor(number);
+ }
+ return this;
+ }
+
+ public Builder ceil() {
+ if (inParenthesis) {
+ sideNumber = Math.ceil(sideNumber);
+ } else {
+ number = Math.ceil(number);
+ }
+ return this;
+ }
+
+ public Builder abs() {
+ if (inParenthesis) {
+ sideNumber = Math.abs(sideNumber);
+ } else {
+ number = Math.abs(number);
+ }
+ return this;
+ }
+
+ public Builder cbrt() {
+ if (inParenthesis) {
+ sideNumber = Math.cbrt(sideNumber);
+ } else {
+ number = Math.cbrt(number);
+ }
+ return this;
+ }
+
+ public Builder log() {
+ if (inParenthesis) {
+ sideNumber = Math.log(sideNumber);
+ } else {
+ number = Math.log(number);
+ }
+ return this;
+ }
+
+ public Builder log10() {
+ if (inParenthesis) {
+ sideNumber = Math.log10(sideNumber);
+ } else {
+ number = Math.log10(number);
+ }
+ return this;
+ }
+
+ public Builder sin() {
+ if (inParenthesis) {
+ sideNumber = Math.sin(sideNumber);
+ } else {
+ number = Math.sin(number);
+ }
+ return this;
+ }
+
+ public Builder cos() {
+ if (inParenthesis) {
+ sideNumber = Math.cos(sideNumber);
+ } else {
+ number = Math.cos(number);
+ }
+ return this;
+ }
+
+ public Builder tan() {
+ if (inParenthesis) {
+ sideNumber = Math.tan(sideNumber);
+ } else {
+ number = Math.tan(number);
+ }
+ return this;
+ }
+
+ public Builder sinh() {
+ if (inParenthesis) {
+ sideNumber = Math.sinh(sideNumber);
+ } else {
+ number = Math.sinh(number);
+ }
+ return this;
+ }
+
+ public Builder cosh() {
+ if (inParenthesis) {
+ sideNumber = Math.cosh(sideNumber);
+ } else {
+ number = Math.cosh(number);
+ }
+ return this;
+ }
+
+ public Builder tanh() {
+ if (inParenthesis) {
+ sideNumber = Math.tanh(sideNumber);
+ } else {
+ number = Math.tanh(number);
+ }
+ return this;
+ }
+
+ public Builder exp() {
+ if (inParenthesis) {
+ sideNumber = Math.exp(sideNumber);
+ } else {
+ number = Math.exp(number);
+ }
+ return this;
+ }
+
+ public Builder toRadians() {
+ if (inParenthesis) {
+ sideNumber = Math.toRadians(sideNumber);
+ } else {
+ number = Math.toRadians(number);
+ }
+ return this;
+ }
+
+ // Remembers the NUMBER
+ public Builder remember() {
+ memory = number;
+ return this;
+ }
+
+ // Recalls the NUMBER
+ public Builder recall(boolean cleanMemory) {
+ number = memory;
+ if (cleanMemory) {
+ memory = 0;
+ }
+ return this;
+ }
+
+ // Recalls the NUMBER on condition
+ public Builder recallIf(Function condition, boolean cleanMemory) {
+ if (!condition.apply(number)) {
+ return this;
+ }
+ number = memory;
+ if (cleanMemory) {
+ memory = 0;
+ }
+ return this;
+ }
+
+ // Replaces NUMBER with given number
+ public Builder set(double num) {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero to set!");
+ }
+ number = num;
+ return this;
+ }
+
+ // Replaces NUMBER with given number on condition
+ public Builder setIf(double num, BiFunction condition) {
+ if (number != 0) {
+ throw new RuntimeException("Number must be zero to set!");
+ }
+ if (condition.apply(number, num)) {
+ number = num;
+ }
+ return this;
+ }
+
+ // Prints current NUMBER
+ public Builder print() {
+ System.out.println("MathBuilder Result :: " + number);
+ return this;
+ }
+
+ public Builder openParenthesis(double num) {
+ sideNumber = num;
+ inParenthesis = true;
+ return this;
+ }
+
+ public Builder closeParenthesisAndPlus() {
+ number += sideNumber;
+ inParenthesis = false;
+ sideNumber = 0;
+ return this;
+ }
+
+ public Builder closeParenthesisAndMinus() {
+ number -= sideNumber;
+ inParenthesis = false;
+ sideNumber = 0;
+ return this;
+ }
+
+ public Builder closeParenthesisAndMultiply() {
+ number *= sideNumber;
+ inParenthesis = false;
+ sideNumber = 0;
+ return this;
+ }
+
+ public Builder closeParenthesisAndDivide() {
+ number /= sideNumber;
+ inParenthesis = false;
+ sideNumber = 0;
+ return this;
+ }
+
+ public Builder format(String format) {
+ DecimalFormat formater = new DecimalFormat(format);
+ String num = formater.format(number);
+ number = Double.parseDouble(num);
+ return this;
+ }
+
+ public Builder format(int decimalPlace) {
+ String pattern = "."
+ + "#".repeat(decimalPlace);
+ DecimalFormat formater = new DecimalFormat(pattern);
+ String num = formater.format(number);
+ number = Double.parseDouble(num);
+ return this;
+ }
+
+ public MathBuilder build() {
+ return new MathBuilder(this);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java
index 90507701332f..2da22c4c8696 100644
--- a/src/main/java/com/thealgorithms/maths/NthUglyNumber.java
+++ b/src/main/java/com/thealgorithms/maths/NthUglyNumber.java
@@ -1,7 +1,9 @@
package com.thealgorithms.maths;
+import static java.util.Collections.singletonList;
+
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Map;
import org.apache.commons.lang3.tuple.MutablePair;
/**
@@ -15,7 +17,7 @@
* - the base [2, 3, 5] ugly numbers are the same as base [5, 6, 2, 3, 5] ugly numbers
*/
public class NthUglyNumber {
- private ArrayList uglyNumbers = new ArrayList<>(Arrays.asList(1L));
+ private ArrayList uglyNumbers = new ArrayList<>(singletonList(1L));
private ArrayList> positions = new ArrayList<>();
/**
@@ -64,7 +66,7 @@ private void updatePositions() {
}
}
- private long computeCandidate(final MutablePair entry) {
+ private long computeCandidate(final Map.Entry entry) {
return entry.getKey() * uglyNumbers.get(entry.getValue());
}
diff --git a/src/main/java/com/thealgorithms/maths/PerfectNumber.java b/src/main/java/com/thealgorithms/maths/PerfectNumber.java
index 2a935b067094..f299d08e5d27 100644
--- a/src/main/java/com/thealgorithms/maths/PerfectNumber.java
+++ b/src/main/java/com/thealgorithms/maths/PerfectNumber.java
@@ -63,7 +63,7 @@ public static boolean isPerfectNumber2(int n) {
// if n is a perfect square then its root was added twice in above loop, so subtracting root
// from sum
if (root == (int) root) {
- sum -= root;
+ sum -= (int) root;
}
return sum == n;
diff --git a/src/main/java/com/thealgorithms/maths/PerfectSquare.java b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
index fbc7a6f19bd0..e9318bd7d805 100644
--- a/src/main/java/com/thealgorithms/maths/PerfectSquare.java
+++ b/src/main/java/com/thealgorithms/maths/PerfectSquare.java
@@ -18,4 +18,16 @@ public static boolean isPerfectSquare(final int number) {
final int sqrt = (int) Math.sqrt(number);
return sqrt * sqrt == number;
}
+
+ /**
+ * Check if a number is perfect square or not
+ *
+ * @param number number to be checked
+ * @return {@code true} if {@code number} is perfect square, otherwise
+ * {@code false}
+ */
+ public static boolean isPerfectSquareUsingPow(long number) {
+ long a = (long) Math.pow(number, 1.0 / 2);
+ return a * a == number;
+ }
}
diff --git a/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java b/src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java
similarity index 93%
rename from src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java
rename to src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java
index c0f55f5e3485..73535b3aedae 100644
--- a/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
/*
* Java program for liouville lambda function
@@ -24,7 +24,7 @@ private LiouvilleLambdaFunction() {
* -1 when number has odd number of prime factors
* @throws IllegalArgumentException when number is negative
*/
- static int liouvilleLambda(int number) {
+ public static int liouvilleLambda(int number) {
if (number <= 0) {
// throw exception when number is less than or is zero
throw new IllegalArgumentException("Number must be greater than zero.");
diff --git a/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java b/src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java
similarity index 98%
rename from src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java
rename to src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java
index f889213abfcb..debe3a214a32 100644
--- a/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
import java.util.Random;
diff --git a/src/main/java/com/thealgorithms/maths/MobiusFunction.java b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
similarity index 96%
rename from src/main/java/com/thealgorithms/maths/MobiusFunction.java
rename to src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
index 915d0d9a6dae..3d4e4eff0f03 100644
--- a/src/main/java/com/thealgorithms/maths/MobiusFunction.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
/*
* Java program for mobius function
@@ -25,7 +25,7 @@ private MobiusFunction() {
* 0 when number has repeated prime factor
* -1 when number has odd number of prime factors
*/
- static int mobius(int number) {
+ public static int mobius(int number) {
if (number <= 0) {
// throw exception when number is less than or is zero
throw new IllegalArgumentException("Number must be greater than zero.");
diff --git a/src/main/java/com/thealgorithms/maths/PrimeCheck.java b/src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java
similarity index 98%
rename from src/main/java/com/thealgorithms/maths/PrimeCheck.java
rename to src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java
index 628a819aeba4..91c490f70aef 100644
--- a/src/main/java/com/thealgorithms/maths/PrimeCheck.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
import java.util.Scanner;
diff --git a/src/main/java/com/thealgorithms/maths/PrimeFactorization.java b/src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java
similarity index 95%
rename from src/main/java/com/thealgorithms/maths/PrimeFactorization.java
rename to src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java
index 9ac50fd9043b..e12002b3d8c7 100644
--- a/src/main/java/com/thealgorithms/maths/PrimeFactorization.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
/*
* Authors:
diff --git a/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java b/src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java
similarity index 97%
rename from src/main/java/com/thealgorithms/maths/SquareFreeInteger.java
rename to src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java
index 22e9fee00605..15c0a8a691cd 100644
--- a/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java
+++ b/src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.maths.Prime;
/*
* Java program for Square free integer
* This class has a function which checks
diff --git a/src/main/java/com/thealgorithms/maths/PronicNumber.java b/src/main/java/com/thealgorithms/maths/PronicNumber.java
index 4891cf3c63b3..c2ad2a8139c8 100644
--- a/src/main/java/com/thealgorithms/maths/PronicNumber.java
+++ b/src/main/java/com/thealgorithms/maths/PronicNumber.java
@@ -21,6 +21,9 @@ private PronicNumber() {
* @return true if input number is a pronic number, false otherwise
*/
static boolean isPronic(int inputNumber) {
+ if (inputNumber == 0) {
+ return true;
+ }
// Iterating from 0 to input_number
for (int i = 0; i <= inputNumber; i++) {
// Checking if product of i and (i+1) is equals input_number
@@ -34,4 +37,15 @@ static boolean isPronic(int inputNumber) {
// equals input_number
return false;
}
+
+ /**
+ * This method checks if the given number is pronic number or non-pronic number using square root of number for finding divisors
+ *
+ * @param number Integer value which is to be checked if is a pronic number or not
+ * @return true if input number is a pronic number, false otherwise
+ */
+ public static boolean isPronicNumber(int number) {
+ int squareRoot = (int) Math.sqrt(number); // finding just smaller divisor of the number than its square root.
+ return squareRoot * (squareRoot + 1) == number;
+ }
}
diff --git a/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java b/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java
new file mode 100644
index 000000000000..cd654c5dc023
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.maths;
+
+/**
+ * This class represents a complex number which has real and imaginary part
+ */
+class ComplexNumber {
+ Double real;
+ Double imaginary;
+
+ ComplexNumber(double real, double imaginary) {
+ this.real = real;
+ this.imaginary = imaginary;
+ }
+
+ ComplexNumber(double real) {
+ this.real = real;
+ this.imaginary = null;
+ }
+}
+
+/**
+ * Quadratic Equation Formula is used to find
+ * the roots of a quadratic equation of the form ax^2 + bx + c = 0
+ *
+ * @see Quadratic Equation
+ */
+public class QuadraticEquationSolver {
+ /**
+ * Function takes in the coefficients of the quadratic equation
+ *
+ * @param a is the coefficient of x^2
+ * @param b is the coefficient of x
+ * @param c is the constant
+ * @return roots of the equation which are ComplexNumber type
+ */
+ public ComplexNumber[] solveEquation(double a, double b, double c) {
+ double discriminant = b * b - 4 * a * c;
+
+ // if discriminant is positive, roots will be different
+ if (discriminant > 0) {
+ return new ComplexNumber[] {new ComplexNumber((-b + Math.sqrt(discriminant)) / (2 * a)), new ComplexNumber((-b - Math.sqrt(discriminant)) / (2 * a))};
+ }
+
+ // if discriminant is zero, roots will be same
+ if (discriminant == 0) {
+ return new ComplexNumber[] {new ComplexNumber((-b) / (2 * a))};
+ }
+
+ // if discriminant is negative, roots will have imaginary parts
+ if (discriminant < 0) {
+ double realPart = -b / (2 * a);
+ double imaginaryPart = Math.sqrt(-discriminant) / (2 * a);
+
+ return new ComplexNumber[] {new ComplexNumber(realPart, imaginaryPart), new ComplexNumber(realPart, -imaginaryPart)};
+ }
+
+ // return no roots
+ return new ComplexNumber[] {};
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java b/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java
new file mode 100644
index 000000000000..caa1abfc3203
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java
@@ -0,0 +1,133 @@
+package com.thealgorithms.maths;
+
+import java.util.Random;
+
+/**
+ * This class implements the Solovay-Strassen primality test,
+ * which is a probabilistic algorithm to determine whether a number is prime.
+ * The algorithm is based on properties of the Jacobi symbol and modular exponentiation.
+ *
+ * For more information, go to {@link https://en.wikipedia.org/wiki/Solovay%E2%80%93Strassen_primality_test}
+ */
+final class SolovayStrassenPrimalityTest {
+
+ private final Random random;
+
+ /**
+ * Constructs a SolovayStrassenPrimalityTest instance with a specified seed for randomness.
+ *
+ * @param seed the seed for generating random numbers
+ */
+ private SolovayStrassenPrimalityTest(int seed) {
+ random = new Random(seed);
+ }
+
+ /**
+ * Factory method to create an instance of SolovayStrassenPrimalityTest.
+ *
+ * @param seed the seed for generating random numbers
+ * @return a new instance of SolovayStrassenPrimalityTest
+ */
+ public static SolovayStrassenPrimalityTest getSolovayStrassenPrimalityTest(int seed) {
+ return new SolovayStrassenPrimalityTest(seed);
+ }
+
+ /**
+ * Calculates modular exponentiation using the method of exponentiation by squaring.
+ *
+ * @param base the base number
+ * @param exponent the exponent
+ * @param mod the modulus
+ * @return (base^exponent) mod mod
+ */
+ private static long calculateModularExponentiation(long base, long exponent, long mod) {
+ long x = 1; // This will hold the result of (base^exponent) % mod
+ long y = base; // This holds the current base value being squared
+
+ while (exponent > 0) {
+ // If exponent is odd, multiply the current base (y) with x
+ if (exponent % 2 == 1) {
+ x = x * y % mod; // Update result with current base
+ }
+ // Square the base for the next iteration
+ y = y * y % mod; // Update base to be y^2
+ exponent = exponent / 2; // Halve the exponent for next iteration
+ }
+
+ return x % mod; // Return final result after all iterations
+ }
+
+ /**
+ * Computes the Jacobi symbol (a/n), which is a generalization of the Legendre symbol.
+ *
+ * @param a the numerator
+ * @param num the denominator (must be an odd positive integer)
+ * @return the Jacobi symbol value: 1, -1, or 0
+ */
+ public int calculateJacobi(long a, long num) {
+ // Check if num is non-positive or even; Jacobi symbol is not defined in these cases
+ if (num <= 0 || num % 2 == 0) {
+ return 0;
+ }
+
+ a = a % num; // Reduce a modulo num to simplify calculations
+ int jacobi = 1; // Initialize Jacobi symbol value
+
+ while (a != 0) {
+ // While a is even, reduce it and adjust jacobi based on properties of num
+ while (a % 2 == 0) {
+ a /= 2; // Divide a by 2 until it becomes odd
+ long nMod8 = num % 8; // Get num modulo 8 to check conditions for jacobi adjustment
+ if (nMod8 == 3 || nMod8 == 5) {
+ jacobi = -jacobi; // Flip jacobi sign based on properties of num modulo 8
+ }
+ }
+
+ long temp = a; // Temporarily store value of a
+ a = num; // Set a to be num for next iteration
+ num = temp; // Set num to be previous value of a
+
+ // Adjust jacobi based on properties of both numbers when both are odd and congruent to 3 modulo 4
+ if (a % 4 == 3 && num % 4 == 3) {
+ jacobi = -jacobi; // Flip jacobi sign again based on congruences
+ }
+
+ a = a % num; // Reduce a modulo num for next iteration of Jacobi computation
+ }
+
+ return (num == 1) ? jacobi : 0; // If num reduces to 1, return jacobi value, otherwise return 0 (not defined)
+ }
+
+ /**
+ * Performs the Solovay-Strassen primality test on a given number.
+ *
+ * @param num the number to be tested for primality
+ * @param iterations the number of iterations to run for accuracy
+ * @return true if num is likely prime, false if it is composite
+ */
+ public boolean solovayStrassen(long num, int iterations) {
+ if (num <= 1) {
+ return false; // Numbers <=1 are not prime by definition.
+ }
+ if (num <= 3) {
+ return true; // Numbers <=3 are prime.
+ }
+
+ for (int i = 0; i < iterations; i++) {
+ long r = Math.abs(random.nextLong() % (num - 1)) + 2; // Generate a non-negative random number.
+ long a = r % (num - 1) + 1; // Choose random 'a' in range [1, n-1].
+
+ long jacobi = (num + calculateJacobi(a, num)) % num;
+ // Calculate Jacobi symbol and adjust it modulo n.
+
+ long mod = calculateModularExponentiation(a, (num - 1) / 2, num);
+ // Calculate modular exponentiation: a^((n-1)/2) mod n.
+
+ if (jacobi == 0 || mod != jacobi) {
+ return false; // If Jacobi symbol is zero or doesn't match modular result, n is composite.
+ }
+ }
+
+ return true; // If no contradictions found after all iterations, n is likely prime.
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java
new file mode 100644
index 000000000000..c0a1e782659a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java
@@ -0,0 +1,25 @@
+package com.thealgorithms.maths;
+
+/**
+ * This program calculates the sum of the first n odd numbers.
+ *
+ * https://www.cuemath.com/algebra/sum-of-odd-numbers/
+ */
+
+public final class SumOfOddNumbers {
+ private SumOfOddNumbers() {
+ }
+
+ /**
+ * Calculate sum of the first n odd numbers
+ *
+ * @param n the number of odd numbers to sum
+ * @return sum of the first n odd numbers
+ */
+ public static int sumOfFirstNOddNumbers(final int n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n must be non-negative.");
+ }
+ return n * n;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/TwinPrime.java b/src/main/java/com/thealgorithms/maths/TwinPrime.java
index ef8de0d1018e..f4e546a2d7a4 100644
--- a/src/main/java/com/thealgorithms/maths/TwinPrime.java
+++ b/src/main/java/com/thealgorithms/maths/TwinPrime.java
@@ -9,6 +9,8 @@
*
* */
+import com.thealgorithms.maths.Prime.PrimeCheck;
+
public final class TwinPrime {
private TwinPrime() {
}
diff --git a/src/main/java/com/thealgorithms/maths/UniformNumbers.java b/src/main/java/com/thealgorithms/maths/UniformNumbers.java
new file mode 100644
index 000000000000..c83783aab0b3
--- /dev/null
+++ b/src/main/java/com/thealgorithms/maths/UniformNumbers.java
@@ -0,0 +1,50 @@
+package com.thealgorithms.maths;
+
+/**
+ * A positive integer is considered uniform if all
+ * of its digits are equal. For example, 222 is uniform,
+ * while 223 is not.
+ * Given two positive integers a and b, determine the
+ * number of uniform integers between a and b.
+ */
+public final class UniformNumbers {
+ // Private constructor to prevent instantiation of the utility class
+ private UniformNumbers() {
+ // Prevent instantiation
+ }
+ /**
+ * This function will find the number of uniform numbers
+ * from 1 to num
+ * @param num upper limit to find the uniform numbers
+ * @return the count of uniform numbers between 1 and num
+ */
+ public static int uniformNumbers(int num) {
+ String numStr = Integer.toString(num);
+ int uniformCount = (numStr.length() - 1) * 9;
+ int finalUniform = Integer.parseInt(String.valueOf(numStr.charAt(0)).repeat(numStr.length()));
+
+ if (finalUniform <= num) {
+ uniformCount += Integer.parseInt(String.valueOf(numStr.charAt(0)));
+ } else {
+ uniformCount += Integer.parseInt(String.valueOf(numStr.charAt(0))) - 1;
+ }
+
+ return uniformCount;
+ }
+ /**
+ * This function will calculate the number of uniform numbers
+ * between a and b
+ * @param a lower bound of range
+ * @param b upper bound of range
+ * @return the count of uniform numbers between a and b
+ */
+ public static int countUniformIntegers(int a, int b) {
+ if (b > a && b > 0 && a > 0) {
+ return uniformNumbers(b) - uniformNumbers(a - 1);
+ } else if (b == a) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/VampireNumber.java b/src/main/java/com/thealgorithms/maths/VampireNumber.java
index d64c82c6e68e..45bb9a587778 100644
--- a/src/main/java/com/thealgorithms/maths/VampireNumber.java
+++ b/src/main/java/com/thealgorithms/maths/VampireNumber.java
@@ -1,79 +1,48 @@
package com.thealgorithms.maths;
import java.util.ArrayList;
-import java.util.Collections;
/**
- * n number theory, a vampire number (or true vampire number) is a composite
+ * In number theory, a vampire number (or true vampire number) is a composite
* natural number with an even number of digits, that can be factored into two
* natural numbers each with half as many digits as the original number and not
* both with trailing zeroes, where the two factors contain precisely all the
* digits of the original number, in any order, counting multiplicity. The first
- * vampire number is 1260 = 21 × 60. *
+ * vampire number is 1260 = 21 × 60.
*
- *
- * link: https://en.wikipedia.org/wiki/Vampire_number *
- *
- *
+ * @see Vampire number on Wikipedia
*/
public final class VampireNumber {
+ // Forbid instantiation.
private VampireNumber() {
}
- public static void main(String[] args) {
- test(10, 1000);
- }
-
- static void test(int startValue, int stopValue) {
- int countofRes = 1;
- StringBuilder res = new StringBuilder();
-
- for (int i = startValue; i <= stopValue; i++) {
- for (int j = i; j <= stopValue; j++) {
- // System.out.println(i+ " "+ j);
- if (isVampireNumber(i, j, true)) {
- countofRes++;
- res.append("" + countofRes + ": = ( " + i + "," + j + " = " + i * j + ")"
- + "\n");
- }
- }
- }
- System.out.println(res);
- }
-
- static boolean isVampireNumber(int a, int b, boolean noPseudoVamireNumbers) {
- // this is for pseudoVampireNumbers pseudovampire number need not be of length n/2 digits
- // for example 126 = 6 x 21
- if (noPseudoVamireNumbers) {
- if (a * 10 <= b || b * 10 <= a) {
- return false;
- }
+ static boolean isVampireNumber(int a, int b, boolean ignorePseudoVampireNumbers) {
+ // Pseudo vampire numbers don't have to be of n/2 digits. E.g., 126 = 6 x 21 is such a number.
+ if (ignorePseudoVampireNumbers && String.valueOf(a).length() != String.valueOf(b).length()) {
+ return false;
}
- String mulDigits = splitIntoDigits(a * b, 0);
- String faktorDigits = splitIntoDigits(a, b);
+ String mulDigits = splitIntoSortedDigits(a * b);
+ String factorDigits = splitIntoSortedDigits(a, b);
- return mulDigits.equals(faktorDigits);
+ return mulDigits.equals(factorDigits);
}
- // methode to Split the numbers to Digits
- static String splitIntoDigits(int num, int num2) {
- StringBuilder res = new StringBuilder();
-
+ // Method to split a pair of numbers to digits and sort them in the ascending order.
+ static String splitIntoSortedDigits(int... nums) {
+ // Collect all digits in a list.
ArrayList digits = new ArrayList<>();
- while (num > 0) {
- digits.add(num % 10);
- num /= 10;
- }
- while (num2 > 0) {
- digits.add(num2 % 10);
- num2 /= 10;
- }
- Collections.sort(digits);
- for (int i : digits) {
- res.append(i);
+ for (int num : nums) {
+ while (num > 0) {
+ digits.add(num % 10);
+ num /= 10;
+ }
}
+ // Sort all digits and convert to String.
+ StringBuilder res = new StringBuilder();
+ digits.stream().sorted().forEach(res::append);
return res.toString();
}
}
diff --git a/src/main/java/com/thealgorithms/misc/InverseOfMatrix.java b/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java
similarity index 98%
rename from src/main/java/com/thealgorithms/misc/InverseOfMatrix.java
rename to src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java
index 706feab0c69d..13e795a91297 100644
--- a/src/main/java/com/thealgorithms/misc/InverseOfMatrix.java
+++ b/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.matrix;
/**
* This class provides methods to compute the inverse of a square matrix
diff --git a/src/main/java/com/thealgorithms/maths/MatrixRank.java b/src/main/java/com/thealgorithms/matrix/MatrixRank.java
similarity index 77%
rename from src/main/java/com/thealgorithms/maths/MatrixRank.java
rename to src/main/java/com/thealgorithms/matrix/MatrixRank.java
index 7a628b92dccb..6692b6c37c60 100644
--- a/src/main/java/com/thealgorithms/maths/MatrixRank.java
+++ b/src/main/java/com/thealgorithms/matrix/MatrixRank.java
@@ -1,4 +1,6 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.matrix;
+
+import static com.thealgorithms.matrix.utils.MatrixUtil.validateInputMatrix;
/**
* This class provides a method to compute the rank of a matrix.
@@ -63,47 +65,6 @@ private static double[][] deepCopy(double[][] matrix) {
return matrixCopy;
}
- private static void validateInputMatrix(double[][] matrix) {
- if (matrix == null) {
- throw new IllegalArgumentException("The input matrix cannot be null");
- }
- if (matrix.length == 0) {
- throw new IllegalArgumentException("The input matrix cannot be empty");
- }
- if (!hasValidRows(matrix)) {
- throw new IllegalArgumentException("The input matrix cannot have null or empty rows");
- }
- if (isJaggedMatrix(matrix)) {
- throw new IllegalArgumentException("The input matrix cannot be jagged");
- }
- }
-
- private static boolean hasValidRows(double[][] matrix) {
- for (double[] row : matrix) {
- if (row == null || row.length == 0) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * @brief Checks if the input matrix is a jagged matrix.
- * Jagged matrix is a matrix where the number of columns in each row is not the same.
- *
- * @param matrix The input matrix
- * @return True if the input matrix is a jagged matrix, false otherwise
- */
- private static boolean isJaggedMatrix(double[][] matrix) {
- int numColumns = matrix[0].length;
- for (double[] row : matrix) {
- if (row.length != numColumns) {
- return true;
- }
- }
- return false;
- }
-
/**
* @brief The pivot row is the row in the matrix that is used to eliminate other rows and reduce the matrix to its row echelon form.
* The pivot row is selected as the first row (from top to bottom) where the value in the current column (the pivot column) is not zero.
diff --git a/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java b/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java
new file mode 100644
index 000000000000..f91ebc10b8a9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.matrix;
+
+/**
+ *
+ *
+ * Find the Transpose of Matrix!
+ *
+ * Simply take input from the user and print the matrix before the transpose and
+ * after the transpose.
+ *
+ *
+ * Note: Giving proper comments in your program makes it more user
+ * friendly and it is assumed as a high quality code.
+ *
+ * @author Rajat-Jain29
+ * @version 11.0.9
+ * @since 2014-03-31
+ */
+public final class MatrixTranspose {
+ private MatrixTranspose() {
+ }
+
+ /**
+ * Calculate the transpose of the given matrix.
+ *
+ * @param matrix The matrix to be transposed
+ * @throws IllegalArgumentException if the matrix is empty
+ * @throws NullPointerException if the matrix is null
+ * @return The transposed matrix
+ */
+ public static int[][] transpose(int[][] matrix) {
+ if (matrix == null || matrix.length == 0) {
+ throw new IllegalArgumentException("Matrix is empty");
+ }
+
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ int[][] transposedMatrix = new int[cols][rows];
+ for (int i = 0; i < cols; i++) {
+ for (int j = 0; j < rows; j++) {
+ transposedMatrix[i][j] = matrix[j][i];
+ }
+ }
+ return transposedMatrix;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
similarity index 88%
rename from src/main/java/com/thealgorithms/misc/MedianOfMatrix.java
rename to src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
index d4ddffe8ddd7..c710c60a2d2a 100644
--- a/src/main/java/com/thealgorithms/misc/MedianOfMatrix.java
+++ b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.matrix;
import java.util.ArrayList;
import java.util.Collections;
@@ -13,7 +13,7 @@ public final class MedianOfMatrix {
private MedianOfMatrix() {
}
- public static int median(List> matrix) {
+ public static int median(Iterable> matrix) {
// Flatten the matrix into a 1D list
List linear = new ArrayList<>();
for (List row : matrix) {
diff --git a/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java
new file mode 100644
index 000000000000..3a3055f38732
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.matrix;
+
+// Problem Statement
+
+import com.thealgorithms.matrix.utils.MatrixUtil;
+
+/*
+We have given an array of m x n (where m is the number of rows and n is the number of columns).
+Print the new matrix in such a way that the new matrix is the mirror image of the original matrix.
+
+The Original matrix is: | The Mirror matrix is:
+1 2 3 | 3 2 1
+4 5 6 | 6 5 4
+7 8 9 | 9 8 7
+
+@author - Aman (https://github.com/Aman28801)
+*/
+
+public final class MirrorOfMatrix {
+ private MirrorOfMatrix() {
+ }
+
+ public static double[][] mirrorMatrix(final double[][] originalMatrix) {
+ MatrixUtil.validateInputMatrix(originalMatrix);
+
+ int numRows = originalMatrix.length;
+ int numCols = originalMatrix[0].length;
+
+ double[][] mirroredMatrix = new double[numRows][numCols];
+
+ for (int i = 0; i < numRows; i++) {
+ mirroredMatrix[i] = MatrixUtil.reverseRow(originalMatrix[i]);
+ }
+ return mirroredMatrix;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java
new file mode 100644
index 000000000000..2757da1f9023
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java
@@ -0,0 +1,53 @@
+package com.thealgorithms.matrix;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrintAMatrixInSpiralOrder {
+ /**
+ * Search a key in row and column wise sorted matrix
+ *
+ * @param matrix matrix to be searched
+ * @param row number of rows matrix has
+ * @param col number of columns matrix has
+ * @author Sadiul Hakim : https://github.com/sadiul-hakim
+ */
+ public List print(int[][] matrix, int row, int col) {
+
+ // r traverses matrix row wise from first
+ int r = 0;
+ // c traverses matrix column wise from first
+ int c = 0;
+ int i;
+ List result = new ArrayList<>();
+ while (r < row && c < col) {
+ // print first row of matrix
+ for (i = c; i < col; i++) {
+ result.add(matrix[r][i]);
+ }
+ // increase r by one because first row printed
+ r++;
+ // print last column
+ for (i = r; i < row; i++) {
+ result.add(matrix[i][col - 1]);
+ }
+ // decrease col by one because last column has been printed
+ col--;
+ // print rows from last except printed elements
+ if (r < row) {
+ for (i = col - 1; i >= c; i--) {
+ result.add(matrix[row - 1][i]);
+ }
+ row--;
+ }
+ // print columns from first except printed elements
+ if (c < col) {
+ for (i = row - 1; i >= r; i--) {
+ result.add(matrix[i][c]);
+ }
+ c++;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java b/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java
similarity index 98%
rename from src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java
rename to src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java
index 6ad0ef024342..9a7f255282ac 100644
--- a/src/main/java/com/thealgorithms/others/RotateMatrixBy90Degrees.java
+++ b/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.matrix;
import java.util.Scanner;
/**
diff --git a/src/main/java/com/thealgorithms/matrix/SolveSystem.java b/src/main/java/com/thealgorithms/matrix/SolveSystem.java
new file mode 100644
index 000000000000..9e683bc4dc5c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/SolveSystem.java
@@ -0,0 +1,71 @@
+package com.thealgorithms.matrix;
+
+/**
+ * This class implements an algorithm for solving a system of equations of the form Ax=b using gaussian elimination and back substitution.
+ *
+ * @link Gaussian Elimination Wiki
+ * @see InverseOfMatrix finds the full of inverse of a matrice, but is not required to solve a system.
+ */
+public final class SolveSystem {
+ private SolveSystem() {
+ }
+
+ /**
+ * Problem: Given a matrix A and vector b, solve the linear system Ax = b for the vector x.\
+ *
+ * This OVERWRITES the input matrix to save on memory
+ *
+ * @param matrix - a square matrix of doubles
+ * @param constants - an array of constant
+ * @return solutions
+ */
+ public static double[] solveSystem(double[][] matrix, double[] constants) {
+ final double tol = 0.00000001; // tolerance for round off
+ for (int k = 0; k < matrix.length - 1; k++) {
+ // find the largest value in column (to avoid zero pivots)
+ double maxVal = Math.abs(matrix[k][k]);
+ int maxIdx = k;
+ for (int j = k + 1; j < matrix.length; j++) {
+ if (Math.abs(matrix[j][k]) > maxVal) {
+ maxVal = matrix[j][k];
+ maxIdx = j;
+ }
+ }
+ if (Math.abs(maxVal) < tol) {
+ // hope the matrix works out
+ continue;
+ }
+ // swap rows
+ double[] temp = matrix[k];
+ matrix[k] = matrix[maxIdx];
+ matrix[maxIdx] = temp;
+ double tempConst = constants[k];
+ constants[k] = constants[maxIdx];
+ constants[maxIdx] = tempConst;
+ for (int i = k + 1; i < matrix.length; i++) {
+ // compute multipliers and save them in the column
+ matrix[i][k] /= matrix[k][k];
+ for (int j = k + 1; j < matrix.length; j++) {
+ matrix[i][j] -= matrix[i][k] * matrix[k][j];
+ }
+ constants[i] -= matrix[i][k] * constants[k];
+ }
+ }
+ // back substitution
+ double[] x = new double[constants.length];
+ System.arraycopy(constants, 0, x, 0, constants.length);
+ for (int i = matrix.length - 1; i >= 0; i--) {
+ double sum = 0;
+ for (int j = i + 1; j < matrix.length; j++) {
+ sum += matrix[i][j] * x[j];
+ }
+ x[i] = constants[i] - sum;
+ if (Math.abs(matrix[i][i]) > tol) {
+ x[i] /= matrix[i][i];
+ } else {
+ throw new IllegalArgumentException("Matrix was found to be singular");
+ }
+ }
+ return x;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java b/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java
new file mode 100644
index 000000000000..85852713b9ba
--- /dev/null
+++ b/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java
@@ -0,0 +1,42 @@
+package com.thealgorithms.matrix.matrixexponentiation;
+
+import com.thealgorithms.matrix.utils.MatrixUtil;
+import java.math.BigDecimal;
+
+/**
+ * @author Anirudh Buvanesh (https://github.com/anirudhb11) For more information
+ * see https://www.geeksforgeeks.org/matrix-exponentiation/
+ *
+ */
+public final class Fibonacci {
+ private Fibonacci() {
+ }
+
+ // Exponentiation matrix for Fibonacci sequence
+ private static final BigDecimal ONE = BigDecimal.valueOf(1);
+ private static final BigDecimal ZERO = BigDecimal.valueOf(0);
+
+ private static final BigDecimal[][] FIB_MATRIX = {{ONE, ONE}, {ONE, ZERO}};
+ private static final BigDecimal[][] IDENTITY_MATRIX = {{ONE, ZERO}, {ZERO, ONE}};
+
+ /**
+ * Calculates the fibonacci number using matrix exponentiaition technique
+ *
+ * @param n The input n for which we have to determine the fibonacci number
+ * Outputs the nth * fibonacci number
+ * @return a 2 X 1 array as { {F_n+1}, {F_n} }
+ */
+ public static BigDecimal[][] fib(int n) {
+ if (n == 0) {
+ return IDENTITY_MATRIX;
+ } else {
+ BigDecimal[][] cachedResult = fib(n / 2);
+ BigDecimal[][] matrixExpResult = MatrixUtil.multiply(cachedResult, cachedResult).get();
+ if (n % 2 == 0) {
+ return matrixExpResult;
+ } else {
+ return MatrixUtil.multiply(FIB_MATRIX, matrixExpResult).get();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/maths/MatrixUtil.java b/src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java
similarity index 62%
rename from src/main/java/com/thealgorithms/maths/MatrixUtil.java
rename to src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java
index 7e462f92e185..5ff9e37f6b9a 100644
--- a/src/main/java/com/thealgorithms/maths/MatrixUtil.java
+++ b/src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.maths;
+package com.thealgorithms.matrix.utils;
import java.math.BigDecimal;
import java.util.Optional;
@@ -10,6 +10,7 @@
* @date: 31 October 2021 (Sunday)
*/
public final class MatrixUtil {
+
private MatrixUtil() {
}
@@ -18,11 +19,52 @@ private static boolean isValid(final BigDecimal[][] matrix) {
}
private static boolean hasEqualSizes(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2) {
- return (isValid(matrix1) && isValid(matrix2) && matrix1.length == matrix2.length && matrix1[0].length == matrix2[0].length);
+ return isValid(matrix1) && isValid(matrix2) && matrix1.length == matrix2.length && matrix1[0].length == matrix2[0].length;
}
private static boolean canMultiply(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2) {
- return (isValid(matrix1) && isValid(matrix2) && matrix1[0].length == matrix2.length);
+ return isValid(matrix1) && isValid(matrix2) && matrix1[0].length == matrix2.length;
+ }
+
+ public static void validateInputMatrix(double[][] matrix) {
+ if (matrix == null) {
+ throw new IllegalArgumentException("The input matrix cannot be null");
+ }
+ if (matrix.length == 0) {
+ throw new IllegalArgumentException("The input matrix cannot be empty");
+ }
+ if (!hasValidRows(matrix)) {
+ throw new IllegalArgumentException("The input matrix cannot have null or empty rows");
+ }
+ if (isJaggedMatrix(matrix)) {
+ throw new IllegalArgumentException("The input matrix cannot be jagged");
+ }
+ }
+
+ private static boolean hasValidRows(double[][] matrix) {
+ for (double[] row : matrix) {
+ if (row == null || row.length == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @brief Checks if the input matrix is a jagged matrix.
+ * Jagged matrix is a matrix where the number of columns in each row is not the same.
+ *
+ * @param matrix The input matrix
+ * @return True if the input matrix is a jagged matrix, false otherwise
+ */
+ private static boolean isJaggedMatrix(double[][] matrix) {
+ int numColumns = matrix[0].length;
+ for (double[] row : matrix) {
+ if (row.length != numColumns) {
+ return true;
+ }
+ }
+ return false;
}
private static Optional operate(final BigDecimal[][] matrix1, final BigDecimal[][] matrix2, final BiFunction operation) {
@@ -80,4 +122,12 @@ public static Optional multiply(final BigDecimal[][] matrix1, fi
return Optional.of(result);
}
+
+ public static double[] reverseRow(final double[] inRow) {
+ double[] res = new double[inRow.length];
+ for (int i = 0; i < inRow.length; ++i) {
+ res[i] = inRow[inRow.length - 1 - i];
+ }
+ return res;
+ }
}
diff --git a/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java b/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java
deleted file mode 100644
index afd34933047a..000000000000
--- a/src/main/java/com/thealgorithms/matrixexponentiation/Fibonacci.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.thealgorithms.matrixexponentiation;
-
-import java.util.Scanner;
-
-/**
- * @author Anirudh Buvanesh (https://github.com/anirudhb11) For more information
- * see https://www.geeksforgeeks.org/matrix-exponentiation/
- *
- */
-public final class Fibonacci {
- private Fibonacci() {
- }
-
- // Exponentiation matrix for Fibonacci sequence
- private static final int[][] FIB_MATRIX = {{1, 1}, {1, 0}};
- private static final int[][] IDENTITY_MATRIX = {{1, 0}, {0, 1}};
- // First 2 fibonacci numbers
- private static final int[][] BASE_FIB_NUMBERS = {{1}, {0}};
-
- /**
- * Performs multiplication of 2 matrices
- *
- * @param matrix1
- * @param matrix2
- * @return The product of matrix1 and matrix2
- */
- private static int[][] matrixMultiplication(int[][] matrix1, int[][] matrix2) {
- // Check if matrices passed can be multiplied
- int rowsInMatrix1 = matrix1.length;
- int columnsInMatrix1 = matrix1[0].length;
-
- int rowsInMatrix2 = matrix2.length;
- int columnsInMatrix2 = matrix2[0].length;
-
- assert columnsInMatrix1 == rowsInMatrix2;
- int[][] product = new int[rowsInMatrix1][columnsInMatrix2];
- for (int rowIndex = 0; rowIndex < rowsInMatrix1; rowIndex++) {
- for (int colIndex = 0; colIndex < columnsInMatrix2; colIndex++) {
- int matrixEntry = 0;
- for (int intermediateIndex = 0; intermediateIndex < columnsInMatrix1; intermediateIndex++) {
- matrixEntry += matrix1[rowIndex][intermediateIndex] * matrix2[intermediateIndex][colIndex];
- }
- product[rowIndex][colIndex] = matrixEntry;
- }
- }
- return product;
- }
-
- /**
- * Calculates the fibonacci number using matrix exponentiaition technique
- *
- * @param n The input n for which we have to determine the fibonacci number
- * Outputs the nth * fibonacci number
- * @return a 2 X 1 array as { {F_n+1}, {F_n} }
- */
- public static int[][] fib(int n) {
- if (n == 0) {
- return Fibonacci.IDENTITY_MATRIX;
- } else {
- int[][] cachedResult = fib(n / 2);
- int[][] matrixExpResult = matrixMultiplication(cachedResult, cachedResult);
- if (n % 2 == 0) {
- return matrixExpResult;
- } else {
- return matrixMultiplication(Fibonacci.FIB_MATRIX, matrixExpResult);
- }
- }
- }
-
- public static void main(String[] args) {
- // Returns [0, 1, 1, 2, 3, 5 ..] for n = [0, 1, 2, 3, 4, 5.. ]
- Scanner sc = new Scanner(System.in);
- int n = sc.nextInt();
- int[][] result = matrixMultiplication(fib(n), BASE_FIB_NUMBERS);
- System.out.println("Fib(" + n + ") = " + result[1][0]);
- sc.close();
- }
-}
diff --git a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java b/src/main/java/com/thealgorithms/misc/MatrixTranspose.java
deleted file mode 100644
index 153cf4e9df99..000000000000
--- a/src/main/java/com/thealgorithms/misc/MatrixTranspose.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.thealgorithms.misc;
-
-import java.util.Scanner;
-
-/**
- *
- *
- * Find the Transpose of Matrix!
- *
- * Simply take input from the user and print the matrix before the transpose and
- * after the transpose.
- *
- *
- * Note: Giving proper comments in your program makes it more user
- * friendly and it is assumed as a high quality code.
- *
- * @author Rajat-Jain29
- * @version 11.0.9
- * @since 2014-03-31
- */
-public final class MatrixTranspose {
- private MatrixTranspose() {
- }
-
- public static void main(String[] args) {
- /*
- * This is the main method
- *
- * @param args Unused.
- *
- * @return Nothing.
- */
- Scanner sc = new Scanner(System.in);
- int i;
- int j;
- int row;
- int column;
- System.out.println("Enter the number of rows in the 2D matrix:");
-
- /*
- * Take input from user for how many rows to be print
- */
- row = sc.nextInt();
-
- System.out.println("Enter the number of columns in the 2D matrix:");
-
- /*
- * Take input from user for how many coloumn to be print
- */
- column = sc.nextInt();
- int[][] arr = new int[row][column];
- System.out.println("Enter the elements");
- for (i = 0; i < row; i++) {
- for (j = 0; j < column; j++) {
- arr[i][j] = sc.nextInt();
- }
- }
-
- /*
- * Print matrix before the Transpose in proper way
- */
- System.out.println("The matrix is:");
- for (i = 0; i < row; i++) {
- for (j = 0; j < column; j++) {
- System.out.print(arr[i][j] + "\t");
- }
- System.out.print("\n");
- }
-
- /*
- * Print matrix after the tranpose in proper way Transpose means Interchanging
- * of rows wth column so we interchange the rows in next loop Thus at last
- * matrix of transpose is obtained through user input...
- */
- System.out.println("The Transpose of the given matrix is:");
- for (i = 0; i < column; i++) {
- for (j = 0; j < row; j++) {
- System.out.print(arr[j][i] + "\t");
- }
- System.out.print("\n");
- }
- }
-}
diff --git a/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java b/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java
deleted file mode 100644
index 89dfce3fe049..000000000000
--- a/src/main/java/com/thealgorithms/misc/MirrorOfMatrix.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.thealgorithms.misc;
-
-// Problem Statement
-/*
-We have given an array of m x n (where m is the number of rows and n is the number of columns).
-Print the new matrix in such a way that the new matrix is the mirror image of the original matrix.
-
-The Original matrix is: | The Mirror matrix is:
-1 2 3 | 3 2 1
-4 5 6 | 6 5 4
-7 8 9 | 9 8 7
-
-@author - Aman (https://github.com/Aman28801)
-*/
-
-public final class MirrorOfMatrix {
- private MirrorOfMatrix() {
- }
-
- public static int[][] mirrorMatrix(final int[][] originalMatrix) {
- if (originalMatrix == null) {
- // Handle invalid input
- return null;
- }
- if (originalMatrix.length == 0) {
- return new int[0][0];
- }
-
- checkInput(originalMatrix);
-
- int numRows = originalMatrix.length;
- int numCols = originalMatrix[0].length;
-
- int[][] mirroredMatrix = new int[numRows][numCols];
-
- for (int i = 0; i < numRows; i++) {
- mirroredMatrix[i] = reverseRow(originalMatrix[i]);
- }
- return mirroredMatrix;
- }
- private static int[] reverseRow(final int[] inRow) {
- int[] res = new int[inRow.length];
- for (int i = 0; i < inRow.length; ++i) {
- res[i] = inRow[inRow.length - 1 - i];
- }
- return res;
- }
-
- private static void checkInput(final int[][] matrix) {
- // Check if all rows have the same number of columns
- for (int i = 1; i < matrix.length; i++) {
- if (matrix[i].length != matrix[0].length) {
- throw new IllegalArgumentException("The input is not a matrix.");
- }
- }
- }
-}
diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/misc/PalindromePrime.java
index e1cbf3ff839a..164e957a9d12 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java
+++ b/src/main/java/com/thealgorithms/misc/PalindromePrime.java
@@ -1,51 +1,57 @@
package com.thealgorithms.misc;
-import java.util.Scanner;
+import java.util.ArrayList;
+import java.util.List;
public final class PalindromePrime {
private PalindromePrime() {
}
- public static void main(String[] args) { // Main function
- Scanner in = new Scanner(System.in);
- System.out.println("Enter the quantity of First Palindromic Primes you want");
- int n = in.nextInt(); // Input of how many first palindromic prime we want
- functioning(n); // calling function - functioning
- in.close();
- }
+ public static boolean prime(int num) {
+ if (num < 2) {
+ return false; // Handle edge case for numbers < 2
+ }
+ if (num == 2) {
+ return true; // 2 is prime
+ }
+ if (num % 2 == 0) {
+ return false; // Even numbers > 2 are not prime
+ }
- public static boolean prime(int num) { // checking if number is prime or not
for (int divisor = 3; divisor <= Math.sqrt(num); divisor += 2) {
if (num % divisor == 0) {
- return false; // false if not prime
+ return false;
}
}
- return true; // True if prime
+ return true;
}
- public static int reverse(int n) { // Returns the reverse of the number
+ public static int reverse(int n) {
int reverse = 0;
while (n != 0) {
- reverse *= 10;
- reverse += n % 10;
+ reverse = reverse * 10 + (n % 10);
n /= 10;
}
return reverse;
}
- public static void functioning(int y) {
- if (y == 0) {
- return;
+ public static List generatePalindromePrimes(int n) {
+ List palindromicPrimes = new ArrayList<>();
+ if (n <= 0) {
+ return palindromicPrimes; // Handle case for 0 or negative input
}
- System.out.print(2 + "\n"); // print the first Palindromic Prime
+
+ palindromicPrimes.add(2); // 2 is the first palindromic prime
int count = 1;
int num = 3;
- while (count < y) {
- if (num == reverse(num) && prime(num)) { // number is prime and it's reverse is same
- count++; // counts check when to terminate while loop
- System.out.print(num + "\n"); // print the Palindromic Prime
+
+ while (count < n) {
+ if (num == reverse(num) && prime(num)) {
+ palindromicPrimes.add(num);
+ count++;
}
- num += 2; // inrease iterator value by two
+ num += 2; // Skip even numbers
}
+ return palindromicPrimes;
}
}
diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
index 07286b39f2e4..51dc099ba32e 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
@@ -1,6 +1,5 @@
package com.thealgorithms.misc;
-import com.thealgorithms.datastructures.lists.SinglyLinkedList;
import java.util.Stack;
/**
@@ -15,8 +14,8 @@ public final class PalindromeSinglyLinkedList {
private PalindromeSinglyLinkedList() {
}
- public static boolean isPalindrome(final SinglyLinkedList linkedList) {
- Stack linkedListValues = new Stack<>();
+ public static boolean isPalindrome(final Iterable linkedList) {
+ var linkedListValues = new Stack<>();
for (final var x : linkedList) {
linkedListValues.push(x);
@@ -30,4 +29,47 @@ public static boolean isPalindrome(final SinglyLinkedList linkedList) {
return true;
}
+
+ // Optimised approach with O(n) time complexity and O(1) space complexity
+
+ public static boolean isPalindromeOptimised(Node head) {
+ if (head == null || head.next == null) {
+ return true;
+ }
+ Node slow = head;
+ Node fast = head;
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+ Node midNode = slow;
+
+ Node prevNode = null;
+ Node currNode = midNode;
+ Node nextNode;
+ while (currNode != null) {
+ nextNode = currNode.next;
+ currNode.next = prevNode;
+ prevNode = currNode;
+ currNode = nextNode;
+ }
+ Node left = head;
+ Node right = prevNode;
+ while (left != null && right != null) {
+ if (left.val != right.val) {
+ return false;
+ }
+ right = right.next;
+ left = left.next;
+ }
+ return true;
+ }
+ static class Node {
+ int val;
+ Node next;
+ Node(int val) {
+ this.val = val;
+ this.next = null;
+ }
+ }
}
diff --git a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java
index 0dfc8ac32a6f..6d3caa1814b6 100644
--- a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java
+++ b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java
@@ -1,18 +1,9 @@
package com.thealgorithms.misc;
-import java.util.Arrays;
-
public final class RangeInSortedArray {
private RangeInSortedArray() {
}
- public static void main(String[] args) {
- // Testcases
- assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 3), new int[] {2, 4});
- assert Arrays.equals(sortedRange(new int[] {1, 2, 3, 3, 3, 4, 5}, 4), new int[] {5, 5});
- assert Arrays.equals(sortedRange(new int[] {0, 1, 2}, 3), new int[] {-1, -1});
- }
-
// Get the 1st and last occurrence index of a number 'key' in a non-decreasing array 'nums'
// Gives [-1, -1] in case element doesn't exist in array
public static int[] sortedRange(int[] nums, int key) {
diff --git a/src/main/java/com/thealgorithms/misc/ShuffleArray.java b/src/main/java/com/thealgorithms/misc/ShuffleArray.java
new file mode 100644
index 000000000000..65d38adfc37a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/misc/ShuffleArray.java
@@ -0,0 +1,38 @@
+package com.thealgorithms.misc;
+
+import java.util.Random;
+
+/**
+ * The Fisher-Yates (Knuth) Shuffle algorithm randomly permutes an array's
+ * elements, ensuring each permutation is equally likely.
+ *
+ *
+ * Worst-case performance O(n)
+ * Best-case performance O(n)
+ * Average performance O(n)
+ * Worst-case space complexity O(1)
+ *
+ * This class provides a static method to shuffle an array in place.
+ *
+ * @author Rashi Dashore (https://github.com/rashi07dashore)
+ */
+public final class ShuffleArray {
+ // Prevent instantiation
+ private ShuffleArray() {
+ }
+
+ /**
+ * This method shuffles an array using the Fisher-Yates algorithm.
+ *
+ * @param arr is the input array to be shuffled
+ */
+ public static void shuffle(int[] arr) {
+ Random random = new Random();
+ for (int i = arr.length - 1; i > 0; i--) {
+ int j = random.nextInt(i + 1);
+ int temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/misc/Sparsity.java b/src/main/java/com/thealgorithms/misc/Sparsity.java
index cae2fbdead94..08e50a121da4 100644
--- a/src/main/java/com/thealgorithms/misc/Sparsity.java
+++ b/src/main/java/com/thealgorithms/misc/Sparsity.java
@@ -1,7 +1,5 @@
package com.thealgorithms.misc;
-import java.util.Scanner;
-
/*
*A matrix is sparse if many of its coefficients are zero (In general if 2/3rd of matrix elements
*are 0, it is considered as sparse). The interest in sparsity arises because its exploitation can
@@ -16,12 +14,17 @@ private Sparsity() {
}
/*
+ * @param mat the input matrix
* @return Sparsity of matrix
*
* where sparsity = number of zeroes/total elements in matrix
*
*/
static double sparsity(double[][] mat) {
+ if (mat == null || mat.length == 0) {
+ throw new IllegalArgumentException("Matrix cannot be null or empty");
+ }
+
int zero = 0;
// Traversing the matrix to count number of zeroes
for (int i = 0; i < mat.length; i++) {
@@ -32,25 +35,6 @@ static double sparsity(double[][] mat) {
}
}
// return sparsity
- return ((double) zero / (mat.length * mat[1].length));
- }
-
- // Driver method
- public static void main(String[] args) {
- Scanner in = new Scanner(System.in);
- System.out.println("Enter number of rows in matrix: ");
- int n = in.nextInt();
- System.out.println("Enter number of Columns in matrix: ");
- int m = in.nextInt();
-
- System.out.println("Enter Matrix elements: ");
- double[][] mat = new double[n][m];
- for (int i = 0; i < n; i++) {
- for (int j = 0; j < m; j++) {
- mat[i][j] = in.nextDouble();
- }
- }
- System.out.println("Sparsity of matrix is: " + sparsity(mat));
- in.close();
+ return ((double) zero / (mat.length * mat[0].length));
}
}
diff --git a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java
index 1c5f4a440532..8ef10758ef80 100644
--- a/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java
+++ b/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java
@@ -7,29 +7,10 @@
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Scanner;
import java.util.Set;
public class ThreeSumProblem {
- public static void main(String[] args) {
- Scanner scan = new Scanner(System.in);
- System.out.print("Enter the target sum ");
- int ts = scan.nextInt();
- System.out.print("Enter the number of elements in the array ");
- int n = scan.nextInt();
- System.out.println("Enter all your array elements:");
- int[] arr = new int[n];
- for (int i = 0; i < n; i++) {
- arr[i] = scan.nextInt();
- }
- ThreeSumProblem th = new ThreeSumProblem();
- System.out.println("Brute Force Approach\n" + (th.bruteForce(arr, ts)) + "\n");
- System.out.println("Two Pointer Approach\n" + (th.twoPointer(arr, ts)) + "\n");
- System.out.println("Hashmap Approach\n" + (th.hashMap(arr, ts)));
- scan.close();
- }
-
public List> bruteForce(int[] nums, int target) {
List> arr = new ArrayList>();
diff --git a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java
index bfa8828e250b..284a290a5af8 100644
--- a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java
+++ b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java
@@ -25,8 +25,6 @@ public class CRCAlgorithm {
private ArrayList message;
- private ArrayList dividedMessage;
-
private ArrayList p;
private Random randomGenerator;
@@ -44,7 +42,6 @@ public CRCAlgorithm(String str, int size, double ber) {
messageChanged = false;
message = new ArrayList<>();
messSize = size;
- dividedMessage = new ArrayList<>();
p = new ArrayList<>();
for (int i = 0; i < str.length(); i++) {
p.add(Character.getNumericValue(str.charAt(i)));
@@ -103,7 +100,6 @@ public int getCorrectMess() {
public void refactor() {
messageChanged = false;
message = new ArrayList<>();
- dividedMessage = new ArrayList<>();
}
/**
@@ -156,7 +152,7 @@ public void divideMessageWithP(boolean check) {
}
}
}
- dividedMessage = (ArrayList) x.clone();
+ ArrayList dividedMessage = (ArrayList) x.clone();
if (!check) {
message.addAll(dividedMessage);
} else {
diff --git a/src/main/java/com/thealgorithms/others/FibbonaciSeries.java b/src/main/java/com/thealgorithms/others/FibbonaciSeries.java
deleted file mode 100644
index a4815296e547..000000000000
--- a/src/main/java/com/thealgorithms/others/FibbonaciSeries.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.thealgorithms.others;
-
-import java.util.Scanner;
-
-/**
- * Fibonacci sequence, and characterized by the fact that every number after the
- * first two is the sum of the two preceding ones.
- *
- *
- * Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21,...
- *
- *
- * Source for the explanation: https://en.wikipedia.org/wiki/Fibonacci_number
- *
- * Problem Statement: print all Fibonacci numbers that are smaller than your
- * given input N
- */
-public final class FibbonaciSeries {
- private FibbonaciSeries() {
- }
-
- public static void main(String[] args) {
- // Get input from the user
- Scanner scan = new Scanner(System.in);
- int n = scan.nextInt();
- int first = 0;
- int second = 1;
- scan.close();
- while (first <= n) {
- // print first fibo 0 then add second fibo into it while updating second as well
- System.out.println(first);
- int next = first + second;
- first = second;
- second = next;
- }
- }
-}
diff --git a/src/main/java/com/thealgorithms/others/KochSnowflake.java b/src/main/java/com/thealgorithms/others/KochSnowflake.java
index 0e2600a7d72f..10986aabec4f 100644
--- a/src/main/java/com/thealgorithms/others/KochSnowflake.java
+++ b/src/main/java/com/thealgorithms/others/KochSnowflake.java
@@ -7,6 +7,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
import javax.imageio.ImageIO;
/**
@@ -104,7 +105,7 @@ public static BufferedImage getKochSnowflake(int imageWidth, int steps) {
double offsetX = imageWidth / 10.;
double offsetY = imageWidth / 3.7;
Vector2 vector1 = new Vector2(offsetX, offsetY);
- Vector2 vector2 = new Vector2(imageWidth / 2, Math.sin(Math.PI / 3) * imageWidth * 0.8 + offsetY);
+ Vector2 vector2 = new Vector2(imageWidth / 2.0, Math.sin(Math.PI / 3.0) * imageWidth * 0.8 + offsetY);
Vector2 vector3 = new Vector2(imageWidth - offsetX, offsetY);
ArrayList initialVectors = new ArrayList();
initialVectors.add(vector1);
@@ -125,7 +126,7 @@ public static BufferedImage getKochSnowflake(int imageWidth, int steps) {
* applied.
* @return The transformed vectors after the iteration-step.
*/
- private static ArrayList iterationStep(ArrayList vectors) {
+ private static ArrayList iterationStep(List vectors) {
ArrayList newVectors = new ArrayList();
for (int i = 0; i < vectors.size() - 1; i++) {
Vector2 startVector = vectors.get(i);
diff --git a/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java b/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java
new file mode 100644
index 000000000000..d0b2c2a0e56d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java
@@ -0,0 +1,56 @@
+package com.thealgorithms.others;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Maximum Sliding Window Algorithm
+ *
+ * This algorithm finds the maximum element in each sliding window of size k
+ * in a given array of integers. It uses a deque (double-ended queue) to
+ * efficiently keep track of potential maximum values in the current window.
+ *
+ * Time Complexity: O(n), where n is the number of elements in the input array
+ * Space Complexity: O(k), where k is the size of the sliding window
+ */
+
+public class MaximumSlidingWindow {
+
+ /**
+ * Finds the maximum values in each sliding window of size k.
+ *
+ * @param nums The input array of integers
+ * @param windowSize The size of the sliding window
+ * @return An array of integers representing the maximums in each window
+ */
+ public int[] maxSlidingWindow(int[] nums, int windowSize) {
+ if (nums == null || nums.length == 0 || windowSize <= 0 || windowSize > nums.length) {
+ return new int[0]; // Handle edge cases
+ }
+
+ int[] result = new int[nums.length - windowSize + 1];
+ Deque deque = new ArrayDeque<>();
+
+ for (int currentIndex = 0; currentIndex < nums.length; currentIndex++) {
+
+ // Remove the first element if it's outside the current window
+ if (!deque.isEmpty() && deque.peekFirst() == currentIndex - windowSize) {
+ deque.pollFirst();
+ }
+
+ // Remove all elements smaller than the current element from the end
+ while (!deque.isEmpty() && nums[deque.peekLast()] < nums[currentIndex]) {
+ deque.pollLast();
+ }
+
+ // Add the current element's index to the deque
+ deque.offerLast(currentIndex);
+
+ // If we have processed at least k elements, add to result
+ if (currentIndex >= windowSize - 1) {
+ result[currentIndex - windowSize + 1] = nums[deque.peekFirst()];
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java b/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java
index ddc37a916cbf..abfdd006879e 100644
--- a/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java
+++ b/src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java
@@ -12,43 +12,33 @@ public class PrintAMatrixInSpiralOrder {
* @param col number of columns matrix has
* @author Sadiul Hakim : https://github.com/sadiul-hakim
*/
-
public List print(int[][] matrix, int row, int col) {
-
// r traverses matrix row wise from first
int r = 0;
// c traverses matrix column wise from first
int c = 0;
int i;
-
List result = new ArrayList<>();
-
while (r < row && c < col) {
// print first row of matrix
for (i = c; i < col; i++) {
result.add(matrix[r][i]);
}
-
// increase r by one because first row printed
r++;
-
// print last column
for (i = r; i < row; i++) {
result.add(matrix[i][col - 1]);
}
-
// decrease col by one because last column has been printed
col--;
-
// print rows from last except printed elements
if (r < row) {
for (i = col - 1; i >= c; i--) {
result.add(matrix[row - 1][i]);
}
-
row--;
}
-
// print columns from first except printed elements
if (c < col) {
for (i = row - 1; i >= r; i--) {
diff --git a/src/main/java/com/thealgorithms/others/TowerOfHanoi.java b/src/main/java/com/thealgorithms/others/TowerOfHanoi.java
deleted file mode 100644
index 2216799b987a..000000000000
--- a/src/main/java/com/thealgorithms/others/TowerOfHanoi.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.thealgorithms.others;
-
-import java.util.Scanner;
-
-final class TowerOfHanoi {
- private TowerOfHanoi() {
- }
-
- public static void shift(int n, String startPole, String intermediatePole, String endPole) {
- // if n becomes zero the program returns thus ending the loop.
- if (n != 0) {
- // Shift function is called in recursion for swapping the n-1 disc from the startPole to
- // the intermediatePole
- shift(n - 1, startPole, endPole, intermediatePole);
- System.out.format("Move %d from %s to %s%n", n, startPole, endPole); // Result Printing
- // Shift function is called in recursion for swapping the n-1 disc from the
- // intermediatePole to the endPole
- shift(n - 1, intermediatePole, startPole, endPole);
- }
- }
-
- public static void main(String[] args) {
- System.out.print("Enter number of discs on Pole 1: ");
- Scanner scanner = new Scanner(System.in);
- int numberOfDiscs = scanner.nextInt(); // input of number of discs on pole 1
- shift(numberOfDiscs, "Pole1", "Pole2", "Pole3"); // Shift function called
- scanner.close();
- }
-}
diff --git a/src/main/java/com/thealgorithms/others/Sudoku.java b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java
similarity index 99%
rename from src/main/java/com/thealgorithms/others/Sudoku.java
rename to src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java
index 0e88aee46f4d..fce665c4de00 100644
--- a/src/main/java/com/thealgorithms/others/Sudoku.java
+++ b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.others;
+package com.thealgorithms.puzzlesandgames;
/**
* A class that provides methods to solve Sudoku puzzles of any n x n size
diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
new file mode 100644
index 000000000000..72e9a14ac070
--- /dev/null
+++ b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
@@ -0,0 +1,65 @@
+package com.thealgorithms.puzzlesandgames;
+
+import java.util.List;
+
+/**
+ * The {@code TowerOfHanoi} class provides a recursive solution to the Tower of Hanoi puzzle.
+ * This puzzle involves moving a set of discs from one pole to another, following specific rules:
+ * 1. Only one disc can be moved at a time.
+ * 2. A disc can only be placed on top of a larger disc.
+ * 3. All discs must start on one pole and end on another.
+ *
+ * This implementation recursively calculates the steps required to solve the puzzle and stores them
+ * in a provided list.
+ *
+ *
+ * For more information about the Tower of Hanoi, see
+ * Tower of Hanoi on Wikipedia.
+ *
+ *
+ * The {@code shift} method takes the number of discs and the names of the poles,
+ * and appends the steps required to solve the puzzle to the provided list.
+ * Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem.
+ * Space Complexity: O(n) - Linear space complexity due to the recursion stack.
+ * Wikipedia: https://en.wikipedia.org/wiki/Tower_of_Hanoi
+ */
+final class TowerOfHanoi {
+
+ private TowerOfHanoi() {
+ }
+
+ /**
+ * Recursively solve the Tower of Hanoi puzzle by moving discs between poles.
+ *
+ * @param n The number of discs to move.
+ * @param startPole The name of the start pole from which discs are moved.
+ * @param intermediatePole The name of the intermediate pole used as a temporary holding area.
+ * @param endPole The name of the end pole to which discs are moved.
+ * @param result A list to store the steps required to solve the puzzle.
+ *
+ *
+ * This method is called recursively to move n-1 discs
+ * to the intermediate pole,
+ * then moves the nth disc to the end pole, and finally
+ * moves the n-1 discs from the
+ * intermediate pole to the end pole.
+ *
+ *
+ *
+ * Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem.
+ * Space Complexity: O(n) - Linear space complexity due to the recursion stack.
+ *
+ */
+ public static void shift(int n, String startPole, String intermediatePole, String endPole, List result) {
+ if (n != 0) {
+ // Move n-1 discs from startPole to intermediatePole
+ shift(n - 1, startPole, endPole, intermediatePole, result);
+
+ // Add the move of the nth disc from startPole to endPole
+ result.add(String.format("Move %d from %s to %s", n, startPole, endPole));
+
+ // Move the n-1 discs from intermediatePole to endPole
+ shift(n - 1, intermediatePole, startPole, endPole, result);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/misc/WordBoggle.java b/src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java
similarity index 75%
rename from src/main/java/com/thealgorithms/misc/WordBoggle.java
rename to src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java
index 3eb0dc95ffb5..ca1430f744ab 100644
--- a/src/main/java/com/thealgorithms/misc/WordBoggle.java
+++ b/src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java
@@ -1,7 +1,6 @@
-package com.thealgorithms.misc;
+package com.thealgorithms.puzzlesandgames;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -9,9 +8,9 @@
import java.util.Set;
public final class WordBoggle {
+
private WordBoggle() {
}
-
/**
* O(nm * 8^s + ws) time where n = width of boggle board, m = height of
* boggle board, s = length of longest word in string array, w = length of
@@ -32,36 +31,6 @@ public static List boggleBoard(char[][] board, String[] words) {
return new ArrayList<>(finalWords);
}
- public static void main(String[] args) {
- // Testcase
- List ans = new ArrayList<>(Arrays.asList("a", "boggle", "this", "NOTRE_PEATED", "is", "simple", "board"));
- assert (boggleBoard(
- new char[][] {
- {'t', 'h', 'i', 's', 'i', 's', 'a'},
- {'s', 'i', 'm', 'p', 'l', 'e', 'x'},
- {'b', 'x', 'x', 'x', 'x', 'e', 'b'},
- {'x', 'o', 'g', 'g', 'l', 'x', 'o'},
- {'x', 'x', 'x', 'D', 'T', 'r', 'a'},
- {'R', 'E', 'P', 'E', 'A', 'd', 'x'},
- {'x', 'x', 'x', 'x', 'x', 'x', 'x'},
- {'N', 'O', 'T', 'R', 'E', '_', 'P'},
- {'x', 'x', 'D', 'E', 'T', 'A', 'E'},
- },
- new String[] {
- "this",
- "is",
- "not",
- "a",
- "simple",
- "test",
- "boggle",
- "board",
- "REPEATED",
- "NOTRE_PEATED",
- })
- .equals(ans));
- }
-
public static void explore(int i, int j, char[][] board, TrieNode trieNode, boolean[][] visited, Set finalWords) {
if (visited[i][j]) {
return;
diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java
new file mode 100644
index 000000000000..14f1f97450a0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java
@@ -0,0 +1,195 @@
+package com.thealgorithms.randomized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Implementation of Karger's Minimum Cut algorithm.
+ *
+ * Karger's algorithm is a randomized algorithm to compute the minimum cut of a connected graph.
+ * A minimum cut is the smallest set of edges that, if removed, would split the graph into two
+ * disconnected components.
+ *
+ *
The algorithm works by repeatedly contracting random edges in the graph until only two
+ * nodes remain. The edges between these two nodes represent a cut. By running the algorithm
+ * multiple times and keeping track of the smallest cut found, the probability of finding the
+ * true minimum cut increases.
+ *
+ *
Key steps of the algorithm:
+ *
+ * - Randomly select an edge and contract it, merging the two nodes into one.
+ * - Repeat the contraction process until only two nodes remain.
+ * - Count the edges between the two remaining nodes to determine the cut size.
+ * - Repeat the process multiple times to improve the likelihood of finding the true minimum cut.
+ *
+ *
+ * See more: Karger's algorithm
+ *
+ * @author MuhammadEzzatHBK
+ */
+public final class KargerMinCut {
+
+ /**
+ * Output of the Karger algorithm.
+ *
+ * @param first The first set of nodes in the cut.
+ * @param second The second set of nodes in the cut.
+ * @param minCut The size of the minimum cut.
+ */
+ public record KargerOutput(Set first, Set second, int minCut) {
+ }
+
+ private KargerMinCut() {
+ }
+
+ public static KargerOutput findMinCut(Collection nodeSet, List edges) {
+ return findMinCut(nodeSet, edges, 100);
+ }
+
+ /**
+ * Finds the minimum cut of a graph using Karger's algorithm.
+ *
+ * @param nodeSet: Input graph nodes
+ * @param edges: Input graph edges
+ * @param iterations: Iterations to run the algorithms for, more iterations = more accuracy
+ * @return A KargerOutput object containing the two sets of nodes and the size of the minimum cut.
+ */
+ public static KargerOutput findMinCut(Collection nodeSet, List edges, int iterations) {
+ Graph graph = new Graph(nodeSet, edges);
+ KargerOutput minCut = new KargerOutput(new HashSet<>(), new HashSet<>(), Integer.MAX_VALUE);
+ KargerOutput output;
+
+ // Run the algorithm multiple times to increase the probability of finding
+ for (int i = 0; i < iterations; i++) {
+ Graph clone = graph.copy();
+ output = clone.findMinCut();
+ if (output.minCut < minCut.minCut) {
+ minCut = output;
+ }
+ }
+ return minCut;
+ }
+
+ private static class DisjointSetUnion {
+ private final int[] parent;
+ int setCount;
+
+ DisjointSetUnion(int size) {
+ parent = new int[size];
+ for (int i = 0; i < size; i++) {
+ parent[i] = i;
+ }
+ setCount = size;
+ }
+
+ int find(int i) {
+ // If it's not its own parent, then it's not the root of its set
+ if (parent[i] != i) {
+ // Recursively find the root of its parent
+ // and update i's parent to point directly to the root (path compression)
+ parent[i] = find(parent[i]);
+ }
+
+ // Return the root (representative) of the set
+ return parent[i];
+ }
+
+ void union(int u, int v) {
+ // Find the root of each node
+ int rootU = find(u);
+ int rootV = find(v);
+
+ // If they belong to different sets, merge them
+ if (rootU != rootV) {
+ // Make rootV point to rootU — merge the two sets
+ parent[rootV] = rootU;
+
+ // Reduce the count of disjoint sets by 1
+ setCount--;
+ }
+ }
+
+ boolean inSameSet(int u, int v) {
+ return find(u) == find(v);
+ }
+
+ /*
+ This is a verbosity method, it's not a part of the core algorithm,
+ But it helps us provide more useful output.
+ */
+ Set getAnySet() {
+ int aRoot = find(0); // Get one of the two roots
+
+ Set set = new HashSet<>();
+ for (int i = 0; i < parent.length; i++) {
+ if (find(i) == aRoot) {
+ set.add(i);
+ }
+ }
+
+ return set;
+ }
+ }
+
+ private static class Graph {
+ private final List nodes;
+ private final List edges;
+
+ Graph(Collection nodeSet, List edges) {
+ this.nodes = new ArrayList<>(nodeSet);
+ this.edges = new ArrayList<>();
+ for (int[] e : edges) {
+ this.edges.add(new int[] {e[0], e[1]});
+ }
+ }
+
+ Graph copy() {
+ return new Graph(this.nodes, this.edges);
+ }
+
+ KargerOutput findMinCut() {
+ DisjointSetUnion dsu = new DisjointSetUnion(nodes.size());
+ List workingEdges = new ArrayList<>(edges);
+
+ Random rand = new Random();
+
+ while (dsu.setCount > 2) {
+ int[] e = workingEdges.get(rand.nextInt(workingEdges.size()));
+ if (!dsu.inSameSet(e[0], e[1])) {
+ dsu.union(e[0], e[1]);
+ }
+ }
+
+ int cutEdges = 0;
+ for (int[] e : edges) {
+ if (!dsu.inSameSet(e[0], e[1])) {
+ cutEdges++;
+ }
+ }
+
+ return collectResult(dsu, cutEdges);
+ }
+
+ /*
+ This is a verbosity method, it's not a part of the core algorithm,
+ But it helps us provide more useful output.
+ */
+ private KargerOutput collectResult(DisjointSetUnion dsu, int cutEdges) {
+ Set firstIndices = dsu.getAnySet();
+ Set firstSet = new HashSet<>();
+ Set secondSet = new HashSet<>();
+ for (int i = 0; i < nodes.size(); i++) {
+ if (firstIndices.contains(i)) {
+ firstSet.add(nodes.get(i));
+ } else {
+ secondSet.add(nodes.get(i));
+ }
+ }
+ return new KargerOutput(firstSet, secondSet, cutEdges);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
new file mode 100644
index 000000000000..05d7abbbcd6c
--- /dev/null
+++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java
@@ -0,0 +1,82 @@
+package com.thealgorithms.randomized;
+
+import java.util.Random;
+import java.util.function.Function;
+
+/**
+ * A demonstration of the Monte Carlo integration algorithm in Java.
+ *
+ * This class estimates the value of definite integrals using randomized sampling,
+ * also known as the Monte Carlo method. It is particularly effective for:
+ *
+ * - Functions that are difficult or impossible to integrate analytically
+ * - High-dimensional integrals where traditional methods are inefficient
+ * - Simulation and probabilistic analysis tasks
+ *
+ *
+ * The core idea is to sample random points uniformly from the integration domain,
+ * evaluate the function at those points, and compute the scaled average to estimate the integral.
+ *
+ *
For a one-dimensional integral over [a, b], the approximation is the function range (b-a),
+ * multiplied by the function average result for a random sample.
+ * See more: Monte Carlo Integration
+ *
+ * @author: MuhammadEzzatHBK
+ */
+
+public final class MonteCarloIntegration {
+
+ private MonteCarloIntegration() {
+ }
+
+ /**
+ * Approximates the definite integral of a given function over a specified
+ * interval using the Monte Carlo method with a fixed random seed for
+ * reproducibility.
+ *
+ * @param fx the function to integrate
+ * @param a the lower bound of the interval
+ * @param b the upper bound of the interval
+ * @param n the number of random samples to use
+ * @param seed the seed for the random number generator
+ * @return the approximate value of the integral
+ */
+ public static double approximate(Function fx, double a, double b, int n, long seed) {
+ return doApproximate(fx, a, b, n, new Random(seed));
+ }
+
+ /**
+ * Approximates the definite integral of a given function over a specified
+ * interval using the Monte Carlo method with a random seed based on the
+ * current system time for more randomness.
+ *
+ * @param fx the function to integrate
+ * @param a the lower bound of the interval
+ * @param b the upper bound of the interval
+ * @param n the number of random samples to use
+ * @return the approximate value of the integral
+ */
+ public static double approximate(Function fx, double a, double b, int n) {
+ return doApproximate(fx, a, b, n, new Random(System.currentTimeMillis()));
+ }
+
+ private static double doApproximate(Function fx, double a, double b, int n, Random generator) {
+ if (!validate(fx, a, b, n)) {
+ throw new IllegalArgumentException("Invalid input parameters");
+ }
+ double totalArea = 0.0;
+ double interval = b - a;
+ for (int i = 0; i < n; i++) {
+ double x = a + generator.nextDouble() * interval;
+ totalArea += fx.apply(x);
+ }
+ return interval * totalArea / n;
+ }
+
+ private static boolean validate(Function fx, double a, double b, int n) {
+ boolean isFunctionValid = fx != null;
+ boolean isIntervalValid = a < b;
+ boolean isSampleSizeValid = n > 0;
+ return isFunctionValid && isIntervalValid && isSampleSizeValid;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java
new file mode 100644
index 000000000000..e9af223a0622
--- /dev/null
+++ b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java
@@ -0,0 +1,68 @@
+package com.thealgorithms.randomized;
+
+/**
+ * This class implements the Randomized QuickSort algorithm.
+ * It selects a pivot randomly to improve performance on sorted or nearly sorted data.
+ * @author Vibhu Khera
+ */
+public final class RandomizedQuickSort {
+
+ private RandomizedQuickSort() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Sorts the array using the randomized quicksort algorithm.
+ *
+ * @param arr the array to sort
+ * @param low the starting index of the array
+ * @param high the ending index of the array
+ */
+ public static void randomizedQuickSort(int[] arr, int low, int high) {
+ if (low < high) {
+ int pivotIndex = partition(arr, low, high);
+ randomizedQuickSort(arr, low, pivotIndex - 1);
+ randomizedQuickSort(arr, pivotIndex + 1, high);
+ }
+ }
+
+ /**
+ * Partitions the array around a pivot chosen randomly.
+ *
+ * @param arr the array to partition
+ * @param low the starting index
+ * @param high the ending index
+ * @return the index of the pivot after partitioning
+ */
+ private static int partition(int[] arr, int low, int high) {
+ int pivotIndex = low + (int) (Math.random() * (high - low + 1));
+ int pivotValue = arr[pivotIndex];
+ swap(arr, pivotIndex, high); // Move pivot to end
+ int storeIndex = low;
+ for (int i = low; i < high; i++) {
+ if (arr[i] < pivotValue) {
+ swap(arr, storeIndex, i);
+ storeIndex++;
+ }
+ }
+ swap(arr, storeIndex, high); // Move pivot to its final place
+ return storeIndex;
+ }
+
+ /**
+ * Swaps two elements in the array, only if the indices are different.
+ *
+ * @param arr the array in which elements are to be swapped
+ * @param i the first index
+ * @param j the second index
+ */
+ private static void swap(int[] arr, int i, int j) {
+ // Skip if indices are the same
+ if (i == j) {
+ return;
+ }
+ int temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java
new file mode 100644
index 000000000000..05e70f635055
--- /dev/null
+++ b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.randomized;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Reservoir Sampling Algorithm
+ *
+ * Use Case:
+ * - Efficient for selecting k random items from a stream of unknown size
+ * - Used in streaming systems, big data, and memory-limited environments
+ *
+ * Time Complexity: O(n)
+ * Space Complexity: O(k)
+ *
+ * @author Michael Alexander Montoya (@cureprotocols)
+ * @see Reservoir Sampling - Wikipedia
+ */
+public final class ReservoirSampling {
+
+ // Prevent instantiation of utility class
+ private ReservoirSampling() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Selects k random elements from a stream using reservoir sampling.
+ *
+ * @param stream The input stream as an array of integers.
+ * @param sampleSize The number of elements to sample.
+ * @return A list containing k randomly selected elements.
+ */
+ public static List sample(int[] stream, int sampleSize) {
+ if (sampleSize > stream.length) {
+ throw new IllegalArgumentException("Sample size cannot exceed stream size.");
+ }
+
+ List reservoir = new ArrayList<>(sampleSize);
+ Random rand = new Random();
+
+ for (int i = 0; i < stream.length; i++) {
+ if (i < sampleSize) {
+ reservoir.add(stream[i]);
+ } else {
+ int j = rand.nextInt(i + 1);
+ if (j < sampleSize) {
+ reservoir.set(j, stream[i]);
+ }
+ }
+ }
+
+ return reservoir;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
new file mode 100644
index 000000000000..e5f474085367
--- /dev/null
+++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java
@@ -0,0 +1,21 @@
+package com.thealgorithms.recursion;
+
+/*
+ The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones,
+ starting with 0 and 1.
+ NUMBER 0 1 2 3 4 5 6 7 8 9 10 ...
+ FIBONACCI 0 1 1 2 3 5 8 13 21 34 55 ...
+*/
+
+public final class FibonacciSeries {
+ private FibonacciSeries() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+ public static int fibonacci(int n) {
+ if (n <= 1) {
+ return n;
+ } else {
+ return fibonacci(n - 1) + fibonacci(n - 2);
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java
similarity index 96%
rename from src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java
rename to src/main/java/com/thealgorithms/recursion/GenerateSubsets.java
index 417bf1307790..5a3ff2e88040 100644
--- a/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java
+++ b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java
@@ -1,4 +1,4 @@
-package com.thealgorithms.Recursion;
+package com.thealgorithms.recursion;
// program to find power set of a string
diff --git a/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java b/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java
new file mode 100644
index 000000000000..1e5512be9edd
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.scheduling;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * AgingScheduling is an algorithm designed to prevent starvation
+ * by gradually increasing the priority of waiting tasks.
+ * The longer a process waits, the higher its priority becomes.
+ *
+ * Use Case: Useful in systems with mixed workloads to avoid
+ * lower-priority tasks being starved by higher-priority tasks.
+ *
+ * @author Hardvan
+ */
+public final class AgingScheduling {
+
+ static class Task {
+ String name;
+ int waitTime;
+ int priority;
+
+ Task(String name, int priority) {
+ this.name = name;
+ this.priority = priority;
+ this.waitTime = 0;
+ }
+ }
+
+ private final Queue taskQueue;
+
+ public AgingScheduling() {
+ taskQueue = new LinkedList<>();
+ }
+
+ /**
+ * Adds a task to the scheduler with a given priority.
+ *
+ * @param name name of the task
+ * @param priority priority of the task
+ */
+ public void addTask(String name, int priority) {
+ taskQueue.offer(new Task(name, priority));
+ }
+
+ /**
+ * Schedules the next task based on the priority and wait time.
+ * The priority of a task increases with the time it spends waiting.
+ *
+ * @return name of the next task to be executed
+ */
+ public String scheduleNext() {
+ if (taskQueue.isEmpty()) {
+ return null;
+ }
+ Task nextTask = taskQueue.poll();
+ nextTask.waitTime++;
+ nextTask.priority += nextTask.waitTime;
+ taskQueue.offer(nextTask);
+ return nextTask.name;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java b/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java
new file mode 100644
index 000000000000..5ba79cdbb73a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java
@@ -0,0 +1,99 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The Earliest Deadline First (EDF) Scheduling class implements a dynamic scheduling algorithm.
+ * It assigns the CPU to processes with the earliest deadlines, ensuring that deadlines are met if possible.
+ * This scheduling algorithm is ideal for real-time systems where meeting deadlines is critical.
+ */
+public final class EDFScheduling {
+ private EDFScheduling() {
+ }
+
+ private List processes;
+
+ /**
+ * Constructs an EDFScheduling object with a list of processes.
+ *
+ * @param processes List of processes to be scheduled.
+ */
+ public EDFScheduling(final List processes) {
+ this.processes = processes;
+ }
+
+ /**
+ * Schedules the processes using Earliest Deadline First (EDF) scheduling.
+ * Processes are sorted by their deadlines, and the method simulates their execution.
+ * It calculates the waiting time and turnaround time for each process.
+ *
+ * @return List of processes after they have been executed in order of earliest deadline first.
+ */
+ public List scheduleProcesses() {
+ processes.sort(Comparator.comparingInt(Process::getDeadline));
+
+ int currentTime = 0;
+ List executedProcesses = new ArrayList<>();
+
+ for (Process process : processes) {
+ process.setWaitingTime(currentTime);
+ currentTime += process.getBurstTime();
+ process.setTurnAroundTime(process.getWaitingTime() + process.getBurstTime());
+
+ if (currentTime > process.getDeadline()) {
+ System.out.println("Warning: Process " + process.getProcessId() + " missed its deadline.");
+ }
+
+ executedProcesses.add(process);
+ }
+
+ return executedProcesses;
+ }
+
+ /**
+ * The Process class represents a process with an ID, burst time, deadline, waiting time, and turnaround time.
+ */
+ public static class Process {
+ private String processId;
+ private int burstTime;
+ private int deadline;
+ private int waitingTime;
+ private int turnAroundTime;
+
+ public Process(String processId, int burstTime, int deadline) {
+ this.processId = processId;
+ this.burstTime = burstTime;
+ this.deadline = deadline;
+ }
+
+ public String getProcessId() {
+ return processId;
+ }
+
+ public int getBurstTime() {
+ return burstTime;
+ }
+
+ public int getDeadline() {
+ return deadline;
+ }
+
+ public int getWaitingTime() {
+ return waitingTime;
+ }
+
+ public void setWaitingTime(int waitingTime) {
+ this.waitingTime = waitingTime;
+ }
+
+ public int getTurnAroundTime() {
+ return turnAroundTime;
+ }
+
+ public void setTurnAroundTime(int turnAroundTime) {
+ this.turnAroundTime = turnAroundTime;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java b/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java
new file mode 100644
index 000000000000..776fc59c0c4d
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java
@@ -0,0 +1,65 @@
+package com.thealgorithms.scheduling;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * FairShareScheduling allocates CPU resources equally among users or groups
+ * instead of individual tasks. Each group gets a proportional share,
+ * preventing resource hogging by a single user's processes.
+ *
+ * Use Case: Multi-user systems where users submit multiple tasks simultaneously,
+ * such as cloud environments.
+ *
+ * @author Hardvan
+ */
+public final class FairShareScheduling {
+
+ static class User {
+ String name;
+ int allocatedResources;
+ int totalWeight;
+
+ User(String name) {
+ this.name = name;
+ this.allocatedResources = 0;
+ this.totalWeight = 0;
+ }
+
+ void addWeight(int weight) {
+ this.totalWeight += weight;
+ }
+ }
+
+ private final Map users; // username -> User
+
+ public FairShareScheduling() {
+ users = new HashMap<>();
+ }
+
+ public void addUser(String userName) {
+ users.putIfAbsent(userName, new User(userName));
+ }
+
+ public void addTask(String userName, int weight) {
+ User user = users.get(userName);
+ if (user != null) {
+ user.addWeight(weight);
+ }
+ }
+
+ public void allocateResources(int totalResources) {
+ int totalWeights = users.values().stream().mapToInt(user -> user.totalWeight).sum();
+ for (User user : users.values()) {
+ user.allocatedResources = (int) ((double) user.totalWeight / totalWeights * totalResources);
+ }
+ }
+
+ public Map getAllocatedResources() {
+ Map allocation = new HashMap<>();
+ for (User user : users.values()) {
+ allocation.put(user.name, user.allocatedResources);
+ }
+ return allocation;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/GangScheduling.java b/src/main/java/com/thealgorithms/scheduling/GangScheduling.java
new file mode 100644
index 000000000000..ac1ce8ddd6ae
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/GangScheduling.java
@@ -0,0 +1,61 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * GangScheduling groups related tasks (gangs) to run simultaneously on multiple processors.
+ * All tasks in a gang are executed together or not at all.
+ *
+ * Use Case: Parallel computing environments where multiple threads of a program
+ * need to run concurrently for optimal performance.
+ *
+ * @author Hardvan
+ */
+public final class GangScheduling {
+
+ static class Gang {
+ String name;
+ List tasks;
+
+ Gang(String name) {
+ this.name = name;
+ this.tasks = new ArrayList<>();
+ }
+
+ void addTask(String task) {
+ tasks.add(task);
+ }
+
+ List getTasks() {
+ return tasks;
+ }
+ }
+
+ private final Map gangs;
+
+ public GangScheduling() {
+ gangs = new HashMap<>();
+ }
+
+ public void addGang(String gangName) {
+ gangs.putIfAbsent(gangName, new Gang(gangName));
+ }
+
+ public void addTaskToGang(String gangName, String task) {
+ Gang gang = gangs.get(gangName);
+ if (gang != null) {
+ gang.addTask(task);
+ }
+ }
+
+ public Map> getGangSchedules() {
+ Map> schedules = new HashMap<>();
+ for (Gang gang : gangs.values()) {
+ schedules.put(gang.name, gang.getTasks());
+ }
+ return schedules;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java b/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java
new file mode 100644
index 000000000000..49638d39fc2a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java
@@ -0,0 +1,88 @@
+package com.thealgorithms.scheduling;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * A class that implements a job scheduling algorithm to maximize profit
+ * while adhering to job deadlines and arrival times.
+ *
+ * This class provides functionality to schedule jobs based on their profit,
+ * arrival time, and deadlines to ensure that the maximum number of jobs is completed
+ * within the given timeframe. It sorts the jobs in decreasing order of profit
+ * and attempts to assign them to the latest possible time slots.
+ */
+public final class JobSchedulingWithDeadline {
+ private JobSchedulingWithDeadline() {
+ }
+
+ /**
+ * Represents a job with an ID, arrival time, deadline, and profit.
+ *
+ * Each job has a unique identifier, an arrival time (when it becomes available for scheduling),
+ * a deadline by which it must be completed, and a profit associated with completing the job.
+ */
+ static class Job {
+ int jobId;
+ int arrivalTime;
+ int deadline;
+ int profit;
+
+ /**
+ * Constructs a Job instance with the specified job ID, arrival time, deadline, and profit.
+ *
+ * @param jobId Unique identifier for the job
+ * @param arrivalTime Time when the job becomes available for scheduling
+ * @param deadline Deadline for completing the job
+ * @param profit Profit earned upon completing the job
+ */
+ Job(int jobId, int arrivalTime, int deadline, int profit) {
+ this.jobId = jobId;
+ this.arrivalTime = arrivalTime;
+ this.deadline = deadline;
+ this.profit = profit;
+ }
+ }
+
+ /**
+ * Schedules jobs to maximize profit while respecting their deadlines and arrival times.
+ *
+ * This method sorts the jobs in descending order of profit and attempts
+ * to allocate them to time slots that are before or on their deadlines,
+ * provided they have arrived. The function returns an array where the first element
+ * is the total number of jobs scheduled and the second element is the total profit earned.
+ *
+ * @param jobs An array of Job objects, each representing a job with an ID, arrival time,
+ * deadline, and profit.
+ * @return An array of two integers: the first element is the count of jobs
+ * that were successfully scheduled, and the second element is the
+ * total profit earned from those jobs.
+ */
+ public static int[] jobSequencingWithDeadlines(Job[] jobs) {
+ Arrays.sort(jobs, Comparator.comparingInt(job -> - job.profit));
+
+ int maxDeadline = Arrays.stream(jobs).mapToInt(job -> job.deadline).max().orElse(0);
+
+ int[] timeSlots = new int[maxDeadline];
+ Arrays.fill(timeSlots, -1);
+
+ int count = 0;
+ int maxProfit = 0;
+
+ // Schedule the jobs
+ for (Job job : jobs) {
+ if (job.arrivalTime <= job.deadline) {
+ for (int i = Math.min(job.deadline - 1, maxDeadline - 1); i >= job.arrivalTime - 1; i--) {
+ if (timeSlots[i] == -1) {
+ timeSlots[i] = job.jobId;
+ count++;
+ maxProfit += job.profit;
+ break;
+ }
+ }
+ }
+ }
+
+ return new int[] {count, maxProfit};
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java b/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java
new file mode 100644
index 000000000000..cea0c793d340
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java
@@ -0,0 +1,141 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * The LotteryScheduling class implements the Lottery Scheduling algorithm, which is
+ * a probabilistic CPU scheduling algorithm. Processes are assigned tickets, and
+ * the CPU is allocated to a randomly selected process based on ticket count.
+ * Processes with more tickets have a higher chance of being selected.
+ */
+public final class LotteryScheduling {
+ private LotteryScheduling() {
+ }
+
+ private List processes;
+ private Random random;
+
+ /**
+ * Constructs a LotteryScheduling object with the provided list of processes.
+ *
+ * @param processes List of processes to be scheduled using Lottery Scheduling.
+ */
+ public LotteryScheduling(final List processes) {
+ this.processes = processes;
+ this.random = new Random();
+ }
+
+ /**
+ * Constructs a LotteryScheduling object with the provided list of processes and a Random object.
+ *
+ * @param processes List of processes to be scheduled using Lottery Scheduling.
+ * @param random Random object used for generating random numbers.
+ */
+ public LotteryScheduling(final List processes, Random random) {
+ this.processes = processes;
+ this.random = random;
+ }
+
+ /**
+ * Schedules the processes using the Lottery Scheduling algorithm.
+ * Each process is assigned a certain number of tickets, and the algorithm randomly
+ * selects a process to execute based on ticket count. The method calculates the
+ * waiting time and turnaround time for each process and simulates their execution.
+ */
+ public List scheduleProcesses() {
+ int totalTickets = processes.stream().mapToInt(Process::getTickets).sum();
+ int currentTime = 0;
+ List executedProcesses = new ArrayList<>();
+
+ while (!processes.isEmpty()) {
+ int winningTicket = random.nextInt(totalTickets) + 1;
+ Process selectedProcess = selectProcessByTicket(winningTicket);
+
+ if (selectedProcess == null) {
+ // This should not happen in normal circumstances, but we'll handle it just in case
+ System.err.println("Error: No process selected. Recalculating total tickets.");
+ totalTickets = processes.stream().mapToInt(Process::getTickets).sum();
+ continue;
+ }
+
+ selectedProcess.setWaitingTime(currentTime);
+ currentTime += selectedProcess.getBurstTime();
+ selectedProcess.setTurnAroundTime(selectedProcess.getWaitingTime() + selectedProcess.getBurstTime());
+
+ executedProcesses.add(selectedProcess);
+ processes.remove(selectedProcess);
+
+ totalTickets = processes.stream().mapToInt(Process::getTickets).sum();
+ }
+
+ return executedProcesses;
+ }
+
+ /**
+ * Selects a process based on a winning ticket. The method iterates over the
+ * list of processes, and as the ticket sum accumulates, it checks if the
+ * current process holds the winning ticket.
+ *
+ * @param winningTicket The randomly generated ticket number that determines the selected process.
+ * @return The process associated with the winning ticket.
+ */
+ private Process selectProcessByTicket(int winningTicket) {
+ int ticketSum = 0;
+ for (Process process : processes) {
+ ticketSum += process.getTickets();
+ if (ticketSum >= winningTicket) {
+ return process;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The Process class represents a process in the scheduling system. Each process has
+ * an ID, burst time (CPU time required for execution), number of tickets (used in
+ * lottery selection), waiting time, and turnaround time.
+ */
+ public static class Process {
+ private String processId;
+ private int burstTime;
+ private int tickets;
+ private int waitingTime;
+ private int turnAroundTime;
+
+ public Process(String processId, int burstTime, int tickets) {
+ this.processId = processId;
+ this.burstTime = burstTime;
+ this.tickets = tickets;
+ }
+
+ public String getProcessId() {
+ return processId;
+ }
+
+ public int getBurstTime() {
+ return burstTime;
+ }
+
+ public int getTickets() {
+ return tickets;
+ }
+
+ public int getWaitingTime() {
+ return waitingTime;
+ }
+
+ public void setWaitingTime(int waitingTime) {
+ this.waitingTime = waitingTime;
+ }
+
+ public int getTurnAroundTime() {
+ return turnAroundTime;
+ }
+
+ public void setTurnAroundTime(int turnAroundTime) {
+ this.turnAroundTime = turnAroundTime;
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java b/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java
new file mode 100644
index 000000000000..113b1691dec1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java
@@ -0,0 +1,72 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MultiAgentScheduling assigns tasks to different autonomous agents
+ * who independently decide the execution order of their assigned tasks.
+ * The focus is on collaboration between agents to optimize the overall schedule.
+ *
+ * Use Case: Distributed scheduling in decentralized systems like IoT networks.
+ *
+ * @author Hardvan
+ */
+public class MultiAgentScheduling {
+
+ static class Agent {
+ String name;
+ List tasks;
+
+ Agent(String name) {
+ this.name = name;
+ this.tasks = new ArrayList<>();
+ }
+
+ void addTask(String task) {
+ tasks.add(task);
+ }
+
+ List getTasks() {
+ return tasks;
+ }
+ }
+
+ private final Map agents;
+
+ public MultiAgentScheduling() {
+ agents = new HashMap<>();
+ }
+
+ public void addAgent(String agentName) {
+ agents.putIfAbsent(agentName, new Agent(agentName));
+ }
+
+ /**
+ * Assign a task to a specific agent.
+ *
+ * @param agentName the name of the agent
+ * @param task the task to be assigned
+ */
+ public void assignTask(String agentName, String task) {
+ Agent agent = agents.get(agentName);
+ if (agent != null) {
+ agent.addTask(task);
+ }
+ }
+
+ /**
+ * Get the scheduled tasks for each agent.
+ *
+ * @return a map of agent names to their scheduled tasks
+ */
+ public Map> getScheduledTasks() {
+ Map> schedule = new HashMap<>();
+ for (Agent agent : agents.values()) {
+ schedule.put(agent.name, agent.getTasks());
+ }
+ return schedule;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java b/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java
new file mode 100644
index 000000000000..414de4b24e36
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java
@@ -0,0 +1,133 @@
+package com.thealgorithms.scheduling;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * This class implements the Non-Preemptive Priority Scheduling algorithm.
+ * Processes are executed in order of their priority. The process with the
+ * highest priority (lower priority number) is executed first,
+ * and once a process starts executing, it cannot be preempted.
+ */
+public final class NonPreemptivePriorityScheduling {
+
+ private NonPreemptivePriorityScheduling() {
+ }
+
+ /**
+ * Represents a process with an ID, burst time, priority, arrival time, and start time.
+ */
+ static class Process implements Comparable {
+ int id;
+ int arrivalTime;
+ int startTime;
+ int burstTime;
+ int priority;
+
+ /**
+ * Constructs a Process instance with the specified parameters.
+ *
+ * @param id Unique identifier for the process
+ * @param arrivalTime Time when the process arrives in the system
+ * @param burstTime Time required for the process execution
+ * @param priority Priority of the process
+ */
+ Process(int id, int arrivalTime, int burstTime, int priority) {
+ this.id = id;
+ this.arrivalTime = arrivalTime;
+ this.startTime = -1;
+ this.burstTime = burstTime;
+ this.priority = priority;
+ }
+
+ /**
+ * Compare based on priority for scheduling. The process with the lowest
+ * priority is selected first.
+ * If two processes have the same priority, the one that arrives earlier is selected.
+ *
+ * @param other The other process to compare against
+ * @return A negative integer, zero, or a positive integer as this process
+ * is less than, equal to, or greater than the specified process.
+ */
+ @Override
+ public int compareTo(Process other) {
+ if (this.priority == other.priority) {
+ return Integer.compare(this.arrivalTime, other.arrivalTime);
+ }
+ return Integer.compare(this.priority, other.priority);
+ }
+ }
+
+ /**
+ * Schedules processes based on their priority in a non-preemptive manner, considering their arrival times.
+ *
+ * @param processes Array of processes to be scheduled.
+ * @return Array of processes in the order they are executed.
+ */
+ public static Process[] scheduleProcesses(Process[] processes) {
+ PriorityQueue pq = new PriorityQueue<>();
+ Queue waitingQueue = new LinkedList<>();
+ int currentTime = 0;
+ int index = 0;
+ Process[] executionOrder = new Process[processes.length];
+
+ Collections.addAll(waitingQueue, processes);
+
+ while (!waitingQueue.isEmpty() || !pq.isEmpty()) {
+ // Add processes that have arrived to the priority queue
+ while (!waitingQueue.isEmpty() && waitingQueue.peek().arrivalTime <= currentTime) {
+ pq.add(waitingQueue.poll());
+ }
+
+ if (!pq.isEmpty()) {
+ Process currentProcess = pq.poll();
+ currentProcess.startTime = currentTime;
+ executionOrder[index++] = currentProcess;
+ currentTime += currentProcess.burstTime;
+ } else {
+ // If no process is ready, move to the next arrival time
+ currentTime = waitingQueue.peek().arrivalTime;
+ }
+ }
+
+ return executionOrder;
+ }
+
+ /**
+ * Calculates the average waiting time of the processes.
+ *
+ * @param processes Array of processes.
+ * @param executionOrder Array of processes in execution order.
+ * @return Average waiting time.
+ */
+ public static double calculateAverageWaitingTime(Process[] processes, Process[] executionOrder) {
+ int totalWaitingTime = 0;
+
+ for (Process process : executionOrder) {
+ int waitingTime = process.startTime - process.arrivalTime;
+ totalWaitingTime += waitingTime;
+ }
+
+ return (double) totalWaitingTime / processes.length;
+ }
+
+ /**
+ * Calculates the average turn-around time of the processes.
+ *
+ * @param processes Array of processes.
+ * @param executionOrder Array of processes in execution order.
+ * @return Average turn-around time.
+ */
+ public static double calculateAverageTurnaroundTime(Process[] processes, Process[] executionOrder) {
+ int totalTurnaroundTime = 0;
+
+ for (Process process : executionOrder) {
+ int turnaroundTime = process.startTime + process.burstTime - process.arrivalTime;
+ totalTurnaroundTime += turnaroundTime;
+ }
+
+ return (double) totalTurnaroundTime / processes.length;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java
index 27d85a94d6f5..66c99661d13d 100644
--- a/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java
+++ b/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java
@@ -2,6 +2,7 @@
import com.thealgorithms.devutils.entities.ProcessDetails;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
@@ -15,7 +16,7 @@ public class PreemptivePriorityScheduling {
protected final List processes;
protected final List ganttChart;
- public PreemptivePriorityScheduling(List processes) {
+ public PreemptivePriorityScheduling(Collection processes) {
this.processes = new ArrayList<>(processes);
this.ganttChart = new ArrayList<>();
}
diff --git a/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java b/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java
new file mode 100644
index 000000000000..fd78dc571819
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java
@@ -0,0 +1,54 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ProportionalFairScheduling allocates resources to processes based on their
+ * proportional weight or importance. It aims to balance fairness with
+ * priority, ensuring that higher-weight processes receive a larger share of resources.
+ *
+ * Use Case: Network bandwidth allocation in cellular networks (4G/5G),
+ * where devices receive a proportional share of bandwidth.
+ *
+ * @author Hardvan
+ */
+public final class ProportionalFairScheduling {
+
+ static class Process {
+ String name;
+ int weight;
+ int allocatedResources;
+
+ Process(String name, int weight) {
+ this.name = name;
+ this.weight = weight;
+ this.allocatedResources = 0;
+ }
+ }
+
+ private final List processes;
+
+ public ProportionalFairScheduling() {
+ processes = new ArrayList<>();
+ }
+
+ public void addProcess(String name, int weight) {
+ processes.add(new Process(name, weight));
+ }
+
+ public void allocateResources(int totalResources) {
+ int totalWeight = processes.stream().mapToInt(p -> p.weight).sum();
+ for (Process process : processes) {
+ process.allocatedResources = (int) ((double) process.weight / totalWeight * totalResources);
+ }
+ }
+
+ public List getAllocatedResources() {
+ List allocation = new ArrayList<>();
+ for (Process process : processes) {
+ allocation.add(process.name + ": " + process.allocatedResources);
+ }
+ return allocation;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java b/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java
new file mode 100644
index 000000000000..b7e863b5cfd8
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java
@@ -0,0 +1,45 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * RandomScheduling is an algorithm that assigns tasks in a random order.
+ * It doesn't consider priority, deadlines, or burst times, making it
+ * inefficient but useful in scenarios where fairness or unpredictability
+ * is required (e.g., load balancing in distributed systems).
+ *
+ * Use Case: Distributed systems where randomness helps avoid task starvation.
+ *
+ * @author Hardvan
+ */
+public final class RandomScheduling {
+
+ private final List tasks;
+ private final Random random;
+
+ /**
+ * Constructs a new RandomScheduling instance.
+ *
+ * @param tasks A collection of task names to be scheduled.
+ * @param random A Random instance for generating random numbers.
+ */
+ public RandomScheduling(Collection tasks, Random random) {
+ this.tasks = new ArrayList<>(tasks);
+ this.random = random;
+ }
+
+ /**
+ * Schedules the tasks randomly and returns the randomized order.
+ *
+ * @return A list representing the tasks in their randomized execution order.
+ */
+ public List schedule() {
+ List shuffledTasks = new ArrayList<>(tasks);
+ Collections.shuffle(shuffledTasks, random);
+ return shuffledTasks;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java
index ca2144e4924f..cbbc65a3afc5 100644
--- a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java
+++ b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java
@@ -2,6 +2,7 @@
import com.thealgorithms.devutils.entities.ProcessDetails;
import java.util.ArrayList;
+import java.util.List;
/**
* Implementation of Shortest Job First Algorithm: The algorithm allows the waiting process with the
@@ -13,6 +14,18 @@ public class SJFScheduling {
protected ArrayList processes;
protected ArrayList schedule;
+ private static void sortProcessesByArrivalTime(List processes) {
+ for (int i = 0; i < processes.size(); i++) {
+ for (int j = i + 1; j < processes.size() - 1; j++) {
+ if (processes.get(j).getArrivalTime() > processes.get(j + 1).getArrivalTime()) {
+ final var temp = processes.get(j);
+ processes.set(j, processes.get(j + 1));
+ processes.set(j + 1, temp);
+ }
+ }
+ }
+ }
+
/**
* a simple constructor
* @param processes a list of processes the user wants to schedule
@@ -21,22 +34,10 @@ public class SJFScheduling {
SJFScheduling(final ArrayList processes) {
this.processes = processes;
schedule = new ArrayList<>();
- sortByArrivalTime();
+ sortProcessesByArrivalTime(this.processes);
}
protected void sortByArrivalTime() {
- int size = processes.size();
- int i;
- int j;
- ProcessDetails temp;
- for (i = 0; i < size; i++) {
- for (j = i + 1; j < size - 1; j++) {
- if (processes.get(j).getArrivalTime() > processes.get(j + 1).getArrivalTime()) {
- temp = processes.get(j);
- processes.set(j, processes.get(j + 1));
- processes.set(j + 1, temp);
- }
- }
- }
+ sortProcessesByArrivalTime(processes);
}
/**
@@ -87,7 +88,7 @@ public void scheduleProcesses() {
* @return returns the process' with the shortest burst time OR NULL if there are no ready
* processes
*/
- private ProcessDetails findShortestJob(ArrayList readyProcesses) {
+ private ProcessDetails findShortestJob(List readyProcesses) {
if (readyProcesses.isEmpty()) {
return null;
}
diff --git a/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java b/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java
new file mode 100644
index 000000000000..15159a07d2d4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java
@@ -0,0 +1,63 @@
+package com.thealgorithms.scheduling;
+
+import java.util.PriorityQueue;
+
+/**
+ * SelfAdjustingScheduling is an algorithm where tasks dynamically adjust
+ * their priority based on real-time feedback, such as wait time and CPU usage.
+ * Tasks that wait longer will automatically increase their priority,
+ * allowing for better responsiveness and fairness in task handling.
+ *
+ * Use Case: Real-time systems that require dynamic prioritization
+ * of tasks to maintain system responsiveness and fairness.
+ *
+ * @author Hardvan
+ */
+public final class SelfAdjustingScheduling {
+
+ private static class Task implements Comparable {
+ String name;
+ int waitTime;
+ int priority;
+
+ Task(String name, int priority) {
+ this.name = name;
+ this.waitTime = 0;
+ this.priority = priority;
+ }
+
+ void incrementWaitTime() {
+ waitTime++;
+ priority = priority + waitTime;
+ }
+
+ @Override
+ public int compareTo(Task other) {
+ return Integer.compare(this.priority, other.priority);
+ }
+ }
+
+ private final PriorityQueue taskQueue;
+
+ public SelfAdjustingScheduling() {
+ taskQueue = new PriorityQueue<>();
+ }
+
+ public void addTask(String name, int priority) {
+ taskQueue.offer(new Task(name, priority));
+ }
+
+ public String scheduleNext() {
+ if (taskQueue.isEmpty()) {
+ return null;
+ }
+ Task nextTask = taskQueue.poll();
+ nextTask.incrementWaitTime();
+ taskQueue.offer(nextTask);
+ return nextTask.name;
+ }
+
+ public boolean isEmpty() {
+ return taskQueue.isEmpty();
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java b/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java
new file mode 100644
index 000000000000..bbfd36f0f660
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java
@@ -0,0 +1,64 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * SlackTimeScheduling is an algorithm that prioritizes tasks based on their
+ * slack time, which is defined as the difference between the task's deadline
+ * and the time required to execute it. Tasks with less slack time are prioritized.
+ *
+ * Use Case: Real-time systems with hard deadlines, such as robotics or embedded systems.
+ *
+ * @author Hardvan
+ */
+public class SlackTimeScheduling {
+
+ static class Task {
+ String name;
+ int executionTime;
+ int deadline;
+
+ Task(String name, int executionTime, int deadline) {
+ this.name = name;
+ this.executionTime = executionTime;
+ this.deadline = deadline;
+ }
+
+ int getSlackTime() {
+ return deadline - executionTime;
+ }
+ }
+
+ private final List tasks;
+
+ public SlackTimeScheduling() {
+ tasks = new ArrayList<>();
+ }
+
+ /**
+ * Adds a task to the scheduler.
+ *
+ * @param name the name of the task
+ * @param executionTime the time required to execute the task
+ * @param deadline the deadline by which the task must be completed
+ */
+ public void addTask(String name, int executionTime, int deadline) {
+ tasks.add(new Task(name, executionTime, deadline));
+ }
+
+ /**
+ * Schedules the tasks based on their slack time.
+ *
+ * @return the order in which the tasks should be executed
+ */
+ public List scheduleTasks() {
+ tasks.sort(Comparator.comparingInt(Task::getSlackTime));
+ List scheduledOrder = new ArrayList<>();
+ for (Task task : tasks) {
+ scheduledOrder.add(task.name);
+ }
+ return scheduledOrder;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java
new file mode 100644
index 000000000000..2230ecaf35a6
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java
@@ -0,0 +1,80 @@
+package com.thealgorithms.scheduling.diskscheduling;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Circular Look Scheduling (C-LOOK) is a disk scheduling algorithm similar to
+ * the C-SCAN algorithm but with a key difference. In C-LOOK, the disk arm also
+ * moves in one direction to service requests, but instead of going all the way
+ * to the end of the disk, it only goes as far as the furthest request in the
+ * current direction. After servicing the last request in the current direction,
+ * the arm immediately jumps back to the closest request on the other side without
+ * moving to the disk's extreme ends. This reduces the unnecessary movement of the
+ * disk arm, resulting in better performance compared to C-SCAN, while still
+ * maintaining fair wait times for requests.
+ */
+public class CircularLookScheduling {
+ private int currentPosition;
+ private boolean movingUp;
+ private final int maxCylinder;
+
+ public CircularLookScheduling(int startPosition, boolean movingUp, int maxCylinder) {
+ this.currentPosition = startPosition;
+ this.movingUp = movingUp;
+ this.maxCylinder = maxCylinder;
+ }
+
+ public List execute(List requests) {
+ List result = new ArrayList<>();
+
+ // Filter and sort valid requests in both directions
+ List upRequests = new ArrayList<>();
+ List downRequests = new ArrayList<>();
+
+ for (int request : requests) {
+ if (request >= 0 && request < maxCylinder) {
+ if (request > currentPosition) {
+ upRequests.add(request);
+ } else if (request < currentPosition) {
+ downRequests.add(request);
+ }
+ }
+ }
+
+ Collections.sort(upRequests);
+ Collections.sort(downRequests);
+
+ if (movingUp) {
+ // Process all requests in the upward direction
+ result.addAll(upRequests);
+
+ // Jump to the lowest request and process all requests in the downward direction
+ result.addAll(downRequests);
+ } else {
+ // Process all requests in the downward direction (in reverse order)
+ Collections.reverse(downRequests);
+ result.addAll(downRequests);
+
+ // Jump to the highest request and process all requests in the upward direction (in reverse order)
+ Collections.reverse(upRequests);
+ result.addAll(upRequests);
+ }
+
+ // Update current position to the last processed request
+ if (!result.isEmpty()) {
+ currentPosition = result.get(result.size() - 1);
+ }
+
+ return result;
+ }
+
+ public int getCurrentPosition() {
+ return currentPosition;
+ }
+
+ public boolean isMovingUp() {
+ return movingUp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java
new file mode 100644
index 000000000000..a31c3d99a89e
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java
@@ -0,0 +1,83 @@
+package com.thealgorithms.scheduling.diskscheduling;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Circular Scan Scheduling (C-SCAN) is a disk scheduling algorithm that
+ * works by moving the disk arm in one direction to service requests until
+ * it reaches the end of the disk. Once it reaches the end, instead of reversing
+ * direction like in the SCAN algorithm, the arm moves back to the starting point
+ * without servicing any requests. This ensures a more uniform wait time for all
+ * requests, especially those near the disk edges. The algorithm then continues in
+ * the same direction, making it effective for balancing service time across all disk sectors.
+ */
+public class CircularScanScheduling {
+ private int currentPosition;
+ private boolean movingUp;
+ private final int diskSize;
+
+ public CircularScanScheduling(int startPosition, boolean movingUp, int diskSize) {
+ this.currentPosition = startPosition;
+ this.movingUp = movingUp;
+ this.diskSize = diskSize;
+ }
+
+ public List execute(List requests) {
+ if (requests.isEmpty()) {
+ return new ArrayList<>(); // Return empty list if there are no requests
+ }
+
+ List sortedRequests = new ArrayList<>(requests);
+ Collections.sort(sortedRequests);
+
+ List result = new ArrayList<>();
+
+ if (movingUp) {
+ // Moving up: process requests >= current position
+ for (int request : sortedRequests) {
+ if (request >= currentPosition && request < diskSize) {
+ result.add(request);
+ }
+ }
+
+ // Jump to the smallest request and continue processing from the start
+ for (int request : sortedRequests) {
+ if (request < currentPosition) {
+ result.add(request);
+ }
+ }
+ } else {
+ // Moving down: process requests <= current position in reverse order
+ for (int i = sortedRequests.size() - 1; i >= 0; i--) {
+ int request = sortedRequests.get(i);
+ if (request <= currentPosition) {
+ result.add(request);
+ }
+ }
+
+ // Jump to the largest request and continue processing in reverse order
+ for (int i = sortedRequests.size() - 1; i >= 0; i--) {
+ int request = sortedRequests.get(i);
+ if (request > currentPosition) {
+ result.add(request);
+ }
+ }
+ }
+
+ // Set final position to the last request processed
+ if (!result.isEmpty()) {
+ currentPosition = result.get(result.size() - 1);
+ }
+ return result;
+ }
+
+ public int getCurrentPosition() {
+ return currentPosition;
+ }
+
+ public boolean isMovingUp() {
+ return movingUp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java
new file mode 100644
index 000000000000..beba69b05eca
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java
@@ -0,0 +1,95 @@
+package com.thealgorithms.scheduling.diskscheduling;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * https://en.wikipedia.org/wiki/LOOK_algorithm
+ * Look Scheduling algorithm implementation.
+ * The Look algorithm moves the disk arm to the closest request in the current direction,
+ * and once it processes all requests in that direction, it reverses the direction.
+ */
+public class LookScheduling {
+ private final int maxTrack;
+ private final int currentPosition;
+ private boolean movingUp;
+ private int farthestPosition;
+ public LookScheduling(int startPosition, boolean initialDirection, int maxTrack) {
+ this.currentPosition = startPosition;
+ this.movingUp = initialDirection;
+ this.maxTrack = maxTrack;
+ }
+
+ /**
+ * Executes the Look Scheduling algorithm on the given list of requests.
+ *
+ * @param requests List of disk requests.
+ * @return Order in which requests are processed.
+ */
+ public List execute(List requests) {
+ List result = new ArrayList<>();
+ List lower = new ArrayList<>();
+ List upper = new ArrayList<>();
+
+ // Split requests into two lists based on their position relative to current position
+ for (int request : requests) {
+ if (request >= 0 && request < maxTrack) {
+ if (request < currentPosition) {
+ lower.add(request);
+ } else {
+ upper.add(request);
+ }
+ }
+ }
+
+ // Sort the requests
+ Collections.sort(lower);
+ Collections.sort(upper);
+
+ // Process the requests depending on the initial moving direction
+ if (movingUp) {
+ // Process requests in the upward direction
+ result.addAll(upper);
+ if (!upper.isEmpty()) {
+ farthestPosition = upper.get(upper.size() - 1);
+ }
+
+ // Reverse the direction and process downward
+ movingUp = false;
+ Collections.reverse(lower);
+ result.addAll(lower);
+ if (!lower.isEmpty()) {
+ farthestPosition = Math.max(farthestPosition, lower.get(0));
+ }
+ } else {
+ // Process requests in the downward direction
+ Collections.reverse(lower);
+ result.addAll(lower);
+ if (!lower.isEmpty()) {
+ farthestPosition = lower.get(0);
+ }
+
+ // Reverse the direction and process upward
+ movingUp = true;
+ result.addAll(upper);
+ if (!upper.isEmpty()) {
+ farthestPosition = Math.max(farthestPosition, upper.get(upper.size() - 1));
+ }
+ }
+
+ return result;
+ }
+
+ public int getCurrentPosition() {
+ return currentPosition;
+ }
+
+ public boolean isMovingUp() {
+ return movingUp;
+ }
+
+ public int getFarthestPosition() {
+ return farthestPosition;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java
new file mode 100644
index 000000000000..261c1a388393
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java
@@ -0,0 +1,57 @@
+package com.thealgorithms.scheduling.diskscheduling;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ *https://en.wikipedia.org/wiki/Shortest_seek_first
+ * Shortest Seek First (SFF) Scheduling algorithm implementation.
+ * The SFF algorithm selects the next request to be serviced based on the shortest distance
+ * from the current position of the disk arm. It continuously evaluates all pending requests
+ * and chooses the one that requires the least amount of movement to service.
+ *
+ * This approach minimizes the average seek time, making it efficient in terms of response
+ * time for individual requests. However, it may lead to starvation for requests located
+ * further away from the current position of the disk arm.
+ *
+ * The SFF algorithm is particularly effective in systems where quick response time
+ * is crucial, as it ensures that the most accessible requests are prioritized for servicing.
+ */
+public class SSFScheduling {
+ private int currentPosition;
+
+ public SSFScheduling(int currentPosition) {
+ this.currentPosition = currentPosition;
+ }
+
+ public List execute(Collection requests) {
+ List result = new ArrayList<>(requests);
+ List orderedRequests = new ArrayList<>();
+
+ while (!result.isEmpty()) {
+ int closest = findClosest(result);
+ orderedRequests.add(closest);
+ result.remove(Integer.valueOf(closest));
+ currentPosition = closest;
+ }
+ return orderedRequests;
+ }
+
+ private int findClosest(List requests) {
+ int minDistance = Integer.MAX_VALUE;
+ int closest = -1;
+ for (int request : requests) {
+ int distance = Math.abs(currentPosition - request);
+ if (distance < minDistance) {
+ minDistance = distance;
+ closest = request;
+ }
+ }
+ return closest;
+ }
+
+ public int getCurrentPosition() {
+ return currentPosition;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java b/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java
new file mode 100644
index 000000000000..2c4fa7844a12
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java
@@ -0,0 +1,82 @@
+package com.thealgorithms.scheduling.diskscheduling;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * https://en.wikipedia.org/wiki/Elevator_algorithm
+ * SCAN Scheduling algorithm implementation.
+ * The SCAN algorithm moves the disk arm towards one end of the disk, servicing all requests
+ * along the way until it reaches the end. Once it reaches the end, it reverses direction
+ * and services the requests on its way back.
+ *
+ * This algorithm ensures that all requests are serviced in a fair manner,
+ * while minimizing the seek time for requests located close to the current position
+ * of the disk arm.
+ *
+ * The SCAN algorithm is particularly useful in environments with a large number of
+ * disk requests, as it reduces the overall movement of the disk arm compared to
+ */
+public class ScanScheduling {
+ private int headPosition;
+ private int diskSize;
+ private boolean movingUp;
+
+ public ScanScheduling(int headPosition, boolean movingUp, int diskSize) {
+ this.headPosition = headPosition;
+ this.movingUp = movingUp;
+ this.diskSize = diskSize;
+ }
+
+ public List execute(List requests) {
+ // If the request list is empty, return an empty result
+ if (requests.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ List result = new ArrayList<>();
+ List left = new ArrayList<>();
+ List right = new ArrayList<>();
+
+ // Separate requests into those smaller than the current head position and those larger
+ for (int request : requests) {
+ if (request < headPosition) {
+ left.add(request);
+ } else {
+ right.add(request);
+ }
+ }
+
+ // Sort the requests
+ Collections.sort(left);
+ Collections.sort(right);
+
+ // Simulate the disk head movement
+ if (movingUp) {
+ // Head moving upward, process right-side requests first
+ result.addAll(right);
+ // After reaching the end of the disk, reverse direction and process left-side requests
+ result.add(diskSize - 1); // Simulate the head reaching the end of the disk
+ Collections.reverse(left);
+ result.addAll(left);
+ } else {
+ // Head moving downward, process left-side requests first
+ Collections.reverse(left);
+ result.addAll(left);
+ // After reaching the start of the disk, reverse direction and process right-side requests
+ result.add(0); // Simulate the head reaching the start of the disk
+ result.addAll(right);
+ }
+
+ return result;
+ }
+
+ public int getHeadPosition() {
+ return headPosition;
+ }
+
+ public boolean isMovingUp() {
+ return movingUp;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java
new file mode 100644
index 000000000000..aeddc591b32b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java
@@ -0,0 +1,220 @@
+package com.thealgorithms.searches;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Inverted Index implementation with BM25 Scoring for movie search.
+ * This class supports adding movie documents and searching for terms
+ * within those documents using the BM25 algorithm.
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+class Movie {
+ int docId; // Unique identifier for the movie
+ String name; // Movie name
+ double imdbRating; // IMDb rating of the movie
+ int releaseYear; // Year the movie was released
+ String content; // Full text content (could be the description or script)
+
+ /**
+ * Constructor for the Movie class.
+ * @param docId Unique identifier for the movie.
+ * @param name Name of the movie.
+ * @param imdbRating IMDb rating of the movie.
+ * @param releaseYear Release year of the movie.
+ * @param content Content or description of the movie.
+ */
+ Movie(int docId, String name, double imdbRating, int releaseYear, String content) {
+ this.docId = docId;
+ this.name = name;
+ this.imdbRating = imdbRating;
+ this.releaseYear = releaseYear;
+ this.content = content;
+ }
+
+ /**
+ * Get all the words from the movie's name and content.
+ * Converts the name and content to lowercase and splits on non-word characters.
+ * @return Array of words from the movie name and content.
+ */
+ public String[] getWords() {
+ return (name + " " + content).toLowerCase().split("\\W+");
+ }
+
+ @Override
+ public String toString() {
+ return "Movie{"
+ + "docId=" + docId + ", name='" + name + '\'' + ", imdbRating=" + imdbRating + ", releaseYear=" + releaseYear + '}';
+ }
+}
+
+class SearchResult {
+ int docId; // Unique identifier of the movie document
+ double relevanceScore; // Relevance score based on the BM25 algorithm
+
+ /**
+ * Constructor for SearchResult class.
+ * @param docId Document ID (movie) for this search result.
+ * @param relevanceScore The relevance score based on BM25 scoring.
+ */
+ SearchResult(int docId, double relevanceScore) {
+ this.docId = docId;
+ this.relevanceScore = relevanceScore;
+ }
+
+ public int getDocId() {
+ return docId;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchResult{"
+ + "docId=" + docId + ", relevanceScore=" + relevanceScore + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SearchResult that = (SearchResult) o;
+ return docId == that.docId && Double.compare(that.relevanceScore, relevanceScore) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(docId, relevanceScore);
+ }
+
+ public double getRelevanceScore() {
+ return this.relevanceScore;
+ }
+}
+
+public final class BM25InvertedIndex {
+ private Map> index; // Inverted index mapping terms to document id and frequency
+ private Map movies; // Mapping of movie document IDs to Movie objects
+ private int totalDocuments; // Total number of movies/documents
+ private double avgDocumentLength; // Average length of documents (number of words)
+ private static final double K = 1.5; // BM25 tuning parameter, controls term frequency saturation
+ private static final double B = 0.75; // BM25 tuning parameter, controls length normalization
+
+ /**
+ * Constructor for BM25InvertedIndex.
+ * Initializes the inverted index and movie storage.
+ */
+ BM25InvertedIndex() {
+ index = new HashMap<>();
+ movies = new HashMap<>();
+ totalDocuments = 0;
+ avgDocumentLength = 0.0;
+ }
+
+ /**
+ * Add a movie to the index.
+ * @param docId Unique identifier for the movie.
+ * @param name Name of the movie.
+ * @param imdbRating IMDb rating of the movie.
+ * @param releaseYear Release year of the movie.
+ * @param content Content or description of the movie.
+ */
+ public void addMovie(int docId, String name, double imdbRating, int releaseYear, String content) {
+ Movie movie = new Movie(docId, name, imdbRating, releaseYear, content);
+ movies.put(docId, movie);
+ totalDocuments++;
+
+ // Get words (terms) from the movie's name and content
+ String[] terms = movie.getWords();
+ int docLength = terms.length;
+
+ // Update the average document length
+ avgDocumentLength = (avgDocumentLength * (totalDocuments - 1) + docLength) / totalDocuments;
+
+ // Update the inverted index
+ for (String term : terms) {
+ // Create a new entry if the term is not yet in the index
+ index.putIfAbsent(term, new HashMap<>());
+
+ // Get the list of documents containing the term
+ Map docList = index.get(term);
+ if (docList == null) {
+ docList = new HashMap<>();
+ index.put(term, docList); // Ensure docList is added to the index
+ }
+ // Increment the term frequency in this document
+ docList.put(docId, docList.getOrDefault(docId, 0) + 1);
+ }
+ }
+
+ public int getMoviesLength() {
+ return movies.size();
+ }
+
+ /**
+ * Search for documents containing a term using BM25 scoring.
+ * @param term The search term.
+ * @return A list of search results sorted by relevance score.
+ */
+ public List search(String term) {
+ term = term.toLowerCase(); // Normalize search term
+ if (!index.containsKey(term)) {
+ return new ArrayList<>(); // Return empty list if term not found
+ }
+
+ Map