diff --git a/src/main/java/com/thealgorithms/divideandconquer/Factorial.java b/src/main/java/com/thealgorithms/divideandconquer/Factorial.java new file mode 100644 index 000000000000..ec9db6f2ff5d --- /dev/null +++ b/src/main/java/com/thealgorithms/divideandconquer/Factorial.java @@ -0,0 +1,39 @@ +package com.thealgorithms.divideandconquer; + +/** + * Computes the factorial of a non-negative integer using an iterative + * approach to avoid recursion overhead and stack overflow risks. + * + *

This implementation improves upon the original recursive version by + * eliminating deep recursion, reducing space complexity from O(n) to O(1), + * and improving runtime efficiency on the JVM. + * + *

Time Complexity: O(n) + *
Space Complexity: O(1) + */ +public final class Factorial { + + private Factorial() { + // Utility class + } + + /** + * Returns the factorial of the given non-negative number. + * + * @param n the number to compute factorial for + * @return factorial of n (n!) + * @throws IllegalArgumentException if n is negative + */ + public static long factorial(long n) { + if (n < 0) { + throw new IllegalArgumentException("Negative input not allowed"); + } + + long result = 1; + for (long i = 2; i <= n; i++) { + result *= i; + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/Factorial.java b/src/main/java/com/thealgorithms/maths/Factorial.java deleted file mode 100644 index 511cc1f84f05..000000000000 --- a/src/main/java/com/thealgorithms/maths/Factorial.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.thealgorithms.maths; - -public final class Factorial { - private Factorial() { - } - - /** - * Calculate factorial N using iteration - * - * @param n the number - * @return the factorial of {@code n} - */ - public static long factorial(int n) { - if (n < 0) { - throw new IllegalArgumentException("Input number cannot be negative"); - } - long factorial = 1; - for (int i = 1; i <= n; ++i) { - factorial *= i; - } - return factorial; - } -} diff --git a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java b/src/main/java/com/thealgorithms/maths/FactorialRecursion.java deleted file mode 100644 index d9bafd1e39e9..000000000000 --- a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.thealgorithms.maths; - -public final class FactorialRecursion { - private FactorialRecursion() { - } - /** - * Recursive FactorialRecursion Method - * - * @param n The number to factorial - * @return The factorial of the number - */ - public static long factorial(int n) { - if (n < 0) { - throw new IllegalArgumentException("number is negative"); - } - return n == 0 || n == 1 ? 1 : n * factorial(n - 1); - } -} diff --git a/src/test/java/com/thealgorithms/divideandconquer/FactorialTest.java b/src/test/java/com/thealgorithms/divideandconquer/FactorialTest.java new file mode 100644 index 000000000000..50d63c5e970b --- /dev/null +++ b/src/test/java/com/thealgorithms/divideandconquer/FactorialTest.java @@ -0,0 +1,82 @@ +package com.thealgorithms.divideandconquer; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class FactorialTest { + // -------------------------------------------------------- + // SECTION 1: Basic Correctness Tests + // -------------------------------------------------------- + + @Test + void testFactorialOfFive() { + assertEquals(120, Factorial.factorial(5)); + } + + @Test + void testFactorialOfZero() { + assertEquals(1, Factorial.factorial(0)); + } + + @Test + void testNegativeInputThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { Factorial.factorial(-5); }); + } + + // -------------------------------------------------------- + // SECTION 2: Analysis-Oriented Test (Performance Awareness) + // -------------------------------------------------------- + + @Test + void testLargeInputPerformance() { + long start = System.currentTimeMillis(); + long result = Factorial.factorial(15); + long end = System.currentTimeMillis(); + + assertEquals(1307674368000L, result); + assertTrue((end - start) < 50, "Factorial(15) took too long to compute"); + } + + // -------------------------------------------------------- + // SECTION 3: Algorithmic Improvement Demonstration + // -------------------------------------------------------- + + /** + * Local copy of the original recursive implementation + * used only for comparing performance inside the test. + */ + private long recursiveFactorial(long n) { + if (n < 0) { + throw new IllegalArgumentException("Negative input not allowed"); + } + if (n == 0 || n == 1) { + return 1; + } + return n * recursiveFactorial(n - 1); + } + + @Test + void testIterativeFasterThanRecursive() { + long n = 18; + + long startRec = System.nanoTime(); + long recResult = recursiveFactorial(n); + long endRec = System.nanoTime(); + + long startIter = System.nanoTime(); + long iterResult = Factorial.factorial(n); + long endIter = System.nanoTime(); + + assertEquals(recResult, iterResult); + assertTrue(endIter - startIter < endRec - startRec, "Iterative version should outperform recursive version"); + } + + @Test + void testIterativeHandlesLargerInputsSafely() { + assertDoesNotThrow(() -> Factorial.factorial(20)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java b/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java deleted file mode 100644 index db18b46356b4..000000000000 --- a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.thealgorithms.maths; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class FactorialRecursionTest { - @ParameterizedTest - @MethodSource("inputStream") - void testFactorialRecursion(long expected, int number) { - assertEquals(expected, FactorialRecursion.factorial(number)); - } - - private static Stream inputStream() { - return Stream.of(Arguments.of(1, 0), Arguments.of(1, 1), Arguments.of(2, 2), Arguments.of(6, 3), Arguments.of(120, 5)); - } - - @Test - void testThrowsForNegativeInput() { - assertThrows(IllegalArgumentException.class, () -> FactorialRecursion.factorial(-1)); - } -} diff --git a/src/test/java/com/thealgorithms/maths/FactorialTest.java b/src/test/java/com/thealgorithms/maths/FactorialTest.java deleted file mode 100644 index b38dc45589ee..000000000000 --- a/src/test/java/com/thealgorithms/maths/FactorialTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.thealgorithms.maths; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -public class FactorialTest { - private static final String EXCEPTION_MESSAGE = "Input number cannot be negative"; - - @Test - public void testWhenInvalidInoutProvidedShouldThrowException() { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Factorial.factorial(-1)); - assertEquals(exception.getMessage(), EXCEPTION_MESSAGE); - } - - @Test - public void testCorrectFactorialCalculation() { - assertEquals(1, Factorial.factorial(0)); - assertEquals(1, Factorial.factorial(1)); - assertEquals(120, Factorial.factorial(5)); - assertEquals(3628800, Factorial.factorial(10)); - } -}