Skip to content

Commit

Permalink
Solve LeetCode #297 'Serialize and Deserialize Binary Tree' [Hard].
Browse files Browse the repository at this point in the history
  • Loading branch information
eminencegrs committed Nov 7, 2024
1 parent 4e95037 commit f08a802
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Text;

namespace LeetCode.Challenges.Problems2XX.P297_SerializeAndDeserializeBinaryTree;

// This solution implements serialization and deserialization of a binary tree using level-order traversal.
// Key Points:
// 1. Level-Order Traversal: Both serialization and deserialization follow a level-order traversal,
// ensuring that the binary tree structure is preserved.
// 2. Null Nodes: null child nodes are represented by empty entries between semicolons (";").
// In the serialized string, empty spaces between values indicate missing left or right children.
// 3. Queue for Rebuilding the Tree: A queue is used during deserialization to ensure
// that nodes are processed in the correct order.
public class SerializerDeserializer
{
public string? Serialize(TreeNode? root)
{
if (root == null)
{
return string.Empty;
}

var sb = new StringBuilder();

// The queue is used to implement the level-order traversal.
// It starts by enqueuing the root of the tree into the queue.
var queue = new Queue<TreeNode?>();
queue.Enqueue(root);

// The loop continues as long as there are nodes in the queue to process.
// For each node in the queue:
// -- If the node is null, it appends a semicolon (;) to represent the absence of a child node.
// This indicates a missing left or right child in the tree.
// -- If the node is not null, it appends its value to the StringBuilder, followed by a semicolon (;),
// and enqueue its left and right children for further processing.
// Even if the children are null, they are enqueued,
// and the null entries are handled in the next iterations.
while (queue.Count > 0)
{
var node = queue.Dequeue();
if (node == null)
{
sb.Append(';');
}
else
{
sb.Append(node.Value);
sb.Append(';');
queue.Enqueue(node.Left);
queue.Enqueue(node.Right);
}
}

// Trim the trailing semicolon.
return sb.ToString().TrimEnd(';');
}

public TreeNode? Deserialize(string? data)
{
if (string.IsNullOrWhiteSpace(data))
{
return null;
}

var nodes = data.Split(";");
if (nodes.Length == 0)
{
return null;
}

var root = new TreeNode(int.Parse(nodes[0]));

// A queue is used to track the nodes in the tree while it reconstructs the tree.
var queue = new Queue<TreeNode>();
queue.Enqueue(root);

// The 'index' variable is used to iterate through the serialized data
// starting from the 2nd element, as the 1st one is used for the root.
var index = 1;

while (queue.Count > 0 && index < nodes.Length)
{
var currentNode = queue.Dequeue();

if (index < nodes.Length && !string.IsNullOrEmpty(nodes[index]))
{
currentNode.Left = new TreeNode(int.Parse(nodes[index]));
queue.Enqueue(currentNode.Left);
}

index++;

if (index < nodes.Length && !string.IsNullOrEmpty(nodes[index]))
{
currentNode.Right = new TreeNode(int.Parse(nodes[index]));
queue.Enqueue(currentNode.Right);
}

index++;
}

return root;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace LeetCode.Challenges.Problems2XX.P297_SerializeAndDeserializeBinaryTree;

public class TreeNode(int value)
{
public int Value { get; set; } = value;
public TreeNode? Left { get; set; }
public TreeNode? Right { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using LeetCode.Challenges.Problems2XX.P297_SerializeAndDeserializeBinaryTree;
using Shouldly;
using Xunit;

namespace LeetCode.Challenges.UnitTests.Problems2XX.P297_SerializeAndDeserializeBinaryTree;

public class SerializerDeserializerTests
{
[Theory]
[MemberData(nameof(TestDataForSerialization))]
public void GivenTree_WhenSerialize_ThenStringAsExpected(TreeNode node, string expectedResult)
{
var cut = new SerializerDeserializer();
var actualResult = cut.Serialize(node);
actualResult.ShouldBeEquivalentTo(expectedResult);
}

[Theory]
[MemberData(nameof(TestDataForDeserialization))]
public void GivenString_WhenDeserialize_ThenTreeAsExpected(string serializedTree, TreeNode expectedResult)
{
var cut = new SerializerDeserializer();
var actualResult = cut.Deserialize(serializedTree);
actualResult.ShouldBeEquivalentTo(expectedResult);
}

[Theory]
[MemberData(nameof(TestData))]
public void GivenTree_WhenSerializeAndDeserialize_ThenResultAsExpected(TreeNode node, TreeNode expectedResult)
{
var serializer = new SerializerDeserializer();
var deserializer = new SerializerDeserializer();
var actualResult = deserializer.Deserialize(serializer.Serialize(node));
actualResult.ShouldBeEquivalentTo(expectedResult);
}

// Please, note: the following tree is used for all the test cases.
// 1
// / \
// 2 3
// / \
// 4 5

public static IEnumerable<object[]> TestDataForSerialization()
{
var root = new TreeNode(1)
{
Left = new TreeNode(2),
Right = new TreeNode(3)
{
Left = new TreeNode(4),
Right = new TreeNode(5)
}
};

yield return [root, "1;2;3;;;4;5"];
yield return [null!, ""];
}

public static IEnumerable<object[]> TestDataForDeserialization()
{
var root = new TreeNode(1)
{
Left = new TreeNode(2),
Right = new TreeNode(3)
{
Left = new TreeNode(4),
Right = new TreeNode(5)
}
};

yield return ["1;2;3;;;4;5", root];
yield return ["", null!];
}

public static IEnumerable<object[]> TestData()
{
var input = new TreeNode(1)
{
Left = new TreeNode(2),
Right = new TreeNode(3)
{
Left = new TreeNode(4),
Right = new TreeNode(5)
}
};

var output = new TreeNode(1)
{
Left = new TreeNode(2),
Right = new TreeNode(3)
{
Left = new TreeNode(4),
Right = new TreeNode(5)
}
};

yield return [input, output];
yield return [null!, null!];
}
}

0 comments on commit f08a802

Please sign in to comment.