ZigZag Tree Traversal II in C++ (Time Complexity: O(n))


Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).

Example:

Input: root = [3, 9, 20, null, null, 15, 7]
    3
   / \
  9  20
    /  \
   15   7

Output: 
[
  [3],
  [20,9],
  [15,7]
]

Understanding the Problem

The core challenge of this problem is to traverse the binary tree in a zigzag manner. This means that for each level of the tree, the direction of traversal alternates between left-to-right and right-to-left. This type of traversal is significant in scenarios where hierarchical data needs to be processed in a non-linear order.

Common applications include visualizing hierarchical structures, such as organizational charts or file systems, where a zigzag pattern can provide a more intuitive representation.

Potential pitfalls include handling null nodes and ensuring that the direction of traversal alternates correctly at each level.

Approach

To solve this problem, we can use a breadth-first search (BFS) approach with a queue to traverse the tree level by level. We will also use a flag to keep track of the direction of traversal for each level.

1. **Naive Solution**: A naive solution would involve traversing the tree level by level and then reversing the order of nodes for every alternate level. This approach is not optimal because reversing the order of nodes at each level adds unnecessary complexity.

2. **Optimized Solution**: An optimized solution involves using a deque (double-ended queue) to efficiently add nodes from either end based on the current direction of traversal. This approach ensures that we can achieve the zigzag pattern without the need for reversing the order of nodes.

Algorithm

1. Initialize a deque and add the root node to it.

2. Use a flag to keep track of the direction of traversal (left-to-right or right-to-left).

3. While the deque is not empty, process each level of the tree:

  • Initialize an empty list to store the nodes of the current level.
  • For each node in the current level, add its value to the list and add its children to the deque based on the current direction of traversal.
  • Toggle the direction of traversal for the next level.
  • Add the list of nodes for the current level to the result.

Code Implementation

#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>

using namespace std;

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> result;
        if (!root) return result;

        deque<TreeNode*> dq;
        dq.push_back(root);
        bool leftToRight = true;

        while (!dq.empty()) {
            int size = dq.size();
            vector<int> level;
            for (int i = 0; i < size; ++i) {
                if (leftToRight) {
                    TreeNode* node = dq.front();
                    dq.pop_front();
                    level.push_back(node->val);
                    if (node->left) dq.push_back(node->left);
                    if (node->right) dq.push_back(node->right);
                } else {
                    TreeNode* node = dq.back();
                    dq.pop_back();
                    level.push_back(node->val);
                    if (node->right) dq.push_front(node->right);
                    if (node->left) dq.push_front(node->left);
                }
            }
            result.push_back(level);
            leftToRight = !leftToRight;
        }

        return result;
    }
};

// Helper function to create a binary tree from a vector
TreeNode* createTree(const vector<int>& nodes) {
    if (nodes.empty()) return nullptr;
    TreeNode* root = new TreeNode(nodes[0]);
    deque<TreeNode*> dq;
    dq.push_back(root);
    int i = 1;
    while (i < nodes.size()) {
        TreeNode* node = dq.front();
        dq.pop_front();
        if (nodes[i] != NULL) {
            node->left = new TreeNode(nodes[i]);
            dq.push_back(node->left);
        }
        i++;
        if (i < nodes.size() && nodes[i] != NULL) {
            node->right = new TreeNode(nodes[i]);
            dq.push_back(node->right);
        }
        i++;
    }
    return root;
}

// Main function to test the solution
int main() {
    vector<int> nodes = {3, 9, 20, NULL, NULL, 15, 7};
    TreeNode* root = createTree(nodes);
    Solution sol;
    vector<vector<int>> result = sol.zigzagLevelOrder(root);

    for (const auto& level : result) {
        for (int val : level) {
            cout << val << " ";
        }
        cout << endl;
    }

    return 0;
}

Complexity Analysis

The time complexity of the optimized solution is O(n), where n is the number of nodes in the tree. This is because each node is processed exactly once. The space complexity is also O(n) due to the storage required for the deque and the result vector.

Edge Cases

Potential edge cases include:

  • An empty tree (root is null).
  • A tree with only one node.
  • A tree where all nodes are either to the left or right.

Each of these cases is handled by the algorithm, ensuring that the correct zigzag traversal is produced.

Testing

To test the solution comprehensively, consider the following test cases:

  • Simple tree with a few nodes.
  • Tree with multiple levels and varying numbers of children.
  • Edge cases as mentioned above.

Using a testing framework like Google Test can help automate and validate these test cases.

Thinking and Problem-Solving Tips

When approaching such problems, it's important to:

  • Understand the problem requirements and constraints thoroughly.
  • Break down the problem into smaller, manageable parts.
  • Consider different approaches and their trade-offs.
  • Write clean, readable code with comments to explain your thought process.

Practicing similar problems and studying various algorithms can help improve problem-solving skills.

Conclusion

In this blog post, we discussed the zigzag level order traversal of a binary tree, explored different approaches to solve the problem, and provided a detailed C++ implementation. Understanding and solving such problems is crucial for developing strong problem-solving skills and preparing for technical interviews.

We encourage readers to practice and explore further to deepen their understanding.

Additional Resources