diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java new file mode 100644 index 000000000000..2f9b3b489d56 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java @@ -0,0 +1,100 @@ +package com.thealgorithms.datastructures.trees; + +/** + * Leetcode 606: Construct String from Binary Tree: + * https://leetcode.com/problems/construct-string-from-binary-tree/ + * + * Utility class to convert a {@link BinaryTree} into its string representation. + *

+ * The conversion follows a preorder traversal pattern (root → left → right) + * and uses parentheses to denote the tree structure. + * Empty parentheses "()" are used to explicitly represent missing left children + * when a right child exists, ensuring the structure is unambiguous. + *

+ * + *

Rules:

+ * + * + *

Example:

+ * + *
+ *     Input tree:
+ *           1
+ *          / \
+ *         2   3
+ *          \
+ *           4
+ *
+ *     Output string:
+ *     "1(2()(4))(3)"
+ * 
+ * + *

+ * This implementation matches the logic from LeetCode problem 606: + * Construct String from Binary Tree. + *

+ * + * @author Muhammad Junaid + * @see BinaryTree + */ +public class BinaryTreeToString { + + /** String builder used to accumulate the string representation. */ + private StringBuilder sb; + + /** + * Converts a binary tree (given its root node) to its string representation. + * + * @param root the root node of the binary tree + * @return the string representation of the binary tree, or an empty string if + * the tree is null + */ + public String tree2str(BinaryTree.Node root) { + if (root == null) { + return ""; + } + + sb = new StringBuilder(); + dfs(root); + + // Remove the leading and trailing parentheses added by the root call + return sb.substring(1, sb.length() - 1); + } + + /** + * Performs a recursive depth-first traversal to build the string. + * Each recursive call appends the node value and its children (if any) + * enclosed in parentheses. + * + * @param node the current node being processed + */ + private void dfs(BinaryTree.Node node) { + if (node == null) { + return; + } + + sb.append("(").append(node.data); + + // Recursively build left and right subtrees + if (node.left != null) { + dfs(node.left); + } + + // Handle the special case: right child exists but left child is null + if (node.right != null && node.left == null) { + sb.append("()"); + dfs(node.right); + } else if (node.right != null) { + dfs(node.right); + } + + sb.append(")"); + } +} diff --git a/src/main/java/com/thealgorithms/graph/TopologicalSortDFS.java b/src/main/java/com/thealgorithms/graph/TopologicalSortDFS.java new file mode 100644 index 000000000000..e36d0405683b --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TopologicalSortDFS.java @@ -0,0 +1,131 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of Topological Sort using Depth-First Search + * (DFS). + * + *

+ * This algorithm returns a valid topological ordering of a directed acyclic + * graph (DAG). + * If a cycle is detected, meaning the graph cannot be topologically sorted, + * it returns an empty array. + * + *

+ * Use Case: Determining the order of course completion based on + * prerequisite dependencies + * (commonly known as the “Course Schedule II” problem on LeetCode). + * Problem link: LeetCode — + * Course Schedule II + * + *

+ * Algorithm Overview: + *

+ * + *

+ * Time Complexity: O(V + E) — where V is the number of courses + * (vertices), + * and E is the number of prerequisite relations (edges). + *
+ * Space Complexity: O(V + E) — for adjacency list, recursion stack, and + * auxiliary sets. + * + *

+ * Example: + * + *

+ * int numCourses = 4;
+ * int[][] prerequisites = { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } };
+ * TopologicalSortDFS topo = new TopologicalSortDFS();
+ * int[] order = topo.findOrder(numCourses, prerequisites);
+ * // Possible output: [0, 2, 1, 3]
+ * 
+ * + * @author Muhammad Junaid + */ +public class TopologicalSortDFS { + + /** + * Finds a valid topological order of courses given prerequisite constraints. + * + * @param numCourses the total number of courses labeled from 0 to numCourses + * - 1 + * @param prerequisites an array of prerequisite pairs where each pair [a, b] + * indicates that course {@code a} depends on course + * {@code b} + * @return an integer array representing one possible order to complete all + * courses; + * returns an empty array if it is impossible (i.e., a cycle exists) + */ + public int[] findOrder(int numCourses, int[][] prerequisites) { + Map> prereq = new HashMap<>(); + for (int i = 0; i < numCourses; i++) { + prereq.put(i, new ArrayList<>()); + } + for (int[] pair : prerequisites) { + int crs = pair[0]; + int pre = pair[1]; + prereq.get(crs).add(pre); + } + + List output = new ArrayList<>(); + Set visited = new HashSet<>(); + Set cycle = new HashSet<>(); + + for (int c = 0; c < numCourses; c++) { + if (!dfs(c, prereq, visited, cycle, output)) { + return new int[0]; // Cycle detected — impossible order + } + } + + return output.stream().mapToInt(Integer::intValue).toArray(); + } + + /** + * Performs a depth-first search to visit all prerequisites of a course. + * + * @param crs the current course being visited + * @param prereq adjacency list mapping courses to their prerequisites + * @param visited set of courses that have been completely processed + * @param cycle set of courses currently in the recursion stack (used for + * cycle detection) + * @param output list that accumulates the topological order in reverse + * @return {@code true} if the current course and its prerequisites can be + * processed without cycles; + * {@code false} if a cycle is detected + */ + private boolean dfs(int crs, Map> prereq, Set visited, Set cycle, List output) { + + if (cycle.contains(crs)) { + return false; // Cycle detected + } + if (visited.contains(crs)) { + return true; // Already processed + } + + cycle.add(crs); + for (int pre : prereq.get(crs)) { + if (!dfs(pre, prereq, visited, cycle, output)) { + return false; + } + } + + cycle.remove(crs); + visited.add(crs); + output.add(crs); + return true; + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java new file mode 100644 index 000000000000..2461fd74143d --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.datastructures.trees; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests for the BinaryTreeToString class. + */ +public class BinaryTreeToStringTest { + + @Test + public void testTreeToStringBasic() { + BinaryTree tree = new BinaryTree(); + tree.put(1); + tree.put(2); + tree.put(3); + tree.put(4); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(tree.getRoot()); + + // Output will depend on insertion logic of BinaryTree.put() + // which is BST-style, so result = "1()(2()(3()(4)))" + Assertions.assertEquals("1()(2()(3()(4)))", result); + } + + @Test + public void testSingleNodeTree() { + BinaryTree tree = new BinaryTree(); + tree.put(10); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(tree.getRoot()); + + Assertions.assertEquals("10", result); + } + + @Test + public void testComplexTreeStructure() { + BinaryTree.Node root = new BinaryTree.Node(10); + root.left = new BinaryTree.Node(5); + root.right = new BinaryTree.Node(20); + root.right.left = new BinaryTree.Node(15); + root.right.right = new BinaryTree.Node(25); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(root); + + Assertions.assertEquals("10(5)(20(15)(25))", result); + } + + @Test + public void testNullTree() { + BinaryTreeToString converter = new BinaryTreeToString(); + Assertions.assertEquals("", converter.tree2str(null)); + } +} diff --git a/src/test/java/com/thealgorithms/graph/TopologicalSortDFSTest.java b/src/test/java/com/thealgorithms/graph/TopologicalSortDFSTest.java new file mode 100644 index 000000000000..7e169003ba12 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TopologicalSortDFSTest.java @@ -0,0 +1,76 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TopologicalSortDFSTest { + private TopologicalSortDFS topologicalSortDFS; + + @BeforeEach + public void setUp() { + topologicalSortDFS = new TopologicalSortDFS(); + } + + @Test + public void testSimpleCase() { + // Example: Two courses where 1 depends on 0 + int numCourses = 2; + int[][] prerequisites = {{1, 0}}; + int[] expected = {0, 1}; + + int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites); + + assertArrayEquals(expected, result, "Expected order is [0, 1]."); + } + + @Test + public void testMultipleDependencies() { + // Example: 4 courses with dependencies + // 1 -> 0, 2 -> 0, 3 -> 1, 3 -> 2 + int numCourses = 4; + int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}}; + int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites); + + // Valid answers could be [0,1,2,3] or [0,2,1,3] + int[] expected = {0, 1, 2, 3}; + assertArrayEquals(expected, result, "Valid topological order expected, e.g., [0,1,2,3] or [0,2,1,3]."); + } + + @Test + public void testNoDependencies() { + // Example: 3 courses with no dependencies + int numCourses = 3; + int[][] prerequisites = {}; + int[] expected = {0, 1, 2}; + + int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites); + + assertArrayEquals(expected, result, "Any order is valid when there are no dependencies."); + } + + @Test + public void testCycleGraph() { + // Example: A cycle exists (0 -> 1 -> 0) + int numCourses = 2; + int[][] prerequisites = {{0, 1}, {1, 0}}; + int[] expected = {}; + + int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites); + + assertArrayEquals(expected, result, "Cycle detected, no valid course order."); + } + + @Test + public void testComplexGraph() { + // Complex example: 6 courses + // Dependencies: 5->2, 5->0, 4->0, 4->1, 2->3, 3->1 + int numCourses = 6; + int[][] prerequisites = {{2, 5}, {0, 5}, {0, 4}, {1, 4}, {3, 2}, {1, 3}}; + int[] result = topologicalSortDFS.findOrder(numCourses, prerequisites); + // Valid order: [5, 4, 2, 3, 1, 0] + int[] expected = {5, 4, 0, 2, 3, 1}; + assertArrayEquals(expected, result, "Valid topological order expected such as [5, 4, 0, 2, 3, 1]."); + } +}