Problem overview
The method zPattern receives n, a power of 2, and returns an n by n grid holding the numbers 1 to n * n. The numbers follow a "Z" order: split the square into four quadrants, visit them as upper-left, upper-right, lower-left, lower-right, and apply the same rule inside every quadrant until a quadrant is a single cell.
For n = 2 each quadrant is one cell, so the result is [[1, 2], [3, 4]]. For n = 4 the upper-left quadrant takes 1 to 4, the upper-right takes 5 to 8, the lower-left takes 9 to 12 and the lower-right takes 13 to 16, and inside each quadrant the same Z repeats:
1 2 5 6
3 4 7 8
9 10 13 14
11 12 15 16
Approach: let a helper fill one square
Computing the number for a given cell directly means thinking about bits of the row and column. The recursive view is simpler. Suppose a helper can fill any square of the grid, given its top-left corner, its side length and the first number to write. A square of side 1 is just one cell: write the first number there. A larger square is four squares of half the side, and the numbers are consecutive in Z order, so each quadrant starts where the previous one ended.
Each quadrant of side newLen holds newLen * newLen numbers. That gives the four starting values:
- upper-left:
firstVal; - upper-right:
firstVal + newLen * newLen; - lower-left:
firstVal + 2 * newLen * newLen; - lower-right:
firstVal + 3 * newLen * newLen.
Because n is a power of 2, halving always gives a whole number, and after log2(n) halvings every square has side 1. That is where the recursion stops.
Step by step
- Build the empty grid.
new Array(n)makes a row ofnempty slots, and a loop puts a separate such array in each of thenpositions ofgrid. Each row must be its own array. - Call
this.fillonce for the whole grid: corner (0, 0), siden, first value 1. Methods of a class are called throughthis. - Return
grid. The helper wrote into it in place, so there is nothing to copy. - Declare
fillwith parametersgrid,row,col,lenandfirstVal, returningvoid. - Handle the base case. When
len === 1, storefirstValatgrid[row][col]. - Add the
elsebranch for every larger square. - Compute the half side,
newLen, withMath.floor(len / 2). In TypeScript/is not integer division, so this is how you get a whole number back. - Fill the upper-left quadrant at the same corner, with the same first value.
- Fill the upper-right quadrant. Only the column moves, by
newLen, and the first value skips one quadrant. - Fill the lower-left quadrant. Only the row moves, and the first value skips two quadrants.
- Fill the lower-right quadrant. Both row and column move, and the first value skips three quadrants.
The solution in TypeScript
class Solution {
fill(grid: number[][], row: number, col: number, len: number, firstVal: number): void {
if (len === 1) {
grid[row][col] = firstVal;
} else {
let newLen: number = Math.floor(len / 2);
this.fill(grid, row, col, newLen, firstVal);
this.fill(grid, row, col + newLen, newLen, firstVal + newLen * newLen);
this.fill(grid, row + newLen, col, newLen, firstVal + 2 * newLen * newLen);
this.fill(grid, row + newLen, col + newLen, newLen, firstVal + 3 * newLen * newLen);
}
}
zPattern(n: number): number[][] {
let grid: number[][] = new Array(n);
for (let i = 0; i < n; i++) {
grid[i] = new Array(n);
}
this.fill(grid, 0, 0, n, 1);
return grid;
}
}
Some TypeScript details worth knowing:
- Empty slots, not zeros.
new Array(3)has length 3 but no elements at all; Node prints it as[ <3 empty items> ]. That is fine here, becausefillwrites every cell before the grid is returned. - Don't build the rows with
fill.new Array(2).fill(new Array(2))puts the same inner array in both positions. After writingsq[0][0] = 1, both rows show the 1. The loop inzPatterncreates a new array per row, which avoids that. - The type annotation is a promise to the compiler only.
number[][]checks the code while compiling, and it is erased in the JavaScript that runs. It does not make the empty slots hold numbers. - Passing
gridshares it. Arrays are objects, so every recursive call receives a reference to the same grid. That is whyfillcan returnvoid. - Order of arguments. Five parameters of type
numberare easy to swap, and the compiler cannot notice. Keeprowbeforecol.
Complexity
Let n be the side of the grid.
Time: O(n²). Every call with len > 1 makes four calls with half the side, and the calls with len === 1 each write one cell. The grid has n² cells, so there are n² base cases, and the calls above them add up to about a third as many more. The work is proportional to the number of cells.
Space: O(n²) for the grid that is returned. The recursion itself is only log2(n) levels deep, so the call stack adds O(log n).
Edge cases and common mistakes
n = 1. The base case runs at once and the result is[[1]].- Forgetting to move the first value. If all four calls pass
firstVal, the quadrants overwrite each other with the same numbers. - Using
newLeninstead ofnewLen * newLen. A quadrant of sidenewLenholdsnewLen * newLennumbers, so the offsets must be multiples of that. - Moving the wrong coordinate. The upper-right quadrant is on the same row as the upper-left one, so only
colchanges. The lower-left changes onlyrow. - Not a power of 2. The lesson promises a power of 2. For another size, such as 3, the half side would not be whole, and the recursion would not reach side 1 cleanly.
- Sharing one row array across all rows, as shown above.
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 = 8has first row1 2 5 6 17 18 21 22and last row43 44 47 48 59 60 63 64.