An optimal O(n^2) solution can be done using only lists and right folds (1). I also generalize to maximum area sub-rectangle, not just squares. Limiting to only squares is an easy modification (2).
import Control.Monad (ap)
import Data.Ord (comparing)
data Rect = Rect {
row :: Int, -- lower left row index
col :: Int, -- lower left column index
width :: Int,
height :: Int
} deriving (Show, Eq)
instance Ord Rect where -- compare on area
compare = comparing $ \a -> width a * height a
The idea is that first, at each cell, count ones upwards until you hit a zero. For the example in the question this would be:
[0,0,1,1,0] [0,0,1,1,0]
[1,1,1,0,0] [1,1,2,0,0]
[1,0,1,1,1] >>> [2,0,3,1,1]
[1,1,1,1,1] [3,1,4,2,2]
[0,1,1,1,1] [0,2,5,3,3]
and can be done by a right fold:
count :: Foldable t => t [Int] -> [[Int]]
count = ($ repeat 0) . foldr go (const [])
where
go x f = ap (:) f . zipWith inc x
inc 0 = const 0
inc _ = succ
Then, interpreting each number as height of a building, each row reduces to a skyline problem:
Given height of buildings, find the largest rectangular banner which fits entirely under the skyline (i.e. outline of buildings).
For example, the skyline and the optimal rectangular banner in the last two rows would be as bellow (the banner is marked with #):
+
+ +
+ + # # #
+ # # # + # # #
+ + # # # + # # #
4th: 3 1 4 2 2 5th: 0 2 5 3 3
This problem can be solved in linear time for each row, by maintaining a stack (list) of buildings with increasing height. Whenever an item is popped out of the stack we update the current optimal solution:
solve :: Foldable t => t [Int] -> Rect
solve = maximum . zipWith run [0..] . count
where
run ri xs = maximum $ foldr go end xs 1 [(0, 0)]
where
end = go 0 $ \_ _ -> []
go x f i ((_, y): r@((k, _):_))
| x <= y = Rect ri k (i - k - 1) y: go x f i r
go x f i y = f (i + 1) $ (i, x): y
then,
\> solve [[0,0,1,1,0],[1,1,1,0,0],[1,0,1,1,1]]
Rect {row = 2, col = 2, width = 3, height = 1}
\> solve [[0,0,1,1,0],[1,1,1,0,0],[1,0,1,1,1],[1,1,1,1,1]]
Rect {row = 3, col = 2, width = 3, height = 2}
\> solve [[0,0,1,1,0],[1,1,1,0,0],[1,0,1,1,1],[1,1,1,1,1],[0,1,1,1,1]]
Rect {row = 4, col = 2, width = 3, height = 3}
1. this is optimal because it is linear in the number of elements of the matrix and you cannot do better than linear here.
2. in order to limit to just squares, you only need to change the lambda used in compare
function to: \a -> min (width a) (height a)