Problem overview
The method zPattern receives an integer n, a power of 2, and returns an n by n grid ([[Int]]) holding the numbers 1 to n * n in "Z" order. The grid is split into four quadrants, visited as upper-left, upper-right, lower-left, lower-right. Each quadrant is filled the same way, recursively, down to single cells.
For n = 2 the quadrants are single cells, so the grid is [[1, 2], [3, 4]]. For n = 4 each quadrant is a 2 by 2 block, and the blocks take the numbers 1 to 4, 5 to 8, 9 to 12 and 13 to 16 in that order:
1 2 5 6
3 4 7 8
9 10 13 14
11 12 15 16
Approach: fill a square from its top-left corner
The key observation is that a square of side length contains length * length cells, and those cells get a block of consecutive numbers. If you know the corner of the square and the first number it receives, the four quadrants follow directly:
- the upper-left quadrant starts at the same corner with the same first number;
- the upper-right quadrant starts
newLengthcolumns to the right, after thenewLength * newLengthnumbers of the first quadrant; - the lower-left quadrant starts
newLengthrows down, after two quadrants; - the lower-right quadrant moves both ways, after three quadrants.
Here newLength is half of length. Since n is a power of 2, halving gives a whole number until the side is 1. A 1 by 1 square holds one number, which stops the recursion.
Take n = 4. The call fill(&grid, 0, 0, 4, 1) has newLength = 2. The upper-right quadrant starts at column 2 with first value 1 + 2 * 2 = 5. The lower-left quadrant starts at row 2 with first value 1 + 2 * 4 = 9, and the lower-right at row 2, column 2 with first value 1 + 3 * 4 = 13. This matches the grid above.
Step by step
- Create the grid in
zPatternasnrows ofnzeros. It is avar, since the helper changes it. - Call the helper
fillonce for the whole grid: corner (0, 0), siden, first value 1. The grid goes in as&gridbecause the parameter isinout. - Return
grid. By then the helper has filled it in place. - Declare
fillwith the grid, arow, acolumn, alengthand afirstValue, none labeled and no return value. - Base case: when
lengthis 1, storefirstValueingrid[row][column]. - Otherwise, split. Compute
newLengthas half oflength. - Recurse on the four quadrants in Z order, using the corners and first values described above.
The solution in Swift
class Solution {
func zPattern(_ n: Int) -> [[Int]] {
var grid = Array(repeating: Array(repeating: 0, count: n), count: n)
fill(&grid, 0, 0, n, 1)
return grid
}
func fill(_ grid: inout [[Int]], _ row: Int, _ column: Int, _ length: Int, _ firstValue: Int) {
if length == 1 {
grid[row][column] = firstValue
} else {
let newLength = length / 2
fill(&grid, row, column, newLength, firstValue)
fill(&grid, row, column + newLength, newLength, firstValue + newLength * newLength)
fill(&grid, row + newLength, column, newLength, firstValue + 2 * newLength * newLength)
fill(&grid, row + newLength, column + newLength, newLength, firstValue + 3 * newLength * newLength)
}
}
}
The Swift details worth a closer look:
inoutand&. Swift function parameters are constants. Marking oneinoutlets the function change the caller's variable, and the caller must show it by writing&grid. Leaving the&out is a compile error:passing value of type '[[Int]]' to an inout parameter requires explicit '&'.- Why
var grid. Aninoutargument has to be a variable. A grid declared withletcan't be passed with&. - Nested repeating arrays.
Array(repeating: Array(repeating: 0, count: n), count: n)builds the inner row once and repeats it. Arrays are value types, so the rows are independent: writing togrid[1][0]doesn't touch any other row. This differs from a grid of reference objects, where the same object would be shared. - Two-step subscripts.
grid[row][column] = firstValuewrites straight into the stored row. No copy of the row is made. - Calling a method declared later. Inside a class,
zPatterncan callfilleven thoughfillis written below it. Order of methods doesn't matter in a Swift type. - Return type. Because
fillhas no->, it returns nothing, and its result is the change it makes to the grid.
Complexity
Time: O(n²). Every call with length > 1 makes four calls with a quarter of the area, and the recursion ends at single cells. There are n * n of those, one per number written, and the calls above them add up to about a third more, so the total work is proportional to the number of cells. Creating the grid also costs O(n²).
Space: O(n²). The returned grid has n * n cells. The recursion adds only O(log n) stack frames, since the side halves at each level, so the grid dominates.
Edge cases and common mistakes
n = 1. The first call is already the base case, and the result is[[1]].- Quadrant order. Swapping the lower-left and upper-right calls produces a different pattern. The first values
firstValue + newLength * newLengthandfirstValue + 2 * newLength * newLengthmust match the visiting order. - Offsets in the wrong direction. The upper-right quadrant moves only the column, and the lower-left quadrant only the row. Moving both for either of them places numbers on the wrong diagonal.
- Using
ninstead oflength. Insidefill, the side that matters is the current square'slength, which shrinks with each level. - Input that isn't a power of 2. The statement guarantees a power of 2. For other values the halves aren't exact, and for
n = 0the length never reaches 1, so the recursion doesn't end and the program crashes with a stack overflow.
Testing
n = 1gives[[1]].n = 2gives[[1, 2], [3, 4]].n = 4gives[[1, 2, 5, 6], [3, 4, 7, 8], [9, 10, 13, 14], [11, 12, 15, 16]].n = 8gives a first row of[1, 2, 5, 6, 17, 18, 21, 22]and a last row of[43, 44, 47, 48, 59, 60, 63, 64].
