+ * The quadratic mean is calculated as: √[(x₁^2 × x₂^2 × ... × xₙ^2)/n]
+ *
+ *
+ * Example: For numbers [1, 7], the quadratic mean is √[(1^2+7^2)/2] = √25 = 5.0
+ *
+ *
+ * @param numbers the input numbers (must not be empty)
+ * @return the quadratic mean of the input numbers
+ * @throws IllegalArgumentException if the input is empty
+ * @see numbers) {
+ checkIfNotEmpty(numbers);
+ double sumOfSquares = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y * y);
+ int size = IterableUtils.size(numbers);
+ return Math.pow(sumOfSquares / size, 0.5);
+ }
+
/**
* Validates that the input iterable is not empty.
*
diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
index 399c3f1e041f..d096e0a8d7cd 100644
--- a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
+++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
@@ -41,7 +41,7 @@ public static void resolveCollision(Body a, Body b) {
double dy = b.y - a.y;
double dist = Math.hypot(dx, dy);
- if (dist == 0) {
+ if (dist < a.radius + b.radius) {
return; // overlapping
}
diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
index 3ac6be25bf53..d24cc1c774bc 100644
--- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
+++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java
@@ -1,3 +1,14 @@
+/**
+ * Interpolation Search estimates the position of the target value
+ * based on the distribution of values.
+ *
+ * Example:
+ * Input: [10, 20, 30, 40], target = 30
+ * Output: Index = 2
+ *
+ * Time Complexity: O(log log n) (average case)
+ * Space Complexity: O(1)
+ */
package com.thealgorithms.searches;
/**
diff --git a/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java
new file mode 100644
index 000000000000..2e9f6863c90a
--- /dev/null
+++ b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java
@@ -0,0 +1,67 @@
+package com.thealgorithms.stacks;
+
+import java.util.Stack;
+
+/**
+ * Calculates the stock span for each day in a series of stock prices.
+ *
+ * The span of a price on a given day is the number of consecutive days ending on that day
+ * for which the price was less than or equal to the current day's price.
+ *
+ *
Idea: keep a stack of indices whose prices are strictly greater than the current price.
+ * While processing each day, pop smaller or equal prices because they are part of the current
+ * span. After popping, the nearest greater price left on the stack tells us where the span stops.
+ *
+ *
Time complexity is O(n) because each index is pushed onto the stack once and popped at most
+ * once, so the total number of stack operations grows linearly with the number of prices. This
+ * makes the stack approach efficient because it avoids rechecking earlier days repeatedly, unlike
+ * a naive nested-loop solution that can take O(n^2) time.
+ *
+ *
Example: for prices [100, 80, 60, 70, 60, 75, 85], the spans are
+ * [1, 1, 1, 2, 1, 4, 6].
+ */
+public final class StockSpanProblem {
+ private StockSpanProblem() {
+ }
+
+ /**
+ * Calculates the stock span for each price in the input array.
+ *
+ * @param prices the stock prices
+ * @return the span for each day
+ * @throws IllegalArgumentException if the input array is null
+ */
+ public static int[] calculateSpan(int[] prices) {
+ if (prices == null) {
+ throw new IllegalArgumentException("Input prices cannot be null");
+ }
+
+ int[] spans = new int[prices.length];
+ Stack stack = new Stack<>();
+
+ // Small example:
+ // prices = [100, 80, 60, 70]
+ // spans = [ 1, 1, 1, 2]
+ // When we process 70, we pop 60 because 60 <= 70, so the span becomes 2.
+ //
+ // The stack stores indices of days with prices greater than the current day's price.
+ for (int index = 0; index < prices.length; index++) {
+ // Remove all previous days whose prices are less than or equal to the current price.
+ while (!stack.isEmpty() && prices[stack.peek()] <= prices[index]) {
+ stack.pop();
+ }
+
+ // If the stack is empty, there is no earlier day with a greater price,
+ // so the count will be from day 0 to this day (index + 1).
+ //
+ // Otherwise, the span is the number of days between
+ // the nearest earlier day with a greater price and the current day.
+ spans[index] = stack.isEmpty() ? index + 1 : index - stack.peek();
+
+ // Store the current index as a candidate for future span calculations.
+ stack.push(index);
+ }
+
+ return spans;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java
new file mode 100644
index 000000000000..b8b10dcf4538
--- /dev/null
+++ b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java
@@ -0,0 +1,79 @@
+package com.thealgorithms.strings;
+
+/**
+ * Kasai's Algorithm for constructing the Longest Common Prefix (LCP) array.
+ *
+ *
+ * The LCP array stores the lengths of the longest common prefixes between
+ * lexicographically adjacent suffixes of a string. Kasai's algorithm computes
+ * this array in O(N) time given the string and its suffix array.
+ *
+ *
+ * @see LCP array - Wikipedia
+ */
+public final class KasaiAlgorithm {
+
+ private KasaiAlgorithm() {
+ }
+
+ /**
+ * Computes the LCP array using Kasai's algorithm.
+ *
+ * @param text the original string
+ * @param suffixArr the suffix array of the string
+ * @return the LCP array of length N, where LCP[i] is the length of the longest
+ * common prefix of the suffixes indexed by suffixArr[i] and suffixArr[i+1].
+ * The last element LCP[N-1] is always 0.
+ * @throws IllegalArgumentException if text or suffixArr is null, or their lengths differ
+ */
+ public static int[] kasai(String text, int[] suffixArr) {
+ if (text == null || suffixArr == null) {
+ throw new IllegalArgumentException("Text and suffix array must not be null.");
+ }
+ int n = text.length();
+ if (suffixArr.length != n) {
+ throw new IllegalArgumentException("Suffix array length must match text length.");
+ }
+ if (n == 0) {
+ return new int[0];
+ }
+
+ // Compute the inverse suffix array
+ // invSuff[i] stores the index of the suffix text.substring(i) in the suffix array
+ int[] invSuff = new int[n];
+ for (int i = 0; i < n; i++) {
+ if (suffixArr[i] < 0 || suffixArr[i] >= n) {
+ throw new IllegalArgumentException("Suffix array contains out-of-bounds index.");
+ }
+ invSuff[suffixArr[i]] = i;
+ }
+
+ int[] lcp = new int[n];
+ int k = 0; // Length of the longest common prefix
+
+ for (int i = 0; i < n; i++) {
+ // Suffix at index i has not a next suffix in suffix array
+ int rank = invSuff[i];
+ if (rank == n - 1) {
+ k = 0;
+ continue;
+ }
+
+ int nextSuffixIndex = suffixArr[rank + 1];
+
+ // Directly match characters to find LCP
+ while (i + k < n && nextSuffixIndex + k < n && text.charAt(i + k) == text.charAt(nextSuffixIndex + k)) {
+ k++;
+ }
+
+ lcp[rank] = k;
+
+ // Delete the starting character from the string
+ if (k > 0) {
+ k--;
+ }
+ }
+
+ return lcp;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
index 5d1733a3e97c..6b6e670a258b 100644
--- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java
@@ -1,11 +1,9 @@
package com.thealgorithms.datastructures.hashmap.hashing;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
class GenericHashMapUsingArrayTest {
@@ -16,10 +14,10 @@ void testGenericHashmapWhichUsesArrayAndBothKeyAndValueAreStrings() {
map.put("Nepal", "Kathmandu");
map.put("India", "New Delhi");
map.put("Australia", "Sydney");
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals("Kathmandu", map.get("Nepal"));
- assertEquals("Sydney", map.get("Australia"));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals("Kathmandu", map.get("Nepal"));
+ Assertions.assertEquals("Sydney", map.get("Australia"));
}
@Test
@@ -29,12 +27,12 @@ void testGenericHashmapWhichUsesArrayAndKeyIsStringValueIsInteger() {
map.put("Nepal", 25);
map.put("India", 101);
map.put("Australia", 99);
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals(25, map.get("Nepal"));
- assertEquals(99, map.get("Australia"));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals(25, map.get("Nepal"));
+ Assertions.assertEquals(99, map.get("Australia"));
map.remove("Nepal");
- assertFalse(map.containsKey("Nepal"));
+ Assertions.assertFalse(map.containsKey("Nepal"));
}
@Test
@@ -44,11 +42,11 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() {
map.put(34, "Kathmandu");
map.put(46, "New Delhi");
map.put(89, "Sydney");
- assertNotNull(map);
- assertEquals(4, map.size());
- assertEquals("Sydney", map.get(89));
- assertEquals("Washington DC", map.get(101));
- assertTrue(map.containsKey(46));
+ Assertions.assertNotNull(map);
+ Assertions.assertEquals(4, map.size());
+ Assertions.assertEquals("Sydney", map.get(89));
+ Assertions.assertEquals("Washington DC", map.get(101));
+ Assertions.assertTrue(map.containsKey(46));
}
@Test
@@ -56,7 +54,7 @@ void testRemoveNonExistentKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
map.remove("Nepal"); // Attempting to remove a non-existent key
- assertEquals(1, map.size()); // Size should remain the same
+ Assertions.assertEquals(1, map.size()); // Size should remain the same
}
@Test
@@ -65,8 +63,8 @@ void testRehashing() {
for (int i = 0; i < 20; i++) {
map.put("Key" + i, "Value" + i);
}
- assertEquals(20, map.size()); // Ensure all items were added
- assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash
+ Assertions.assertEquals(20, map.size()); // Ensure all items were added
+ Assertions.assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash
}
@Test
@@ -74,7 +72,7 @@ void testUpdateValueForExistingKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
map.put("USA", "New Washington DC"); // Updating value for existing key
- assertEquals("New Washington DC", map.get("USA"));
+ Assertions.assertEquals("New Washington DC", map.get("USA"));
}
@Test
@@ -83,14 +81,154 @@ void testToStringMethod() {
map.put("USA", "Washington DC");
map.put("Nepal", "Kathmandu");
String expected = "{USA : Washington DC, Nepal : Kathmandu}";
- assertEquals(expected, map.toString());
+ Assertions.assertEquals(expected, map.toString());
}
@Test
void testContainsKey() {
GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
map.put("USA", "Washington DC");
- assertTrue(map.containsKey("USA"));
- assertFalse(map.containsKey("Nepal"));
+ Assertions.assertTrue(map.containsKey("USA"));
+ Assertions.assertFalse(map.containsKey("Nepal"));
+ }
+
+ // ======= Added tests from the new version =======
+
+ @Test
+ void shouldThrowNullPointerExceptionForNullKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ String nullKey = null; // Use variable to avoid static analysis false positive
+ Assertions.assertThrows(NullPointerException.class, () -> map.put(nullKey, "value"));
+ }
+
+ @Test
+ void shouldStoreNullValueForKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("keyWithNullValue", null);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertNull(map.get("keyWithNullValue"));
+ // Note: containsKey returns false for null values due to implementation
+ Assertions.assertFalse(map.containsKey("keyWithNullValue"));
+ }
+
+ @Test
+ void shouldHandleCollisionWhenKeysHashToSameBucket() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ Integer key1 = 1;
+ Integer key2 = 17;
+ map.put(key1, 100);
+ map.put(key2, 200);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(key1));
+ Assertions.assertEquals(200, map.get(key2));
+ Assertions.assertTrue(map.containsKey(key1));
+ Assertions.assertTrue(map.containsKey(key2));
+ }
+
+ @Test
+ void shouldHandleEmptyStringAsKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("", "valueForEmptyKey");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("valueForEmptyKey", map.get(""));
+ Assertions.assertTrue(map.containsKey(""));
+ }
+
+ @Test
+ void shouldHandleEmptyStringAsValue() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("keyForEmptyValue", "");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("", map.get("keyForEmptyValue"));
+ Assertions.assertTrue(map.containsKey("keyForEmptyValue"));
+ }
+
+ @Test
+ void shouldHandleNegativeIntegerKeys() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(-1, 100);
+ map.put(-100, 200);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(-1));
+ Assertions.assertEquals(200, map.get(-100));
+ Assertions.assertTrue(map.containsKey(-1));
+ Assertions.assertTrue(map.containsKey(-100));
+ }
+
+ @Test
+ void shouldHandleZeroAsKey() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(0, 100);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals(100, map.get(0));
+ Assertions.assertTrue(map.containsKey(0));
+ }
+
+ @Test
+ void shouldHandleStringWithSpecialCharacters() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put("key!@#$%^&*()", "value<>?/\\|");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("value<>?/\\|", map.get("key!@#$%^&*()"));
+ Assertions.assertTrue(map.containsKey("key!@#$%^&*()"));
+ }
+
+ @Test
+ void shouldHandleLongStrings() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ StringBuilder longKey = new StringBuilder();
+ StringBuilder longValue = new StringBuilder();
+ for (int i = 0; i < 1000; i++) {
+ longKey.append("a");
+ longValue.append("b");
+ }
+ String key = longKey.toString();
+ String value = longValue.toString();
+ map.put(key, value);
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals(value, map.get(key));
+ Assertions.assertTrue(map.containsKey(key));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"a", "ab", "abc", "test", "longerString"})
+ void shouldHandleKeysOfDifferentLengths(String key) {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ map.put(key, "value");
+ Assertions.assertEquals(1, map.size());
+ Assertions.assertEquals("value", map.get(key));
+ Assertions.assertTrue(map.containsKey(key));
+ }
+
+ @Test
+ void shouldHandleUpdateOnExistingKeyInCollisionBucket() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ Integer key1 = 1;
+ Integer key2 = 17;
+ map.put(key1, 100);
+ map.put(key2, 200);
+ Assertions.assertEquals(2, map.size());
+ map.put(key2, 999);
+ Assertions.assertEquals(2, map.size());
+ Assertions.assertEquals(100, map.get(key1));
+ Assertions.assertEquals(999, map.get(key2));
+ Assertions.assertTrue(map.containsKey(key1));
+ Assertions.assertTrue(map.containsKey(key2));
+ }
+
+ @Test
+ void shouldHandleExactlyLoadFactorBoundary() {
+ GenericHashMapUsingArray map = new GenericHashMapUsingArray<>();
+ // Fill exactly to load factor (12 items with capacity 16 and 0.75 load factor)
+ for (int i = 0; i < 12; i++) {
+ map.put(i, i * 10);
+ }
+ Assertions.assertEquals(12, map.size());
+ // Act - This should trigger rehash on 13th item
+ map.put(12, 120);
+ // Assert - Rehash should have happened
+ Assertions.assertEquals(13, map.size());
+ Assertions.assertEquals(120, map.get(12));
+ Assertions.assertTrue(map.containsKey(12));
}
}
diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java
index deee0a931910..853fdbea3963 100644
--- a/src/test/java/com/thealgorithms/maths/MeansTest.java
+++ b/src/test/java/com/thealgorithms/maths/MeansTest.java
@@ -172,6 +172,53 @@ void testHarmonicMeanWithLinkedList() {
assertEquals(expected, Means.harmonic(numbers), EPSILON);
}
+ // ========== Quadratic Mean Tests ==========
+
+ @Test
+ void testQuadraticMeanThrowsExceptionForEmptyList() {
+ List numbers = new ArrayList<>();
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.quadratic(numbers));
+ assertTrue(exception.getMessage().contains("Empty list"));
+ }
+
+ @Test
+ void testQuadraticMeanSingleNumber() {
+ LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5));
+ assertEquals(2.5, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanTwoNumbers() {
+ List numbers = Arrays.asList(1.0, 7.0);
+ assertEquals(5.0, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanMultipleNumbers() {
+ Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 3.0, 7.5, 10.0));
+ double expected = Math.sqrt(34.5);
+ assertEquals(expected, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanThreeNumbers() {
+ List numbers = Arrays.asList(3.0, 6.0, 9.0);
+ double expected = Math.sqrt(42.0);
+ assertEquals(expected, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanIdenticalNumbers() {
+ List numbers = Arrays.asList(5.0, 5.0, 5.0);
+ assertEquals(5.0, Means.quadratic(numbers), EPSILON);
+ }
+
+ @Test
+ void testQuadraticMeanWithLinkedList() {
+ LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 5.0, 11.0));
+ assertEquals(7.0, Means.quadratic(numbers), EPSILON);
+ }
+
// ========== Additional Edge Case Tests ==========
@Test
@@ -198,21 +245,25 @@ void testAllMeansConsistencyForIdenticalValues() {
double arithmetic = Means.arithmetic(numbers);
double geometric = Means.geometric(numbers);
double harmonic = Means.harmonic(numbers);
+ double quadratic = Means.quadratic(numbers);
assertEquals(7.5, arithmetic, EPSILON);
assertEquals(7.5, geometric, EPSILON);
assertEquals(7.5, harmonic, EPSILON);
+ assertEquals(7.5, quadratic, EPSILON);
}
@Test
void testMeansRelationship() {
- // For positive numbers, harmonic mean ≤ geometric mean ≤ arithmetic mean
+ // For positive numbers, harmonic mean ≤ geometric mean ≤ arithmetic mean ≤ quadratic mean
List numbers = Arrays.asList(2.0, 4.0, 8.0);
double arithmetic = Means.arithmetic(numbers);
double geometric = Means.geometric(numbers);
double harmonic = Means.harmonic(numbers);
+ double quadratic = Means.quadratic(numbers);
assertTrue(harmonic <= geometric, "Harmonic mean should be ≤ geometric mean");
assertTrue(geometric <= arithmetic, "Geometric mean should be ≤ arithmetic mean");
+ assertTrue(arithmetic <= quadratic, "Arithmetic mean should be ≤ quadratic mean");
}
}
diff --git a/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java
new file mode 100644
index 000000000000..2e4ea74691da
--- /dev/null
+++ b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.stacks;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class StockSpanProblemTest {
+
+ @ParameterizedTest
+ @MethodSource("validTestCases")
+ void testCalculateSpan(int[] prices, int[] expectedSpans) {
+ assertArrayEquals(expectedSpans, StockSpanProblem.calculateSpan(prices));
+ }
+
+ private static Stream validTestCases() {
+ return Stream.of(Arguments.of(new int[] {10, 4, 5, 90, 120, 80}, new int[] {1, 1, 2, 4, 5, 1}), Arguments.of(new int[] {100, 50, 60, 70, 80, 90}, new int[] {1, 1, 2, 3, 4, 5}), Arguments.of(new int[] {5, 4, 3, 2, 1}, new int[] {1, 1, 1, 1, 1}),
+ Arguments.of(new int[] {1, 2, 3, 4, 5}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {10, 20, 30, 40, 50}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {100, 80, 60, 70, 60, 75, 85}, new int[] {1, 1, 1, 2, 1, 4, 6}),
+ Arguments.of(new int[] {7, 7, 7, 7}, new int[] {1, 2, 3, 4}), Arguments.of(new int[] {}, new int[] {}), Arguments.of(new int[] {42}, new int[] {1}));
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidTestCases")
+ void testCalculateSpanInvalidInput(int[] prices) {
+ assertThrows(IllegalArgumentException.class, () -> StockSpanProblem.calculateSpan(prices));
+ }
+
+ private static Stream invalidTestCases() {
+ return Stream.of(Arguments.of((int[]) null));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java
new file mode 100644
index 000000000000..c22cc77df18a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.strings;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class KasaiAlgorithmTest {
+
+ @Test
+ public void testKasaiBanana() {
+ String text = "banana";
+ // Suffixes:
+ // 0: banana
+ // 1: anana
+ // 2: nana
+ // 3: ana
+ // 4: na
+ // 5: a
+ //
+ // Sorted Suffixes:
+ // 5: a
+ // 3: ana
+ // 1: anana
+ // 0: banana
+ // 4: na
+ // 2: nana
+ int[] suffixArr = {5, 3, 1, 0, 4, 2};
+
+ int[] expectedLcp = {1, 3, 0, 0, 2, 0};
+
+ assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr));
+ }
+
+ @Test
+ public void testKasaiAaaa() {
+ String text = "aaaa";
+ // Sorted Suffixes:
+ // 3: a
+ // 2: aa
+ // 1: aaa
+ // 0: aaaa
+ int[] suffixArr = {3, 2, 1, 0};
+ int[] expectedLcp = {1, 2, 3, 0};
+
+ assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr));
+ }
+
+ @Test
+ public void testKasaiEmptyString() {
+ assertArrayEquals(new int[0], KasaiAlgorithm.kasai("", new int[0]));
+ }
+
+ @Test
+ public void testKasaiSingleChar() {
+ assertArrayEquals(new int[] {0}, KasaiAlgorithm.kasai("A", new int[] {0}));
+ }
+
+ @Test
+ public void testKasaiNullTextOrSuffixArray() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai(null, new int[] {0}));
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", null));
+ }
+
+ @Test
+ public void testKasaiInvalidSuffixArrayLength() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {0, 1}));
+ }
+
+ @Test
+ public void testKasaiInvalidSuffixArrayIndex() {
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {1})); // Out of bounds
+ assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {-1})); // Out of bounds
+ }
+}