Skip to content
Open
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* </p>
*
* <h2>Rules:</h2>
* <ul>
* <li>Each node is represented as {@code (value)}.</li>
* <li>If a node has only a right child, include {@code ()} before the right
* child
* to indicate the missing left child.</li>
* <li>If a node has no children, it appears as just {@code (value)}.</li>
* <li>The outermost parentheses are removed from the final string.</li>
* </ul>
*
* <h3>Example:</h3>
*
* <pre>
* Input tree:
* 1
* / \
* 2 3
* \
* 4
*
* Output string:
* "1(2()(4))(3)"
* </pre>
*
* <p>
* This implementation matches the logic from LeetCode problem 606:
* <i>Construct String from Binary Tree</i>.
* </p>
*
* @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(")");
}
}
131 changes: 131 additions & 0 deletions src/main/java/com/thealgorithms/graph/TopologicalSortDFS.java
Original file line number Diff line number Diff line change
@@ -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 <b>Topological Sort</b> using <b>Depth-First Search
* (DFS)</b>.
*
* <p>
* 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.
*
* <p>
* <b>Use Case:</b> Determining the order of course completion based on
* prerequisite dependencies
* (commonly known as the “Course Schedule II” problem on LeetCode).
* Problem link: <a href=
* "https://leetcode.com/problems/course-schedule-ii/description/">LeetCode —
* Course Schedule II</a>
*
* <p>
* <b>Algorithm Overview:</b>
* <ul>
* <li>Each course (node) is visited using DFS.</li>
* <li>During traversal, nodes currently in the recursion stack are tracked to
* detect cycles.</li>
* <li>When a node finishes processing, it is added to the output list.</li>
* <li>The output list is then reversed to form a valid topological order.</li>
* </ul>
*
* <p>
* <b>Time Complexity:</b> O(V + E) — where V is the number of courses
* (vertices),
* and E is the number of prerequisite relations (edges).
* <br>
* <b>Space Complexity:</b> O(V + E) — for adjacency list, recursion stack, and
* auxiliary sets.
*
* <p>
* <b>Example:</b>
*
* <pre>
* 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]
* </pre>
*
* @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<Integer, List<Integer>> 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<Integer> output = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
Set<Integer> 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<Integer, List<Integer>> prereq, Set<Integer> visited, Set<Integer> cycle, List<Integer> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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].");
}
}