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:

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:

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:

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:

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