Problem overview
The method zPattern receives an integer n, a power of 2, and returns an n by n grid, an Array[Array[Int]], filled with 1, 2, ..., n² in "Z order". The grid is split into four quadrants, which are visited as upper-left, upper-right, lower-left, lower-right. Each quadrant is then 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. The upper-left block holds 1 to 4, the upper-right 5 to 8, the lower-left 9 to 12 and the lower-right 13 to 16:
1 2 5 6
3 4 7 8
9 10 13 14
11 12 15 16
Approach: every quadrant is a smaller copy of the problem
Inside the 4 by 4 grid, the upper-left quadrant is exactly the 2 by 2 answer, and so are the other three, except that their numbers start at a different value. So a recursive function only has to know where a square sits and which number it starts with:
- its top-left corner,
rowandcol; - its side length,
len; - its first number,
firstVal.
A square of side len holds len * len numbers. Each of its four quadrants has side newLen = len / 2 and holds newLen * newLen numbers, so the quadrants start at firstVal, firstVal + newLen * newLen, firstVal + 2 * newLen * newLen and firstVal + 3 * newLen * newLen. A 1 by 1 square is the base case: it holds just firstVal.
Because the power-of-2 side halves exactly each time, the recursion reaches side 1 on every branch, never a side that doesn't divide evenly.
Step by step
- Create the grid with
Array.fill(n)(Array.ofDim[Int](n)):nrows, each anArray[Int]ofnzeros. The second argument offillis evaluated once per row, so every row is a separate array. - Call the helper on the whole grid: corner (0, 0), side
n, first value 1. - Return the grid. The helper wrote into it in place.
- Declare
fillwith the grid,row,col,lenandfirstVal, returningUnit. It returns nothing because it modifies the caller's arrays. - Handle the 1 by 1 case. If
len == 1, setgrid(row)(col) = firstVal. - Otherwise split. In the
elsebranch computenewLen, half the side. - Upper-left: same corner, first value
firstVal. - Upper-right: the column moves right by
newLen, and the numbers start after the upper-left block. - Lower-left: the row moves down by
newLen, with two blocks of numbers before it. - Lower-right: both move by
newLen, with three blocks before it.
The solution in Scala
class Solution {
def fill(grid: Array[Array[Int]], row: Int, col: Int, len: Int, firstVal: Int): Unit = {
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)
}
}
def zPattern(n: Int): Array[Array[Int]] = {
val grid = Array.fill(n)(Array.ofDim[Int](n))
fill(grid, 0, 0, n, 1)
return grid
}
}
Scala details:
grid(row)(col). Scala indexes arrays with parentheses.grid(row)is a row, and the second pair of parentheses picks the cell in it. Assigning to it,grid(row)(col) = firstVal, updates the array.- Arrays are mutable and shared.
fillreceives a reference to the samegrid, so what it writes is visible inzPattern. That is why it can returnUnit. if/elseas the body. Both branches have typeUnit, so the method needs noreturn. The finalreturn gridinzPatternis optional in Scala: writing justgridas the last line does the same.- The shared-row trap. Building the rows from one array that you created beforehand is a bug. With
val row = Array.ofDim[Int](2)andArray.fill(2)(row), both rows are the same array, and aftershared(0)(0) = 9printing the rows gives9,0 | 9,0. Creating the array inside the second argument, as the lesson does, gives9,0 | 0,0.
Complexity
Time: O(n²). Every cell is written exactly once. The recursion makes n² calls that write a cell, plus (n² - 1) / 3 calls that only split a square, so the total is (4n² - 1) / 3 calls, each doing constant work.
Space: O(n²). The result itself has n² cells. The recursion adds only O(log n), since each level halves the side and there are log2(n) + 1 levels.
Edge cases and common mistakes
n = 1. The first call hits the base case, and the result is[[1]].- Using the wrong offsets. Mixing up the lower-left and upper-right quadrant produces a grid that fills column-first. The upper-right quadrant gets one block of numbers before it and the lower-left gets two.
- A side that isn't a power of 2. For
n = 3,len / 2is 1, so the four quadrants are single cells that cover only the 2 by 2 corner. The result is1 2 0 / 3 4 0 / 0 0 0: the rest of the grid stays 0. The requirement thatnbe a power of 2 is what keeps the split exact. n = 0. The base case testslen == 1, which 0 never reaches. Half of 0 is 0, so the function calls itself forever and the program fails with aStackOverflowError.- Forgetting the first value. The quadrants must start at
firstVal + k * newLen * newLen, not at a global counter that restarts from 1.
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]].- For
n = 8the last row is43 44 47 48 59 60 63 64.
