AlgoMaster Logo

Zigzag Conversion

mediumFrequencyUpdated August 19, 2026

Understanding the Problem

The zigzag pattern places characters top-to-bottom in a column, then diagonally upward until they reach the top row again, then top-to-bottom again, and so on. This creates a "V" shape that repeats.

For numRows = 4, the placement order looks like this:

The characters go down rows 0 through 3, then back up through rows 2 and 1, then down again. That down-and-up cycle has a length of 2 * numRows - 2. For 4 rows, the cycle length is 6. This cycle length drives every efficient solution below.

When numRows = 1 or numRows >= len(s), the zigzag pattern equals the original string and no rearrangement happens. Every solution handles this case with an early return.

Key Constraints:

  • 1 <= numRows <= 1000 → numRows can exceed the string length. In that case, every character occupies its own row and the output equals the input, which is why each approach starts with the numRows >= len(s) early return.
  • 1 <= s.length <= 1000 → The output is a permutation of the input, so the answer always fits in a fixed-width integer index. No overflow concerns arise even with the index arithmetic in Approach 3.

Approach 1: Simulation with 2D Grid

Intuition

Simulate the zigzag directly. Create a 2D grid, place each character in its cell by moving down and then diagonally up, then read the grid row by row.

This mirrors drawing the pattern on paper. It is straightforward to reason about, but it wastes space because most cells in the grid stay empty.

Algorithm

  1. If numRows is 1 or greater than or equal to the string length, return the string as-is.
  2. Create a 2D grid with numRows rows and n columns, initialized to empty.
  3. Walk through the string. Start at row 0, column 0. Move downward. When you hit the bottom row, switch to moving diagonally up-right. When you hit the top row, switch back to moving straight down.
  4. After placing all characters, read the grid row by row, skipping empty cells, to build the result.

Visualization and Code

Loading animation...

The bottleneck is the 2D grid. We allocate numRows * n cells, but only n of them ever hold a character. The column of each character is irrelevant to the final answer, since we read row by row. The next approach drops the grid and tracks only each character's row, appending it directly to a per-row list.

Approach 2: Row-by-Row Collection

Intuition

Instead of a full 2D grid, use one list (or StringBuilder) per row. While iterating through the string, track which row the current character belongs to and append it there. The row bounces between 0 and numRows-1 exactly as in the simulation, but the wasted columns disappear.

The result is the same reading order as Approach 1, with O(n) space instead of O(n * numRows).

Algorithm

  1. If numRows is 1 or greater than or equal to the string length, return the string as-is.
  2. Create an array of numRows empty strings (or StringBuilders).
  3. Initialize currentRow = 0 and goingDown = false.
  4. For each character in the string:
    • Append it to the list for currentRow.
    • If currentRow sits on either edge (row 0 or row numRows-1), flip goingDown.
    • Step currentRow by +1 when goingDown is true, by -1 otherwise.
  5. Concatenate all row lists to form the result.

Flipping only at the two edges is what keeps the walk inside the array. goingDown starts false, so the first character, which lands on row 0, flips it to true and the walk starts downward. Every later arrival at row 0 or row numRows-1 flips it again, turning the walk around before currentRow can step out of range. That is why no bounds check is needed when reading rows[currentRow].

Visualization and Code

Loading animation...

Approach 2 runs in O(n) time, which is optimal since every character must be read at least once. It still uses O(n) extra space for the row lists. For any given row, the indices of the characters that belong to it follow an arithmetic sequence determined by the cycle length. The next approach computes those indices directly and writes straight to the output, removing the per-row lists.

Approach 3: Direct Index Calculation

Intuition

The zigzag pattern repeats every cycleLen = 2 * numRows - 2 characters. Rather than tracking where each cycle starts, let j walk a single row directly: it begins at index row and advances by cycleLen, which lands on exactly that row's downward-leg characters.

  • Row 0 (top): one character per cycle, at index j
  • Row numRows-1 (bottom): one character per cycle, at index j
  • Middle row row: two characters per cycle, at j (downward leg) and j + cycleLen - 2 * row (upward leg)

The upward-leg offset comes from the geometry of the V. The character at j sits row steps below the top on the way down. Its partner on the way back up sits the same row steps below the top, which is cycleLen - 2 * row positions further along the string. Top and bottom rows lie on the fold of the V, so they appear only once per cycle and have no separate upward character. This lets us read characters directly from the original string, with no simulation and no extra lists.

Algorithm

  1. If numRows is 1 there is no zigzag to build, so return the string as-is.
  2. Compute cycleLen = 2 * numRows - 2.
  3. For each row row from 0 to numRows-1:
    • Step j from row through the string in increments of cycleLen:
      • Append s[j] (this is the "down" character).
      • If row is not the first or last row, compute second = j + cycleLen - 2 * row and append s[second] when it is within bounds (this is the "up" character).
  4. Return the accumulated result.

Visualization and Code

Loading animation...