Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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].");
}
}