AlgoMaster Logo

Zigzag Conversion

Last Updated: April 6, 2026

medium
4 min read

Understanding the Problem

Before jumping into code, let's make sure we understand the zigzag pattern. Characters are placed 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 observation is the key to all the efficient solutions.

One edge case to watch: when numRows = 1 or numRows >= len(s), the zigzag pattern is just the original string. No rearrangement happens.

Key Constraints:

  • 1 <= s.length <= 1000 → With at most 1000 characters, even an O(n^2) solution runs in well under a millisecond. But we should still aim for O(n) to build good habits.
  • 1 <= numRows <= 1000 → numRows can be larger than the string length. In that case, every character sits in its own row and the output equals the input.

Approach 1: Simulation with 2D Grid

Intuition

The most natural approach is to literally simulate the zigzag. Create a 2D grid, place each character in the right cell by moving down and then diagonally up, and then read the grid row by row.

This mirrors how you'd draw the pattern on paper. It's easy to understand, 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.

Code

The bottleneck here is the 2D grid itself. We allocate numRows * n cells, but only n of them ever hold a character. We don't actually need to know the column of each character, just which row it belongs to. What if we simply tracked each character's row and appended it to that row's list?

Approach 2: Row-by-Row Collection

Intuition

Instead of building a full 2D grid, we can use one list (or StringBuilder) per row. As we iterate through the string, we track which row the current character belongs to and append it there. The row bounces between 0 and numRows-1, just like in the simulation, but we skip the wasted columns.

This is the approach most interviewers expect. It's clean, efficient, and easy to explain.

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 direction = 1 (going down).
  4. For each character in the string:
    • Append it to the list for currentRow.
    • If currentRow is 0, set direction to +1 (down). If currentRow is numRows-1, set direction to -1 (up).
    • Update currentRow += direction.
  5. Concatenate all row lists to form the result.

Code

Approach 2 is already O(n) time, which is optimal since we must read every character at least once. But it uses O(n) extra space for the row lists. The row-by-row collection figures out each character's row by simulating the bounce, but there's a pattern: for any given row, the indices of characters that belong to it follow a predictable arithmetic sequence based on the cycle length. What if we computed those indices directly?

Approach 3: Direct Index Calculation

Intuition

The zigzag pattern repeats every cycle = 2 * numRows - 2 characters. Within each cycle, each row gets characters at predictable positions:

  • Row 0 (top): one character per cycle, at index cycle * j
  • Row numRows-1 (bottom): one character per cycle, at index cycle * j + (numRows - 1)
  • Middle rows (row i): two characters per cycle, at indices cycle * j + i and cycle * j + (cycle - i)

This means we can iterate row by row and directly pick characters from the original string by computing their indices. No simulation, no extra lists.

Algorithm

  1. If numRows is 1 or greater than or equal to the string length, return the string as-is.
  2. Compute cycle = 2 * numRows - 2.
  3. For each row i from 0 to numRows-1:
    • For each cycle starting index j = 0, cycle, 2*cycle, ...:
      • Append s[j + i] if it's within bounds (this is the "down" character).
      • If i is not the first or last row, also append s[j + cycle - i] if within bounds (this is the "up" character).
  4. Return the accumulated result.

Code