Filling the grid

Let’s state the problem: place a b×aNb \times a \in \N grid (rows × columns) into a w×hR+w \times h \in \R^{+} rectangle which fits nNn \in \N squares such that the side length of the squares sR+s \in \R^{+} is maximized and the grid does not overflow the rectangle. Since the side length is maximized, the grid will fit in the rectangle’s full width or height, or both if a perfect fit is possible. Formally:

Sw{sR+a,bN . nabas=wbsh}Sh{sR+a,bN . nabashbs=h}smax(SwSh).\brk[0]{\begin{gather*}} \brk{\begin{align*}} S_w \brk{&}\coloneqq \big\{\, s \in \R^{+} \mid \exist a, b \in \N \text{ . } \brk \brk{&\hspace{2.3em}}n \le ab \land as = w \land bs \le h \,\big\} \\ S_h \brk{&}\coloneqq \big\{\, s \in \R^{+} \mid \exist a, b \in \N \text{ . } \brk \brk{&\hspace{2.3em}}n \le ab \land as \le h \land bs = h \,\big\} \\ s \brk{&}\coloneqq \max (S_w \cup S_h). \brk[0]{\end{gather*}} \brk{\end{align*}}

swSws_w \in S_w is a fit‑width solution, shShs_h \in S_h a fit‑height solution. The final solution ss is the desired maximal side length. Our target is to build an algorithm to find ss.

Observe that for any fixed bb we can always take a=nba = \left\lceil \frac{n}{b} \right\rceil to obtain a grid which fits nn squares, since clearly nabn \le ab. For this grid (a,b)(a, b) let s=min{wa,hb}s' = \min \Set{ \frac{w}{a}, \frac{h}{b}} its corresponding side length; it’s easy to see that sSwShs' \in S_w \cup S_h. We can just test all 1bn1 \le b \le n and pick the maximum ss' to obtain the solution ss:

function fillGrid(n, w, h) {
  let s = 0;
  for (let b = 1; b <= n; b++) {
    s = Math.max(s, Math.min(w / Math.ceil(n / b), h / b));
  }
  return s;
}

The asymptotic complexity is O(n)\mathcal{O}(n). To improve this, notice that if ana \le \sqrt{n} there are obviously O(n)\mathcal{O}(\sqrt{n}) distinct values of aa. If a>na > \sqrt{n}:

nb>n    nb+1>n    b<nn1=n+1+1n1.\left\lceil \frac{n}{b} \right\rceil > \sqrt{n} \implies \frac{n}{b} + 1 > \sqrt{n} \implies b < \frac{n}{\sqrt{n} - 1} = \sqrt{n} + 1 + \frac{1}{\sqrt{n} - 1}.

Therefore a>n    bO(n)a > \sqrt{n} \implies b \le \mathcal{O}(\sqrt{n}), so in all cases there are O(n)\mathcal{O}(\sqrt{n}) distinct aa values. Clever incrementing of bb could then achieve complexity O(n)\mathcal{O}(\sqrt{n}). But for my grid with n=1020n = 10^{20} squares this isn’t good enough – can we do better?

The solution statement as it stands juggles a lot of variables. We can rewrite the grid dimension constraints using only a,b,w,ha, b, w, h:

asw=wbswh    awhbsw=wa,bsh=hashw    bhwash=hb.\begin{gather*} as_w = w \land bs_w \le h \brk \iff a \ge \frac{w}{h}b \land s_w = \frac{w}{a}, \brk[0]{\\} \brk{\\[1.2em]} bs_h = h \land as_h \le w \brk \iff b \ge \frac{h}{w}a \land s_h = \frac{h}{b}. \end{gather*}

ss is now dependent on the grid and rectangle dimensions. Using rwhr \coloneqq \frac{w}{h}, the rectangle’s aspect ratio:

awmin{aNbN . nabarb}bhmin{bNaN . nabbar}s=max{waw,hbh}.\brk[0]{\begin{gather*}} \brk{\begin{align*}} a_w \brk{&}\coloneqq \min \big\{\, a \in \N \mid \exist b \in \N \text{ . } \brk \brk{&\hspace{4.15em}}n \le ab \land a \ge rb \,\big\} \\ b_h \brk{&}\coloneqq \min \brk[0]{\bigg}\brk{\big}\{\, b \in \N \mid \exist a \in \N \text{ . } \brk \brk{&\hspace{4.15em}}n \le ab \land b \ge \frac{a}{r} \,\bigg\} \\ s \brk{&}= \max \Set{ \frac{w}{a_w}, \frac{h}{b_h}}. \brk[0]{\end{gather*}} \brk{\end{align*}}

awa_w is the column count of the fit‑width grid, bhb_h the row count of the fit‑height grid. They are minimized since the side length is inversely proportional. The side length is a trivial result of the grid dimensions; only the algorithm for finding awa_w and bhb_h remains to be developed.

Let’s take a look at a fit‑width grid with dimensions (aw,bw)N2(a_w, b_w) \in \N^2. bwb_w can be abstracted away:

nawbwawrbw    nawbwawr    nawawr.{n \le a_wb_w \land a_w \ge rb_w} \iff {\frac{n}{a_w} \le b_w \le \frac{a_w}{r}} \iff {\left\lceil \frac{n}{a_w} \right\rceil \le \left\lfloor \frac{a_w}{r} \right\rfloor}.

This is a range for all bwb_w corresponding to awa_w. With an analogous derivation for bhb_h, we get:

aw=min{aNarna},bh=min{bNrbnb}.\begin{gather*} a_w = \min \Set{ a \in \N \mid \left\lfloor \frac{a}{r} \right\rfloor \ge \left\lceil \frac{n}{a} \right\rceil }, \brk[0]{\quad} \brk b_h = \min \Set{ b \in \N \mid \left\lfloor rb \right\rfloor \ge \left\lceil \frac{n}{b} \right\rceil }. \end{gather*}

Since the second comparison term is non‑increasing in a,a, respectively in b,b, it follows that every a>awa > a_w and b>bhb > b_h would satisfy the solution condition. The algorithm idea is thus the following: starting from an initial guess a0awa_0 \le a_w (and b0bhb_0 \le b_h) test every value until the solution condition holds, i.e. loop while:

ar<na    a<rna,rb<nb    rb<nb.\begin{gather*} \left\lfloor \frac{a}{r} \right\rfloor < \left\lceil \frac{n}{a} \right\rceil \iff a < r \left\lceil \frac{n}{a} \right\rceil, \brk[0]{\quad} \brk \left\lfloor rb \right\rfloor < \left\lceil \frac{n}{b} \right\rceil \iff rb < \left\lceil \frac{n}{b} \right\rceil. \end{gather*}

Only the initial guesses a0a_0 and b0b_0 remain to be made. From the solution condition of awa_w:

awrnaw    aw2rn    awrn.{a_w \ge r \left\lceil \frac{n}{a_w} \right\rceil} \implies {{a_w}^2 \ge rn} \implies {a_w \ge \sqrt{rn}}.

Since awNa_w \in \N it can therefore be no smaller than a0=aa_0 = \left\lceil a^* \right\rceil, with arna^* \coloneqq \sqrt{rn}. Likewise, bwbb_w \ge \left\lceil b^* \right\rceil, with bnrb^* \coloneqq \sqrt{\frac{n}{r}}. Using the loop condition above, an algorithm can finally be built:

function fillGrid(n, w, h) {
  const r = w / h

  let a = Math.ceil(Math.sqrt(r * n))
  for (; a < r * Math.ceil(n / a); a++)

  let b = Math.ceil(Math.sqrt(n / r))
  for (; r * b < Math.ceil(n / b); b++)

  return Math.max(w / a, h / b)
}

To analyze the runtime complexity, observe that since a0a<rnarna0a_0 \le a < r\left\lceil{\frac{n}{a}}\right\rceil \le r \left\lceil \frac{n}{a_0} \right\rceil, aa is incremented at most rna0a0\left\lceil{r\left\lceil{\frac{n}{a_0}}\right\rceil - a_0}\right\rceil times. An upper bound on the iteration count is:

rna0a0<rna0a0+1<rna0+ra0+1=rnrn+rrn+1<rnrn+rrn+1=r+1.\begin{align*} \brk{&}\left\lceil{r\left\lceil{\frac{n}{a_0}}\right\rceil - a_0}\right\rceil \brk &< r \left\lceil{\frac{n}{a_0}}\right\rceil - a_0 + 1 \\ &< r \frac{n}{a_0} + r -a_0 + 1 \\ &= r\frac{n}{\left\lceil \sqrt{rn} \right\rceil} + r - \left\lceil\sqrt{rn}\right\rceil + 1 \\ &< \frac{rn}{\sqrt{rn}} + r - \sqrt{rn} + 1 \\ &= r + 1. \end{align*}

This means at most r+1\left\lfloor r \right\rfloor + 1 iterations. This bound is tight: for n=33,r=18.3n = 33, r = \frac{1}{8.3} the fit‑width loop does 1=r+11 = \lfloor r \rfloor + 1 iteration, reaching the upper bound. Similarly for the second loop we get a tight upper bound of 1r+1\left\lfloor \frac{1}{r} \right\rfloor + 1 iterations. The runtime is therefore O(R)\mathcal{O}(R) with R=max{r,1r}R = \max \Set{r, \frac{1}{r}}. One could binary search over [a0,a0+r+1]\left[a_0, a_0 + \lfloor r \rfloor + 1\right] and [b0,b0+1r+1]\left[b_0, b_0 + \left\lfloor \frac{1}{r} \right\rfloor + 1\right] to reduce complexity to O(logR)\mathcal{O}(\log R).

My n=1020n = 10^{20} grid is a piece of cake now, but my r=26r = 2 \uarr \uarr 6 grid still requires around 101970010^{19700} iterations. There are 108010^{80} atoms in the observable universe. Can we do better?

Let’s return to the initial algorithm. It iterates through all solutions (a=nb,b)\left(a = \left\lceil \frac{n}{b} \right\rceil, b\right) for 1bn1 \le b \le n. It must find the optimal ss: any other a>aa' > a is worse, as at each step the side length s=min{wa,hb}min{wa,hb}s' = \min \Set{\frac{w}{a'}, \frac{h}{b}} \le \min \Set{\frac{w}{a}, \frac{h}{b}}, and any other b>nb' > n does not help, since it would correspond to a solution (1,b)(1, b') with s=min{w,hb}min{w,hn}s' = \min \Set{ w, \frac{h}{b'} } \le \min \Set{ w, \frac{h}{n} }, with the latter value being the side length of (1,n)(1, n), a solution that’s tested.

Consider then what happens when bb=nrb \le b^* = \sqrt{\frac{n}{r}}:

bnr    rbnba    hbwa    s=wnb.{b \le \sqrt{\frac{n}{r}}} \implies {rb \le \frac{n}{b} \le a} \implies {\frac{h}{b} \ge \frac{w}{a}} \implies {s' = \frac{w}{\left\lceil \frac{n}{b} \right\rceil}}.

Every solution tested is fit‑width and to maximize the side length we must take b=bb = \lfloor b^* \rfloor. Consider now bbb \ge b^*:

bnr    rbnb    rb>nb(1)nbrb(2).b \ge \sqrt{\frac{n}{r}} \implies rb \ge \frac{n}{b} \implies {\underbrace{rb > \left\lceil \frac{n}{b} \right\rceil}_{\text{(1)}} \lor \underbrace{\left\lceil \frac{n}{b} \right\rceil \ge rb}_{\text{(2)}}}.

In case (1)(1) the solution (a=nb,b)(a = \left\lceil \frac{n}{b} \right\rceil, b) is clearly fit‑height, so s=hbs' = \frac{h}{b} is maximized when bbb \ge b^* by b=bb = \lceil b^* \rceil. To tackle case (2)(2) we’ll assume that r1r \ge 1; it then follows:

rbnb<nb+1    rb2bn<0    b<12r+14r2+nr    b<12+(12+nr)2=nr+1\begin{align*} \brk[3,]{&}rb \le \left\lceil \frac{n}{b} \right\rceil < \frac{n}{b} + 1 \brk[3,] \implies &rb^2 - b - n < 0 \brk \implies \brk[1,]{&}b < \frac{1}{2r}+\sqrt{\frac{1}{4r^2} + \frac{n}{r}} \\ \implies &b < \frac{1}{2} + \sqrt{\left(\frac{1}{2} + \sqrt{\frac{n}{r}}\right)^2} \brk[,1]{=} \brk[2,]{\\ \implies &b <} \sqrt{\frac{n}{r}} + 1 \end{align*}

Under both assumptions about bb and rr we have bb<b+1b^* \le b < b^* + 1, thus, simply by definition of the ceiling function, b=bb = \left\lceil b^* \right\rceil. Hence when r1r \ge 1 only b{b,b}b \in \Set{ \lfloor b^* \rfloor, \lceil b^* \rceil } can ever maximize the side length.

To tackle r1r \le 1, notice the symmetry of the problem with respect to rr. Taking r1rr' \coloneqq \frac{1}{r} just rotates the original rectangle by 9090^\circ – the side length of the squares remains the same, the number of rows and columns swap. Using the result above, for r1r \le 1 the optimal grid (a,b)(a, b) must have a{a,a}a \in \Set{ \lfloor a^* \rfloor, \lceil a^* \rceil }, with a=rn=nra^* = \sqrt{rn} = \sqrt{\frac{n}{r'}}, and (a,b)=(b,a)(a, b) = (b', a'), where (a,b)(a', b') is the optimal grid for rr'.

With this we have exhaustively covered the input domain. The O(1)\mathcal{O}(1) algorithm we’ve all been waiting for is…

function fillGrid(n, w, h) {
  const r = w / h;
  if (r < 1) return fillGrid(n, h, w);
  const s = (b) => Math.min(w / Math.ceil(n / b), h / b);
  const bs = Math.sqrt(n / r);
  return Math.max(s(Math.floor(bs)), s(Math.ceil(bs)));
}

If you’ve found the proofs hard to follow, use a graph: plot the functions nx\frac{n}{x} and xr\frac{x}{r} and the solution points (a,b)(a, b) and interpret geometrically. Here’s Desmos.

For additional context, this problem is a particular case of integer programming. There exist general algorithms to solve such problems but the best time complexity they can achieve here is still O(logR)\mathcal{O}(\log R).

Lastly, a challenge! Prove that when a fit‑width solution (aw,bw)(a_w, b_w) is the best, i.e. sw>shs_w > s_h for all (ah,bh)(a_h, b_h) fit‑height, there is no bbwb \ne b_w such that (aw,b)(a_w, b) is fit‑width. Check Uniqueness of fit‑width solution if you get stuck.

Read more
>