From 619c46291127248aea443be2fa0933d364f8096c Mon Sep 17 00:00:00 2001 From: Mind freezer <116938255+vizahat36@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:24:53 +0530 Subject: [PATCH 1/2] Add Tower of Hanoi recursive algorithm with tests --- .../thealgorithms/recursion/TowerOfHanoi.java | 83 +++++++++++++ .../recursion/TowerOfHanoiTest.java | 117 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/main/java/com/thealgorithms/recursion/TowerOfHanoi.java create mode 100644 src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java diff --git a/src/main/java/com/thealgorithms/recursion/TowerOfHanoi.java b/src/main/java/com/thealgorithms/recursion/TowerOfHanoi.java new file mode 100644 index 000000000000..7cb9fb99a31b --- /dev/null +++ b/src/main/java/com/thealgorithms/recursion/TowerOfHanoi.java @@ -0,0 +1,83 @@ +package com.thealgorithms.recursion; + +import java.util.ArrayList; +import java.util.List; + +/** + * A utility class to solve the Tower of Hanoi problem using recursion. + * + * The Tower of Hanoi is a classic recursive algorithm where the objective is to move all disks + * from the source peg to the destination peg following these rules: + * 1. Only one disk can be moved at a time. + * 2. A disk can only be moved if it is the topmost disk on a peg. + * 3. No larger disk may be placed on top of a smaller disk. + * + * The recursive solution divides the problem into three steps: + * Step 1: Move (n-1) disks from source to auxiliary peg. + * Step 2: Move the largest disk from source to destination peg. + * Step 3: Move (n-1) disks from auxiliary to destination peg. + * + * Time Complexity: O(2^n - 1) + * Space Complexity: O(n) - recursion call stack depth + */ +public final class TowerOfHanoi { + + private TowerOfHanoi() { + } + + /** + * Solves the Tower of Hanoi problem for n disks. + * + * @param n number of disks (must be at least 1) + * @param source source peg (typically 'A') + * @param destination destination peg (typically 'C') + * @param auxiliary auxiliary peg (typically 'B') + * @return a list of moves required to solve the problem + * @throws IllegalArgumentException if n is less than 1 + */ + public static List solve(int n, char source, char destination, char auxiliary) { + if (n < 1) { + throw new IllegalArgumentException("Number of disks must be at least 1"); + } + + List moves = new ArrayList<>(); + moveDisks(n, source, destination, auxiliary, moves); + return moves; + } + + /** + * Recursive helper to move disks from source to destination using auxiliary peg. + * Implements the three-step Tower of Hanoi algorithm. + * + * @param n number of disks to move + * @param source source peg + * @param destination destination peg + * @param auxiliary auxiliary peg + * @param moves list to accumulate moves + */ + private static void moveDisks(int n, char source, char destination, char auxiliary, List moves) { + if (n == 1) { + moves.add("Move disk 1 from " + source + " to " + destination); + return; + } + + moveDisks(n - 1, source, auxiliary, destination, moves); + moves.add("Move disk " + n + " from " + source + " to " + destination); + moveDisks(n - 1, auxiliary, destination, source, moves); + } + + /** + * Calculates the number of moves required to solve Tower of Hanoi for n disks. + * Formula: 2^n - 1 + * + * @param n number of disks (must be at least 1) + * @return the number of moves required + * @throws IllegalArgumentException if n is less than 1 + */ + public static long getMoveCount(int n) { + if (n < 1) { + throw new IllegalArgumentException("Number of disks must be at least 1"); + } + return (1L << n) - 1; + } +} diff --git a/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java b/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java new file mode 100644 index 000000000000..f18fead35010 --- /dev/null +++ b/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.recursion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class TowerOfHanoiTest { + + @ParameterizedTest + @MethodSource("diskCountAndMoveCount") + void testMoveCount(int disks, long expectedMoves) { + assertEquals(expectedMoves, TowerOfHanoi.getMoveCount(disks)); + } + + private static Stream diskCountAndMoveCount() { + return Stream.of( + Arguments.of(1, 1L), + Arguments.of(2, 3L), + Arguments.of(3, 7L), + Arguments.of(4, 15L), + Arguments.of(5, 31L), + Arguments.of(10, 1023L) + ); + } + + @ParameterizedTest + @MethodSource("diskCountAndExpectedMoves") + void testSolveReturnsCorrectNumberOfMoves(int disks, long expectedMoveCount) { + List moves = TowerOfHanoi.solve(disks, 'A', 'C', 'B'); + assertEquals(expectedMoveCount, moves.size()); + } + + private static Stream diskCountAndExpectedMoves() { + return Stream.of( + Arguments.of(1, 1L), + Arguments.of(2, 3L), + Arguments.of(3, 7L), + Arguments.of(4, 15L), + Arguments.of(5, 31L) + ); + } + + @Test + void testSolveWithOneDisks() { + List moves = TowerOfHanoi.solve(1, 'A', 'C', 'B'); + assertEquals(1, moves.size()); + assertEquals("Move disk 1 from A to C", moves.get(0)); + } + + @Test + void testSolveWithTwoDisks() { + List moves = TowerOfHanoi.solve(2, 'A', 'C', 'B'); + assertEquals(3, moves.size()); + assertEquals("Move disk 1 from A to B", moves.get(0)); + assertEquals("Move disk 2 from A to C", moves.get(1)); + assertEquals("Move disk 1 from B to C", moves.get(2)); + } + + @Test + void testSolveWithThreeDisks() { + List moves = TowerOfHanoi.solve(3, 'A', 'C', 'B'); + assertEquals(7, moves.size()); + assertEquals("Move disk 1 from A to C", moves.get(0)); + assertEquals("Move disk 3 from A to C", moves.get(3)); + assertEquals("Move disk 1 from A to C", moves.get(6)); + } + + @Test + void testSolveWithDifferentPegs() { + List moves = TowerOfHanoi.solve(2, 'X', 'Z', 'Y'); + assertEquals(3, moves.size()); + assertEquals("Move disk 1 from X to Y", moves.get(0)); + assertEquals("Move disk 2 from X to Z", moves.get(1)); + assertEquals("Move disk 1 from Y to Z", moves.get(2)); + } + + @Test + void testThrowsForNegativeDisks() { + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.solve(-1, 'A', 'C', 'B')); + } + + @Test + void testThrowsForZeroDisks() { + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.solve(0, 'A', 'C', 'B')); + } + + @Test + void testGetMoveCountThrowsForNegativeDisks() { + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.getMoveCount(-1)); + } + + @Test + void testGetMoveCountThrowsForZeroDisks() { + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.getMoveCount(0)); + } + + @ParameterizedTest + @MethodSource("diskCountAndExpectedMoves") + void testSolveMoveCountMatchesGetMoveCount(int disks, long expectedMoveCount) { + List moves = TowerOfHanoi.solve(disks, 'A', 'C', 'B'); + long moveCount = TowerOfHanoi.getMoveCount(disks); + assertEquals(moveCount, moves.size()); + assertEquals(expectedMoveCount, moveCount); + } + + @Test + void testSolveIsNotEmpty() { + List moves = TowerOfHanoi.solve(1, 'A', 'C', 'B'); + assertEquals(false, moves.isEmpty()); + } +} From fedf135f72b4fe6817ad4f073fd9781de07f63c9 Mon Sep 17 00:00:00 2001 From: Mind freezer <116938255+vizahat36@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:59:49 +0530 Subject: [PATCH 2/2] Fix SpotBugs issues and format TowerOfHanoi tests --- .../recursion/TowerOfHanoiTest.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java b/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java index f18fead35010..36f89d9e18ea 100644 --- a/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java +++ b/src/test/java/com/thealgorithms/recursion/TowerOfHanoiTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.recursion; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; @@ -19,14 +20,7 @@ void testMoveCount(int disks, long expectedMoves) { } private static Stream diskCountAndMoveCount() { - return Stream.of( - Arguments.of(1, 1L), - Arguments.of(2, 3L), - Arguments.of(3, 7L), - Arguments.of(4, 15L), - Arguments.of(5, 31L), - Arguments.of(10, 1023L) - ); + return Stream.of(Arguments.of(1, 1L), Arguments.of(2, 3L), Arguments.of(3, 7L), Arguments.of(4, 15L), Arguments.of(5, 31L), Arguments.of(10, 1023L)); } @ParameterizedTest @@ -37,13 +31,7 @@ void testSolveReturnsCorrectNumberOfMoves(int disks, long expectedMoveCount) { } private static Stream diskCountAndExpectedMoves() { - return Stream.of( - Arguments.of(1, 1L), - Arguments.of(2, 3L), - Arguments.of(3, 7L), - Arguments.of(4, 15L), - Arguments.of(5, 31L) - ); + return Stream.of(Arguments.of(1, 1L), Arguments.of(2, 3L), Arguments.of(3, 7L), Arguments.of(4, 15L), Arguments.of(5, 31L)); } @Test @@ -112,6 +100,6 @@ void testSolveMoveCountMatchesGetMoveCount(int disks, long expectedMoveCount) { @Test void testSolveIsNotEmpty() { List moves = TowerOfHanoi.solve(1, 'A', 'C', 'B'); - assertEquals(false, moves.isEmpty()); + assertFalse(moves.isEmpty()); } }