LeetCode ZigZag helper function code
As you pointed out, you don't actually need to lay the characters out into a grid.  It would be more efficient to use arithmetic to figure out the indexes of the characters in each row.

For example, if num_rows is 3, then each down-up cycle will consist of 4 characters.  (zigzag_size = 2 * num_rows - 2).  Then we also know that the top of each zigzag will consist of indexes 0, 4, 8, 12, … of the string (for z in range(0, len(s), zigzag_size)).

The middle row will consist of indexes z+1 and z+3.  The bottom row will consist of indexes z+2.  The zigzag_indexes() generator generalizes those calculations for any numRows.

import math

def zigzag_indexes(num_rows):
    yield (0, math.inf)                             # Top of each zigzag
    yield from zip(
        range(1, num_rows),                         # Downward
        range(2 * num_rows - 3, num_rows - 1, -1)   # Upward
    )
    yield (num_rows - 1, math.inf)                  # Bottom of each zigzag

def convert(s, num_rows):
    if num_rows == 1:
        return s
    zigzag_size = 2 * num_rows - 2
    return ''.join(
        (s[z+a] if z+a &lt; len(s) else '') + (s[z+b] if z+b &lt; len(s) else '')
        for a, b in zigzag_indexes(num_rows)
        for z in range(0, len(s), zigzag_size)
    )

print(convert('PAYPALISHIRING', 3))