Filling the grid

Let’s state the problem: place a b×a∈Nb \times a \in \N grid (rows × columns) into a w×h∈R+w \times h \in \R^{+} rectangle which fits n∈Nn \in \N squares such that the side length of the squares s∈R+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≔{ s∈R+∣∃a,b∈N . n≤ab∧as=w∧bs≤h }Sh≔{ s∈R+∣∃a,b∈N . n≤ab∧as≤h∧bs=h }s≔max⁡(Sw∪Sh).\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*}}

sw∈Sws_w \in S_w is a fit‑width solution, sh∈Shs_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=⌈nb⌉a = \left\lceil \frac{n}{b} \right\rceil to obtain a grid which fits nn squares, since clearly n≤abn \le ab. For this grid (a,b)(a, b) let s′=min⁡{wa,hb}s' = \min \big\{ \frac{w}{a}, \frac{h}{b} \big\} its corresponding side length; it’s easy to see that s′∈Sw∪Shs' \in S_w \cup S_h. We can just test all 1≤b≤n1 \le b \le n and pick the maximum s′s' 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 a≤na \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<nn−1=n+1+1n−1.\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  ⟹  b≤O(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=w∧bsw≤h  ⟺  a≥whb∧sw=wa,bsh=h∧ash≤w  ⟺  b≥hwa∧sh=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 r≔whr \coloneqq \frac{w}{h}, the rectangle’s aspect ratio:

aw≔min⁡{ a∈N∣∃b∈N . n≤ab∧a≥rb }bh≔min⁡{ b∈N∣∃a∈N . n≤ab∧b≥ar }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:

n≤awbw∧aw≥rbw  ⟺  naw≤bw≤awr  ⟺  ⌈naw⌉≤⌊awr⌋.{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⁡{ a∈N∣⌊ar⌋≥⌈na⌉ },bh=min⁡{ b∈N∣⌊rb⌋≥⌈nb⌉ }.\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 a0≤awa_0 \le a_w (and b0≤bhb_0 \le b_h) test every value until the solution condition holds, i.e. loop while:

⌊ar⌋<⌈na⌉  ⟺  a<r⌈na⌉,⌊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:

aw≥r⌈naw⌉  ⟹  aw2≥rn  ⟹  aw≥rn.{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 aw∈Na_w \in \N it can therefore be no smaller than a0=⌈a∗⌉a_0 = \left\lceil a^* \right\rceil, with a∗≔rna^* \coloneqq \sqrt{rn}. Likewise, bw≥⌈b∗⌉b_w \ge \left\lceil b^* \right\rceil, with b∗≔nrb^* \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 a0≤a<r⌈na⌉≤r⌈na0⌉a_0 \le a < r\left\lceil{\frac{n}{a}}\right\rceil \le r \big\lceil \frac{n}{a_0} \big\rceil, aa is incremented at most ⌈r⌈na0⌉−a0⌉\big\lceil{r\big\lceil{\frac{n}{a_0}}\big\rceil - a_0}\big\rceil times. An upper bound on the iteration count is:

⌈r⌈na0⌉−a0⌉<r⌈na0⌉−a0+1<rna0+r−a0+1=rn⌈rn⌉+r−⌈rn⌉+1<rnrn+r−rn+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(log⁡R)\mathcal{O}(\log R).

My n=1020n = 10^{20} grid is a piece of cake now, but my r=2↑↑6r = 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 1≤b≤n1 \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 \big\{\frac{w}{a'}, \frac{h}{b} \big\} \le \min \big\{\frac{w}{a}, \frac{h}{b} \big\}, 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 \big\{ w, \frac{h}{b'} \big\} \le \min \big\{ w, \frac{h}{n} \big\}, with the latter value being the side length of (1,n)(1, n), a solution that’s tested.

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

b≤nr  ⟹  rb≤nb≤a  ⟹  hb≥wa  ⟹  s′=w⌈nb⌉.{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=⌊b∗⌋b = \lfloor b^* \rfloor. Consider now b≥b∗b \ge b^*:

b≥nr  ⟹  rb≥nb  ⟹  rb>⌈nb⌉⏟(1)∨⌈nb⌉≥rb⏟(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 b≥b∗b \ge b^* by b=⌈b∗⌉b = \lceil b^* \rceil. To tackle case (2)(2) we’ll assume that r≥1r \ge 1; it then follows:

rb≤⌈nb⌉<nb+1  ⟹  rb2−b−n<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 b∗≤b<b∗+1b^* \le b < b^* + 1, thus, simply by definition of the ceiling function, b=⌈b∗⌉b = \left\lceil b^* \right\rceil. Hence when r≥1r \ge 1 only b∈{ ⌊b∗⌋,⌈b∗⌉ }b \in \Set{ \lfloor b^* \rfloor, \lceil b^* \rceil } can ever maximize the side length.

To tackle r≤1r \le 1, notice the symmetry of the problem with respect to rr. Taking r′≔1rr' \coloneqq \frac{1}{r} just rotates the original rectangle by 90∘90^\circ – the side length of the squares remains the same, the number of rows and columns swap. Using the result above, for r≤1r \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=nr′a^* = \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 r′r'.

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(log⁡R)\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 b≠bwb \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
>