Library · The Fiddler: Solutions · Chapter 63

How Greedily Can You Mow the Lawn?

Revised Report an error

A circular lawn has radius 11. You mow it in straight strips 11 unit wide. Rather than plan ahead, you mow greedily: each pass is oriented and positioned to cut as much still-unmown grass as possible. How many passes does it take to mow the entire lawn?

The Fiddler, Zach Wissner-Gross, June 20, 2025(original post)

Two parallel strips would suffice if planned, but the greedy rule takes five.
Two parallel strips would suffice if planned, but the greedy rule takes five.

Solution

A clever mower needs only two parallel strips (they tile the band ∣y∣≤1|y|\le1, which contains the disk). Greed does worse: the first pass takes the widest cut, the central strip, leaving two caps; no single later strip can clear both caps at once, so the cuts fan out at different angles and leftovers linger near the rim. Simulating the greedy rule on a fine grid, full coverage takes 5 passes.\boxed{5\text{ passes}}.

The computation

Grid the disk, and at each pass try every strip orientation: for a given angle, project the uncovered points onto the perpendicular axis and slide a width-11 window to the position covering the most. Take the best strip over all angles, mark it mown, repeat.

import numpy as np
h = 0.005; xs = np.arange(-1, 1 + h, h); X, Y = np.meshgrid(xs, xs)
m = X*X + Y*Y <= 1; px, py = X[m], Y[m]; cov = np.zeros(len(px), bool)
ang = np.linspace(0, np.pi, 120, endpoint=False); passes = 0
while not cov.all():
    best = (-1, None)
    for th in ang:
        pv = np.sort((px*np.cos(th) + py*np.sin(th))[~cov])
        cnt = np.searchsorted(pv, pv + 1, 'right') - np.arange(len(pv))
        i = cnt.argmax()
        if cnt[i] > best[0]: best = (cnt[i], (np.cos(th), np.sin(th), pv[i]))
    nx, ny, c = best[1]; p = px*nx + py*ny
    cov |= (p >= c - 1e-9) & (p <= c + 1 + 1e-9); passes += 1
print(passes)                            # 5

Extra Credit

Now you bore cylinders of diameter 11 (radius 12\tfrac12) greedily through a unit sphere, each cylinder removing the most remaining material. How many passes to pulverise the whole sphere?

Solution

The same greedy rule in three dimensions clears the ball in about 9 passes.\boxed{9\text{ passes}}. (The exact count is paywalled and mildly grid-sensitive; this is my own simulation: the unmown fraction falls 0.65,0.43,0.23,0.15,0.08,0.03,0.013,0.003,0.0010.65,0.43,0.23,0.15,0.08,0.03,0.013,0.003,0.001 over nine passes, the last specks being voxel-grid artifacts.)

The computation

Voxelise the ball. Each pass samples many cylinder axes (random directions); for each, project the remaining voxels onto the plane perpendicular to the axis and find the radius-12\tfrac12 disk covering the most. Bore the best cylinder and repeat, tracking the unmown fraction.

rng = np.random.default_rng(0); n = 40
xs = (np.arange(n) + 0.5) / n * 2 - 1
X, Y, Z = np.meshgrid(xs, xs, xs, indexing='ij')
m = (X*X + Y*Y + Z*Z) <= 1
V = np.column_stack([X[m], Y[m], Z[m]]); cov = np.zeros(len(V), bool)
def basis(nrm):                          # two axes spanning the plane perp to nrm
    a = np.array([1.0, 0, 0]) if abs(nrm[0]) < 0.9 else np.array([0, 1.0, 0])
    e1 = np.cross(nrm, a); e1 /= np.linalg.norm(e1)
    return e1, np.cross(nrm, e1)
frac = []
while not cov.all():
    idx = np.where(~cov)[0]; rem = V[idx]; best = -1; sel_best = None
    for _ in range(60):
        nrm = rng.normal(size=3); nrm /= np.linalg.norm(nrm)
        e1, e2 = basis(nrm); P = np.column_stack([rem @ e1, rem @ e2])
        for c in P[rng.integers(0, len(P), 50)]:    # candidate disk centres
            sel = (P[:, 0] - c[0])**2 + (P[:, 1] - c[1])**2 <= 0.25
            if sel.sum() > best: best = int(sel.sum()); sel_best = idx[sel]
    cov[sel_best] = True; frac.append(round((~cov).mean(), 3))
print(frac)            # [0.649, 0.427, 0.227, 0.15, 0.078, 0.031, 0.013, 0.003, 0.001, ...]
Report an error on this page

Reports are stored by Netlify. See the privacy note.