-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Solve LeetCode #297 'Serialize and Deserialize Binary Tree' [Hard].
- Loading branch information
1 parent
4e95037
commit f08a802
Showing
3 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
103 changes: 103 additions & 0 deletions
103
...e.Challenges/Problems2XX/P297_SerializeAndDeserializeBinaryTree/SerializerDeserializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...de/src/LeetCode.Challenges/Problems2XX/P297_SerializeAndDeserializeBinaryTree/TreeNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | ||
} |
101 changes: 101 additions & 0 deletions
101
...itTests/Problems2XX/P297_SerializeAndDeserializeBinaryTree/SerializerDeserializerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!]; | ||
} | ||
} |