LeetCode #1372 Medium

Longest ZigZag Path in a Binary Tree

Longest ZigZag Path in a Binary Tree: a zigzag path alternates direction at every step. Return the length of the longest one, measured in edges.

Constraints
  • The number of nodes is in the range [1, 5 * 10⁴].
  • 1 <= Node.val <= 100
dynamic-programmingtreedepth-first-searchbinary-tree
Open on LeetCode ↗
02

Intuition

Longest zigzag path in a binary tree finds the longest path that alternates direction at every step — left, right, left, and so on. The path length is counted in edges, so a single node has length 0. What distinguishes this from ordinary tree recursion is that the answer depends on how a node was reached. A node arrived at from the left can only extend by going right; the same node reached from the right extends by going left. So the recursion carries direction: - At each node, track the current direction and the length of the zigzag arriving there — two separate values, one per direction. Moving in the opposite direction to the one just travelled extends the path by one. Moving in the same direction restarts it at length 1, since the zigzag is broken but a new path begins at that step. That restart is the detail most often missed. Returning 0 on a direction change loses the fresh path starting at the current node, and the answer comes out short on trees where the longest zigzag begins mid-branch. As with Diameter of Binary Tree, the maximum is recorded on the side rather than returned. The recursion must return per-direction lengths so the parent can extend them, while the best answer overall lives in a variable updated at each node. The answer counts edges, not nodes, so a two-node path has length 1. Off-by-one errors here are common and only show on small trees. Every node is visited once with constant work, giving O(n) time and O(h) stack space.

How to spot this pattern

When a path property depends on how you arrived, push that arrival state down as a DFS parameter rather than trying to reconstruct it. Direction-plus-length is the reusable shape — the same technique handles Longest Univalue Path and Binary Tree Maximum Path Sum, which carry different state for the same reason.

03

Approach

Try it first

Before reading on: standing at a node, what two pieces of information do you need in order to know whether stepping left extends the zigzag or restarts it? Aim for O(n).

1

Carry the direction into the recursion

A node's contribution depends on how it was reached — arriving from the left means extending rightwards. The direction must travel with the recursion.

2

Track two lengths per node

Keep the zigzag length arriving from the left and from the right separately. One value cannot describe both continuations.

3

Extend on a direction change

Moving opposite to the previous step lengthens the path by one. This is the only way a zigzag grows.

4

Restart at one on a repeat

Moving the same direction breaks the zigzag but starts a new path of length 1 at that step. Returning 0 instead loses paths beginning mid-branch.

5

Record the maximum separately

Like Diameter of Binary Tree, the answer is held outside the recursion while the return value serves the parent. Mixing the two loses one or the other.

6

Count edges, not nodes

A single node has length 0 and a two-node path has length 1. Off-by-one errors here surface only on small trees.

7

Cost of the traversal

Each node is visited once with O(1) work, giving O(n) time and O(h) space for the recursion stack.

04

Solution & live demo

▶1class Solution:
▶2 def longestZigZag(self, root):
▶3 self.best = 0
▶4 
▶5 def dfs(node, is_left, length):
▶6 if not node:
▶7 return
▶8 self.best = max(self.best, length)
▶9 if is_left:
▶10 dfs(node.left, True, 1)
▶11 dfs(node.right, False, length + 1)
▶12 else:
▶13 dfs(node.left, True, length + 1)
▶14 dfs(node.right, False, 1)
▶15 
▶16 dfs(root, True, 0)
▶17 dfs(root, False, 0)
▶18 return self.best
05

Common pitfalls

Counting nodes instead of edges

✗ Wrong
dfs(root, True, 1)
✓ Right
dfs(root, True, 0)

Length is measured in edges, so a lone node scores 0. Seeding with 1 inflates every answer by exactly one, which passes no test case.

Not restarting on a same-direction move

✗ Wrong
dfs(node.left, True, length + 1)  # when arriving from the left
✓ Right
dfs(node.left, True, 1)

Moving the same way twice breaks the alternation, so the run ends and a new one begins at that node. Extending it instead reports straight chains as valid zigzags.

Only searching from the root

✗ Wrong
dfs(root, True, 0)
return self.best
✓ Right
dfs(root, True, 0)
dfs(root, False, 0)

The first call fixes an arrival direction at the root, exploring only half the possibilities. Both initial directions must be seeded, or paths beginning with the other move are never considered.

06

Edge cases

Single node

No edges exist, so the answer is 0.

Perfectly zigzagging tree

Every step alternates and the length equals the depth minus one.

Straight left chain

The direction never alternates, so each step restarts at 1 and the answer is 1.

Longest path starts mid-tree

The restart case seeds a run of 1 at every node, so interior starts are covered.

Skewed tree

Recursion depth reaches O(n); on very deep trees an iterative stack avoids overflow.

07

Complexity

Time
O(n)
Space
O(h)
Each node is entered at most twice, once per arrival direction. Space is the recursion stack, h being the tree height.