Last Updated: April 6, 2026
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.
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.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.
numRows is 1 or greater than or equal to the string length, return the string as-is.numRows rows and n columns, initialized to empty.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?
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.
numRows is 1 or greater than or equal to the string length, return the string as-is.numRows empty strings (or StringBuilders).currentRow = 0 and direction = 1 (going down).currentRow.currentRow is 0, set direction to +1 (down). If currentRow is numRows-1, set direction to -1 (up).currentRow += direction.The zigzag pattern is really just a bouncing row index. The character at position i in the string goes to whichever row the bounce is on at step i. By collecting characters into row-specific lists and concatenating them, we naturally produce the zigzag reading order.
The direction variable acts as a simple state machine with two states: going down (+1) and going up (-1). The transitions happen at the boundary rows.
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?
The zigzag pattern repeats every cycle = 2 * numRows - 2 characters. Within each cycle, each row gets characters at predictable positions:
cycle * jcycle * j + (numRows - 1)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.
numRows is 1 or greater than or equal to the string length, return the string as-is.cycle = 2 * numRows - 2.i from 0 to numRows-1:j = 0, cycle, 2*cycle, ...:s[j + i] if it's within bounds (this is the "down" character).i is not the first or last row, also append s[j + cycle - i] if within bounds (this is the "up" character).numRows times, and the inner loop processes characters assigned to that row. Together, they cover all n characters.