Problem Walkthrough

Longest ZigZag Path LeetCode 1372 Guide

DFS tracks both the zigzag length if you arrived from the left and the length if you arrived from the right at every node — the child going in the opposite direction extends the zigzag by one, while the child going in the same direction resets its counter to one because the streak breaks at the current node.

8 min read|

Longest ZigZag Path

LeetCode 1372

Problem Overview

LeetCode 1372 — Longest ZigZag Path in a Binary Tree — gives you the root of a binary tree and asks you to find the length of the longest zigzag path. A zigzag path alternates directions at every step: left then right then left, or right then left then right. The length is measured in edges, not nodes.

A zigzag path can start from any node in the tree — not just the root. This means you cannot simply run a DFS from the root and always try to extend; you must handle restarts from any node. The answer is the maximum number of edges in any such alternating-direction path.

With up to 5×10⁴ nodes, an O(n²) brute force restarting DFS from every node would be too slow. The key insight is that a single DFS can track every possible zigzag path simultaneously by carrying two counters at each node: one for paths arriving from the left, one for paths arriving from the right.

  • Find the longest path that alternates left and right child at every step
  • Path can start from any node — not just the root
  • Length is counted in edges: path through k+1 nodes has length k
  • Zigzag breaks when the same direction is taken twice consecutively
  • Constraints: up to 5×10⁴ nodes, values in [1, 10⁴]

DFS with Direction Tracking

The core idea is to pass two values down to every node: leftLen (the zigzag length if you continue by going left from this node) and rightLen (the zigzag length if you continue by going right). At the root, both start at 0 because no edges have been traversed yet.

When visiting a node's left child, the zigzag extends if we came from the right — so we pass rightLen+1 as the new leftLen and 0 as the new rightLen (starting fresh for the right direction). Symmetrically, when visiting a node's right child, we pass 0 as the new leftLen and leftLen+1 as the new rightLen.

At each node, the current zigzag length from both directions is max(leftLen, rightLen). We update a global maximum with this value at every node visit. This single traversal covers all starting points because whenever a counter resets to 0 at some node, the subsequent increments build up a new zigzag starting from that node.

💡

Two Counters Cover All Starting Points

At each node, track two values: the zigzag length if you came from the left, and the length if you came from the right. The child going in the opposite direction extends the zigzag by one; the child going in the same direction resets to 0 (its counter for that same direction) because the streak breaks. This single-pass approach avoids restarting DFS from every node.

Algorithm

The algorithm uses a recursive DFS with a global maximum variable. The function signature is dfs(node, leftLen, rightLen) where leftLen is how long the zigzag is if we arrived going left to reach this node, and rightLen is how long if we arrived going right.

At each node we update the global max and recurse into both children with correctly swapped and reset counters. The left child receives rightLen+1 as its leftLen (continuing the zigzag from the right) and 0 as its rightLen (reset). The right child receives 0 as its leftLen (reset) and leftLen+1 as its rightLen (continuing the zigzag from the left).

The function initializes with dfs(root, 0, 0). After the traversal completes, the global maximum holds the answer. The algorithm visits every node exactly once, giving O(n) time and O(h) space for the call stack where h is the tree height.

  1. 1Initialize: ans = 0, call dfs(root, 0, 0)
  2. 2dfs(node, leftLen, rightLen): if node is null, return
  3. 3ans = max(ans, leftLen, rightLen)
  4. 4dfs(node.left, rightLen + 1, 0) — going left: left child extends from right streak
  5. 5dfs(node.right, 0, leftLen + 1) — going right: right child extends from left streak

Why Two Counters

Each node can be part of a zigzag arriving from the left or a zigzag arriving from the right. These are distinct paths that may have very different lengths. Tracking both simultaneously allows each child to pick up whichever direction continues the zigzag and correctly reset the counter for the direction that breaks the zigzag.

A single counter approach would lose information: if you only track length without direction, when you visit the left child you do not know whether the current streak was built going left (same direction — streak breaks) or going right (opposite direction — streak continues). The two-counter design makes direction explicit without any conditional checks.

The reset to 0 passed to the same-direction child is the key: it means that child starts building a new zigzag from scratch, but going the opposite way from there. So every node is simultaneously the potential start of a fresh zigzag in both directions, and the two counters capture exactly this.

ℹ️

One Pass Covers Every Starting Point

The two-counter approach avoids restarting DFS from every node: a single traversal with leftLen and rightLen covers all possible zigzag starting points. When a counter resets to 0 at a node, the child with that 0 begins building a new zigzag. Increments from that point forward represent paths starting at that child, so no separate pass is needed.

Walk-Through Example

Consider the tree [1, null, 1, 1, 1, null, null, 1, 1, null, 1]. The root has only a right child. That right child has both left and right children. The target zigzag goes root→right→left→right→left = 4 edges.

At the root: dfs(root, 0, 0) — ans = 0. We go right: dfs(rightChild, 0, 1). At rightChild: ans = max(0, 0, 1) = 1. We go left: dfs(leftGrandchild, 2, 0); we go right: dfs(rightGrandchild, 0, 1). At leftGrandchild: ans = max(1, 2, 0) = 2. Continue going right from it: dfs(its right child, 0, 3). At that node: ans = max(2, 0, 3) = 3. Go left from it: dfs(deepest left, 4, 0). At deepest left: ans = max(3, 4, 0) = 4.

The final answer is 4 edges. Each step along the right→left→right→left chain incremented the correct counter by 1 and reset the other to 0, cleanly tracking the alternating path without any backtracking or restart.

  1. 1Tree: [1,null,1,1,1,null,null,1,1,null,1]
  2. 2Call dfs(root, 0, 0): ans=0, recurse right with (0, 1)
  3. 3dfs(rightChild, 0, 1): ans=1, recurse left with (2, 0), right with (0, 1)
  4. 4dfs(leftGrandchild, 2, 0): ans=2, recurse right with (0, 3)
  5. 5dfs(deeper right, 0, 3): ans=3, recurse left with (4, 0)
  6. 6dfs(deepest left, 4, 0): ans=4 — maximum zigzag found
  7. 7Final answer: 4 edges (right→left→right→left)

Code Walkthrough — Python and Java

In Python, use a nonlocal ans variable inside a nested dfs function. dfs(node, left, right): if not node, return; nonlocal ans; ans = max(ans, left, right); dfs(node.left, right+1, 0); dfs(node.right, 0, left+1). Call dfs(root, 0, 0) and return ans. The function is about 8 lines and runs in O(n) time with O(h) call-stack space.

In Java, store ans as an instance variable or a one-element int array. The recursive method void dfs(TreeNode node, int left, int right) updates ans = Math.max(ans, Math.max(left, right)) then calls dfs(node.left, right+1, 0) and dfs(node.right, 0, left+1). The longestZigZag method initializes ans and calls dfs(root, 0, 0). Both implementations are essentially identical in structure.

Time complexity is O(n) — every node is visited exactly once. Space complexity is O(h) for the recursion stack, where h is the height of the tree. For a balanced tree h = O(log n); for a skewed tree (all nodes going one direction) h = O(n). The reset-to-0 passed for the same-direction child is the single most important line in the solution.

⚠️

Zigzag Length Counts Edges Not Nodes

Zigzag length counts EDGES not nodes: a path through 5 nodes has 4 edges. Initialize both counters at 0 for the root call because 0 edges have been traversed when you first visit the root. The counter value at any node already represents the number of edges from the start of that zigzag to the current node, so no off-by-one adjustment is needed in the final answer.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now