Problem overview
Given a number n that is a power of 2, zPattern returns an n by n grid filled with the numbers 1 to n² in a "Z" order. The order is defined recursively. Split the square into four equal quadrants and fill them in this order: upper-left, upper-right, lower-left, lower-right. Inside each quadrant, repeat the same rule, until you reach single cells.
For n = 2 every quadrant is one cell, so the grid is just the Z itself:
1 2
3 4
For n = 4 each quadrant is a 2 by 2 square that is filled like the grid above. 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:
1 2 5 6
3 4 7 8
9 10 13 14
11 12 15 16
Notice that the numbers inside each quadrant are consecutive. That is the fact the whole solution rests on.
Approach
A square of side len holds len * len numbers. If we know the top-left corner of the square and the first number to write in it, we can describe the work for the whole square with four smaller jobs of exactly the same kind:
- the upper-left quadrant starts at the same corner and gets the first number;
- the upper-right quadrant starts half a side to the right and gets the first number plus the size of one quadrant;
- the lower-left quadrant starts half a side down and gets the first number plus two quadrant sizes;
- the lower-right quadrant moves down and right and gets the first number plus three quadrant sizes.
When the side is 1, the square is a single cell and we write the first number there. This is divide and conquer: the problem is split into four independent pieces of half the side, and no step has to combine results afterwards, because every piece writes straight into the shared grid.
Because n is a power of 2, halving always gives a whole number, all the way down to 1.
Step by step
- Create the grid. In
zPattern,Array(n) { IntArray(n) }buildsnrows. The lambda runs once per row, so each row is its ownIntArrayofnzeros. - Delegate to a helper. The helper
fill(grid, row, col, len, firstVal)fills one square. The first call covers the entire grid: corner(0, 0), siden, first value 1. - Return the grid. The helper changed the array in place, so there is nothing to collect.
- Declare
fill. It takes the grid, the corner asrowandcol, the sidelenandfirstVal. It has no return type, which means it returnsUnit: its job is the side effect on the grid. - Base case. If
len == 1, writefirstValintogrid[row][col]. This stops the recursion. - Recursive case. Compute
newLen = len / 2, the side of each quadrant. - Four calls, in Z order. The row and column offsets are 0 or
newLen, and the first values grow bynewLen * newLenfor every quadrant already filled.
The solution in Kotlin
class Solution {
fun fill(grid: Array<IntArray>, row: Int, col: Int, len: Int, firstVal: Int) {
if (len == 1) {
grid[row][col] = firstVal
} else {
val newLen = len / 2
fill(grid, row, col, newLen, firstVal)
fill(grid, row, col + newLen, newLen, firstVal + newLen * newLen)
fill(grid, row + newLen, col, newLen, firstVal + 2 * newLen * newLen)
fill(grid, row + newLen, col + newLen, newLen, firstVal + 3 * newLen * newLen)
}
}
fun zPattern(n: Int): Array<IntArray> {
val grid = Array(n) { IntArray(n) }
fill(grid, 0, 0, n, 1)
return grid
}
}
A few Kotlin details:
- Arrays are references.
fillreceives the sameArray<IntArray>object thatzPatterncreated, so writinggrid[row][col]inside the helper is visible to the caller. That is whyfillneeds no return value. Array(n) { IntArray(n) }is safe. The lambda creates a new row for each index. Reusing a singleIntArrayfor every row would make all the rows the same object.val newLeninsideelse. The value never changes after it is computed, sovalis the right choice.len / 2is integer division, exact here becauselenis even whenever it is bigger than 1.- Printing the result.
println(grid)shows only an object reference. To see the numbers, print each row, for examplefor (r in grid) println(r.joinToString(" ")), or usegrid.contentDeepToString().
Complexity
Time. Every call either writes one cell or makes four calls on squares with a quarter of the cells. The recursion ends in exactly n * n single-cell calls, and the calls above them add up to about a third more, so the total work is O(n²). The output has n² numbers, so no solution can do better.
Space. The grid itself takes O(n²). On top of it the recursion goes only log₂(n) levels deep, because the side halves at every level, so the call stack adds O(log n).
Edge cases and common mistakes
- n = 1. The first call already hits the base case, and the result is a grid holding just
1. - Values that are not powers of 2. The lesson guarantees a power of 2. With
n = 3,len / 2is 1 and the four quadrants cover only a 2 by 2 corner, leaving the rest of the grid at 0. Do not rely on the code for such inputs. - Forgetting the offsets. If the upper-right call uses
colinstead ofcol + newLen, the quadrants overwrite each other. - Wrong first values. Each quadrant must start after all the cells of the previous ones, so the multipliers are 0, 1, 2 and 3 times
newLen * newLen. Usinglenthere instead ofnewLenskips numbers. - Swapping row and column. Row is the vertical position. The upper-right quadrant changes the column, and the lower-left changes the row.
Testing
Try these in the editor above:
zPattern(1)gives[[1]].zPattern(2)gives[[1, 2], [3, 4]].zPattern(4)ends with the row[11, 12, 15, 16], and its first row is[1, 2, 5, 6].- For
zPattern(8), the upper-right quadrant starts with 17 and the lower-left with 33.