Turn right at the big square
Weekly challenge 298 — 2 December 2024
Week 298: 2 Dec 2024
You are given an m x n binary matrix with 0 and 1 only. Write a script to find the largest square containing only 1s and return its area.
Example 1 Input: @matrix = ([1, 0, 1, 0, 0], [1, 0, 1, 1, 1], [1, 1, 1, 1, 1], [1, 0, 0, 1, 0]) Output: 4 Two maximal square found with same size marked as 'x': [1, 0, 1, 0, 0] [1, 0, x, x, 1] [1, 1, x, x, 1] [1, 0, 0, 1, 0] [1, 0, 1, 0, 0] [1, 0, 1, x, x] [1, 1, 1, x, x] [1, 0, 0, 1, 0] Example 2 Input: @matrix = ([0, 1], [1, 0]) Output: 1 Two maximal square found with same size marked as 'x': [0, x] [1, 0] [0, 1] [x, 0] Example 3 Input: @matrix = ([0]) Output: 0
This is a good example of something which the human eye can spot in a second or two, but isn't so easy to do in procedural software.
In general, when we are looking for the largest something we need to generate all the possible somethings and keep track of the largest one we've found so far. That is a possible approach here, but I've found a (somewhat) better one.
First, let's note that the side length $s
of the largest square can't be
more than the smaller of the height $h
or the width $w
of the matrix.
Also, the top-left corner of a square of side length $q
must have
an $x
coordinate no more than $w - $q
and a $y
coordinate
no more than $h - $q
, because otherwise the bottom-right element
would be outside the matrix.
Armed with those insights I have an outer loop looking for squares
with side lengths $q
ranging from $s
down to 1. Searching for
the largest possible square first means we
can stop as soon as we find a square: ther's no need to keep looking
because we've already eliminated the possibility of a larger one.
Within this outer loop I then have two nested loops checking all
the possible top-left corners for a $q x $q
square, bearing in mind
the limits on top-left corners described above.
And lastly, for each of these top-left corner and size combinations I check the sqare for 0s, and if there are none, we have the solution.
An algorithm with 4 nested loops is not something I would use lightly, but with the limits imposed by this approach it handles a 100 x 100 matrix of random 1s and 0s in under a second. The randomness means that in such a matrix there is typically only a 2 x 2 square to be found, so I think I can claim that this is a sufficiently economical solution.
#!/usr/bin/perl # Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge use v5.26; # The Weekly Challenge - 2024-12-02 use utf8; # Week 298 - task 1 - Maximal square use warnings; # Peter Campbell Smith binmode STDOUT, ':utf8'; maximal_square([[1, 0, 1, 0, 0], [1, 0, 1, 1, 1], [1, 1, 1, 1, 1], [1, 0, 0, 1, 0]]); maximal_square([[1, 0, 1, 0, 0], [1, 0, 1, 1, 1], [1, 1, 1, 1, 1], [1, 0, 1, 1, 1], [0, 1, 0, 1, 0]]); maximal_square([[1, 0, 1, 1], [0, 1, 0, 1], [1, 0, 1, 0], [1, 0, 1, 1]]); maximal_square([[0, 0], [0, 0]]); sub maximal_square { my ($m, $w, $h, $s, $q, $x, $y, $xx, $yy, $top_left); $m = shift; $h = @$m; # height $w = @{$m->[0]}; # width $s = $w > $h ? $h : $w; # max possible square side $top_left = ''; # loop over possible sizes, largest to smallest SIZES: for ($q = $s; $q > 0; $q --) { # loop over all possible top-left x and y for $x (0 .. $w - $q) { TOPLEFT: for $y (0 .. $h - $q) { # now check if this is the top left of # a $q-sided square for $xx ($x .. $x + $q - 1) { for $yy ($y .. $y + $q - 1) { next TOPLEFT unless $m->[$yy]->[$xx] == 1; } } # found a square! $top_left = qq[$x, $y]; last SIZES; } } } print_matrix('Input: ', $m); say qq[Output: ] . ($top_left ? (($q * $q) . qq[ (top left at $top_left)]) : '0'); } sub print_matrix { my ($legend, $matrix, $j); # format matrix ($legend, $matrix) = @_; say ''; for $j (0 .. @$matrix - 1) { say qq{$legend [} . join(', ', @{$matrix->[$j]}) . qq(]); $legend = ' ' x length($legend); } }
Input: [1, 0, 1, 0, 0] [1, 0, 1, 1, 1] [1, 1, 1, 1, 1] [1, 0, 0, 1, 0] Output: 4 (top left at 2, 1) Input: [1, 0, 1, 0, 0] [1, 0, 1, 1, 1] [1, 1, 1, 1, 1] [1, 0, 1, 1, 1] [0, 1, 0, 1, 0] Output: 9 (top left at 2, 1) Input: [1, 0, 1, 1] [0, 1, 0, 1] [1, 0, 1, 0] [1, 0, 1, 1] Output: 1 (top left at 0, 0) Input: [0, 0] [0, 0] Output: 0
Any content of this website which has been created by Peter Campbell Smith is in the public domain