Longest ZigZag Path in a Binary Tree

MEDIUM

Description

You are given the root of a binary tree.

A ZigZag path for a binary tree is defined as follow:

  • Choose any node in the binary tree and a direction (right or left).
  • If the current direction is right, move to the right child of the current node; otherwise, move to the left child.
  • Change the direction from right to left or from left to right.
  • Repeat the second and third steps until you can't move in the tree.

Zigzag length is defined as the number of nodes visited - 1. (A single node has a length of 0).

Return the longest ZigZag path contained in that tree.

 

Example 1:

Input: root = [1,null,1,1,1,null,null,1,1,null,1,null,null,null,1]
Output: 3
Explanation: Longest ZigZag path in blue nodes (right -> left -> right).

Example 2:

Input: root = [1,1,1,null,1,null,null,1,1,null,1]
Output: 4
Explanation: Longest ZigZag path in blue nodes (left -> right -> left -> right).

Example 3:

Input: root = [1]
Output: 0

 

Constraints:

  • The number of nodes in the tree is in the range [1, 5 * 104].
  • 1 <= Node.val <= 100

Approaches

Checkout 2 different approaches to solve Longest ZigZag Path in a Binary Tree. Click on different approaches to view the approach and algorithm in detail.

Optimal Single-Pass DFS

A more efficient approach is to use a single Depth First Search (DFS) pass. By employing a post-order traversal, we can compute the necessary information for a node based on the results from its children. For each node, we calculate the length of the longest ZigZag path starting at that node and going left, and the length of the path starting at that node and going right. This information is then 'bubbled up' to the parent node, which uses it to compute the lengths of its own starting paths. A global variable is used to keep track of the maximum length found at any point in the traversal.

Algorithm

  • Use a single Depth First Search (DFS) traversal, specifically a post-order traversal.
  • Define a recursive helper function, dfs(node), that returns information about paths starting at node.
  • The dfs(node) function will return an array of two integers: [leftPath, rightPath].
    • leftPath: The length of the longest ZigZag path that starts at node and first goes to the left.
    • rightPath: The length of the longest ZigZag path that starts at node and first goes to the right.
  • The base case for the recursion is when node is null. In this case, return [-1, -1] to indicate that no path exists, which simplifies the calculation for the parent.
  • For a non-null node, recursively call dfs on its children: leftResult = dfs(node.left) and rightResult = dfs(node.right).
  • To calculate leftPath for the current node: a path going left from node must then continue from node.left by going right. So, leftPath = 1 + leftResult[1].
  • Similarly, rightPath = 1 + rightResult[0].
  • Maintain a global maxLength variable. After computing leftPath and rightPath for the current node, update maxLength = max(maxLength, leftPath, rightPath).
  • The dfs function returns the [leftPath, rightPath] array for its parent to use.

This optimal solution avoids re-computation by solving the problem in a single pass using a post-order DFS traversal. The core idea is to use the return value of the recursive calls to pass information up the tree.

Our recursive function, dfs(node), will return an array of two integers for each node. The first element will be the length of the longest ZigZag path starting at node with a left move, and the second will be the length of the path starting with a right move.

When dfs(node) is called:

  1. It first calls itself on its left and right children.
  2. It receives the results from its children. For example, leftResult from dfs(node.left) contains the lengths of paths starting at node.left.
  3. To find the length of a ZigZag path starting at node and going left, we take one step to node.left (length 1) and then continue the ZigZag. The next move must be to the right. The length of the longest ZigZag path starting from node.left and going right is given by leftResult[1]. So, the total length is 1 + leftResult[1].
  4. Similarly, the length of a path starting at node and going right is 1 + rightResult[0].
  5. We update a global maxLength with the maximum of these two calculated lengths. This ensures we track the longest path found anywhere in the tree.
  6. Finally, the function returns the newly computed lengths for node to its parent.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int maxLength = 0;

    public int longestZigZag(TreeNode root) {
        dfs(root);
        return maxLength;
    }

    /**
     * Performs a post-order traversal to calculate ZigZag path lengths.
     * @return an array of two elements:
     *         result[0] = longest ZigZag path starting from 'node' and going LEFT.
     *         result[1] = longest ZigZag path starting from 'node' and going RIGHT.
     */
    private int[] dfs(TreeNode node) {
        if (node == null) {
            // Base case: no path exists from a null node.
            // -1 helps in calculation: 1 + (-1) = 0 for a path of a single node.
            return new int[]{-1, -1};
        }

        // Recursively get results from children
        int[] leftResult = dfs(node.left);
        int[] rightResult = dfs(node.right);

        // Calculate path starting at current node and going left.
        // This path continues from the left child by going right.
        int pathStartingLeft = 1 + leftResult[1];

        // Calculate path starting at current node and going right.
        // This path continues from the right child by going left.
        int pathStartingRight = 1 + rightResult[0];

        // Update the global maximum length found so far.
        maxLength = Math.max(maxLength, Math.max(pathStartingLeft, pathStartingRight));

        // Return the lengths of paths starting at the current node for the parent.
        return new int[]{pathStartingLeft, pathStartingRight};
    }
}

Complexity Analysis

Time Complexity: O(N). The algorithm traverses each node in the tree exactly once.Space Complexity: O(H) or O(N) in the worst case. The space is used by the recursion stack. For a balanced tree, this is O(log N). For a skewed tree, it becomes O(N).

Pros and Cons

Pros:
  • Optimal time complexity of O(N) as it visits each node only once.
  • Efficient use of space, proportional to the height of the tree.
Cons:
  • The recursive logic, especially the meaning of the returned array, can be slightly less intuitive to grasp initially compared to the brute-force approach.

Code Solutions

Checking out 3 solutions in different languages for Longest ZigZag Path in a Binary Tree. Click on different languages to view the code.

/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { private int ans ; public int longestZigZag ( TreeNode root ) { dfs ( root , 0 , 0 ); return ans ; } private void dfs ( TreeNode root , int l , int r ) { if ( root == null ) { return ; } ans = Math . max ( ans , Math . max ( l , r )); dfs ( root . left , r + 1 , 0 ); dfs ( root . right , 0 , l + 1 ); } }

Video Solution

Watch the video walkthrough for Longest ZigZag Path in a Binary Tree



Algorithms:

Depth-First Search

Patterns:

Dynamic Programming

Data Structures:

TreeBinary Tree

Companies:

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.