Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

449. Serialize and Deserialize BST - Leetcode Solution

Code Implementation

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Codec:
    def serialize(self, root):
        vals = []
        def preorder(node):
            if node:
                vals.append(str(node.val))
                preorder(node.left)
                preorder(node.right)
        preorder(root)
        return ' '.join(vals)

    def deserialize(self, data):
        if not data:
            return None
        vals = list(map(int, data.split()))
        def build(min_val, max_val):
            if vals and min_val < vals[0] < max_val:
                val = vals.pop(0)
                node = TreeNode(val)
                node.left = build(min_val, val)
                node.right = build(val, max_val)
                return node
            return None
        return build(float('-inf'), float('inf'))
      
// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Codec {
public:
    void preorder(TreeNode* root, string &s) {
        if (!root) return;
        s += to_string(root->val) + " ";
        preorder(root->left, s);
        preorder(root->right, s);
    }

    string serialize(TreeNode* root) {
        string s;
        preorder(root, s);
        return s;
    }

    TreeNode* build(queue<int> &q, int minVal, int maxVal) {
        if (q.empty()) return nullptr;
        int val = q.front();
        if (val <= minVal || val >= maxVal) return nullptr;
        q.pop();
        TreeNode* node = new TreeNode(val);
        node->left = build(q, minVal, val);
        node->right = build(q, val, maxVal);
        return node;
    }

    TreeNode* deserialize(string data) {
        if (data.empty()) return nullptr;
        stringstream ss(data);
        int x;
        queue<int> q;
        while (ss >> x) q.push(x);
        return build(q, INT_MIN, INT_MAX);
    }
};
      
// Definition for a binary tree node.
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class Codec {
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        preorder(root, sb);
        return sb.toString().trim();
    }

    private void preorder(TreeNode node, StringBuilder sb) {
        if (node == null) return;
        sb.append(node.val).append(" ");
        preorder(node.left, sb);
        preorder(node.right, sb);
    }

    private int idx;
    public TreeNode deserialize(String data) {
        if (data.isEmpty()) return null;
        String[] vals = data.split("\\s+");
        idx = 0;
        return build(vals, Integer.MIN_VALUE, Integer.MAX_VALUE);
    }

    private TreeNode build(String[] vals, int min, int max) {
        if (idx == vals.length) return null;
        int val = Integer.parseInt(vals[idx]);
        if (val <= min || val >= max) return null;
        idx++;
        TreeNode node = new TreeNode(val);
        node.left = build(vals, min, val);
        node.right = build(vals, val, max);
        return node;
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
var Codec = function() {};

Codec.prototype.serialize = function(root) {
    let vals = [];
    function preorder(node) {
        if (!node) return;
        vals.push(node.val);
        preorder(node.left);
        preorder(node.right);
    }
    preorder(root);
    return vals.join(' ');
};

Codec.prototype.deserialize = function(data) {
    if (!data) return null;
    let vals = data.split(' ').map(Number);
    function build(min, max) {
        if (!vals.length) return null;
        if (vals[0] <= min || vals[0] >= max) return null;
        let val = vals.shift();
        let node = new TreeNode(val);
        node.left = build(min, val);
        node.right = build(val, max);
        return node;
    }
    return build(-Infinity, Infinity);
};
      

Problem Description

The task is to design an algorithm to serialize and deserialize a Binary Search Tree (BST). Serialization means converting the tree into a string so that it can be saved to a file or transferred over a network. Deserialization means reconstructing the tree from that string.

Specifically, you are given the root node of a BST. You need to implement two functions:

  • serialize(root): Takes the root node and returns a string representation of the tree.
  • deserialize(data): Takes the encoded string and reconstructs the exact same BST.
Constraints:
  • There is exactly one valid BST that can be built from the serialized data.
  • You must not reuse tree node objects; build new nodes as needed.
  • Try to keep the serialized string as compact as possible.

Thought Process

To serialize and deserialize a BST, the key insight is to leverage the properties of a BST: for any node, all values in the left subtree are less than the node, and all values in the right subtree are greater.

At first, it might seem natural to use a generic approach for binary trees, such as level-order traversal (BFS) or including null markers for missing children. However, these approaches add unnecessary complexity and extra markers, making the string longer.

Since the input is a BST, we can use a preorder traversal (root, left, right) to serialize the tree. Preorder uniquely defines the BST structure. For deserialization, we can reconstruct the tree efficiently by ensuring that at each step, the node values respect the BST property (using min and max bounds).

This approach is much more efficient and compact than brute-force methods, as we avoid storing nulls and only store the values in traversal order.

Solution Approach

The solution makes use of the BST property to serialize and deserialize efficiently:

  1. Serialization (Preorder Traversal):
    • Traverse the tree in preorder (node, left, right).
    • Append each node's value to a result list as a string.
    • Join the list with spaces to form the final serialized string.
    • No need to record nulls, since the BST property allows us to infer structure.
  2. Deserialization (Rebuilding with Bounds):
    • Split the string into a list of values (converted to integers).
    • Use a helper function to build the tree recursively.
    • Pass down min and max bounds for valid BST node values.
    • At each step, if the next value fits the BST constraint, pop it and create a node.
    • Recursively build the left and right subtrees with updated bounds.

This approach ensures that the tree is reconstructed exactly as it was, and the serialization is compact and efficient.

Example Walkthrough

Let's consider a sample BST:

        5
       / \
      3   7
     / \   \
    2   4   8
    

  1. Serialization:
    • Preorder traversal yields: 5, 3, 2, 4, 7, 8
    • The serialized string is: "5 3 2 4 7 8"
  2. Deserialization:
    • Split the string into values: [5, 3, 2, 4, 7, 8]
    • Start with bounds (-∞, ∞). The first value is 5, which fits, so it's the root.
    • For the left subtree of 5, bounds are (-∞, 5). Next value is 3, which fits.
    • Left of 3, bounds (-∞, 3). Next is 2, which fits.
    • No more values fit left of 2; move to right of 3 (bounds 3, 5). Next is 4, which fits.
    • No more values fit right of 4; back to right subtree of 5 (bounds 5, ∞). Next is 7, which fits.
    • Left of 7 (5, 7): No value fits. Right of 7 (7, ∞): 8 fits.
    • All nodes are placed, and the tree is reconstructed exactly.

Time and Space Complexity

  • Brute-force approach: If we used a generic binary tree serialization (with null markers), both time and space would be O(N), but the serialized string would be much longer due to nulls.
  • Optimized BST approach:
    • Time Complexity: O(N) for both serialization and deserialization, where N is the number of nodes. Each node is visited once.
    • Space Complexity: O(N) for the output string and for the call stack during recursion.

The optimized approach is both faster and more space-efficient compared to generic methods.

Summary

By leveraging the properties of a BST, we can serialize the tree using preorder traversal and reconstruct it using value bounds during deserialization. This method is elegant because it avoids unnecessary markers, is compact, and runs efficiently in linear time. The key insight is that preorder traversal uniquely determines the BST, allowing us to reconstruct the tree with only the node values and their order.