Haskell中所有1的最大尺寸平方

时间:2016-07-04 23:38:26

标签: algorithm haskell functional-programming

如果我有一个矩阵

m = 0 0 1 1 0
    0 1 1 1 0
    1 0 1 1 1
    1 1 1 1 1
    0 1 1 1 1

如何找到m的最大nXn子矩阵,只有1? 这似乎是命令式语言中的一个好问题,实际上在使用其他语言的堆栈溢出时有这个答案,但我不知道从哪里开始使用Haskell。

我们可以假设m由

表示
m = [ [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]
    ]

如果有帮助的话。在这种情况下,答案将是右下角的3x3子矩阵。

2 个答案:

答案 0 :(得分:5)

最佳 O(n ^ 2)解决方案只能使用列表和右侧折叠(1)来完成。我还推广到最大面积子矩形,而不仅仅是正方形。仅限于正方形是一种简单的修改(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

这个想法是,首先,在每个单元格中,向上计算 1 直到你达到零。对于问题中的示例,这将是:

[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]

可以通过右侧折叠来完成:

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

然后,将每个数字解释为建筑物的高度,每一行都会缩小为天际线问题:

  

考虑到建筑物的高度,找到最大的矩形横幅,完全适合天际线(即建筑物的轮廓)。

例如,最后两行中的天际线和最佳矩形横幅将如下(横幅标有#):

                            +
         +                  +
     +   +                  # # #
     +   # # #            + # # #
     + + # # #            + # # #
4th: 3 1 4 2 2     5th: 0 2 5 3 3

通过维持高度增加的建筑物堆栈(列表),可以在每行的线性时间内解决此问题。每当项目从堆栈中弹出时,我们都会更新当前的最佳解决方案:

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

然后,

\> 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。这是最优的,因为它在矩阵元素的数量上是线性的,你不能比线性更好 2.为了限制正方形,您只需要将compare函数中使用的lambda更改为:\a -> min (width a) (height a)

答案 1 :(得分:3)

有关如何在Haskell中实现动态编程算法的示例,请参阅此Wiki页面:

这是一个简单的例子,它解决了“最小步骤”和“#34;问题描述here。在整数 n ,您可以移至 n-1 n / 2 (如果 n 是偶数)或 n / 3 如果 n 可以被3整除。这是使用Haskell数组的解决方案:

import Data.Array

stepsToOne n = arr
  where arr = array (1,n) [
                 (i,e)
                   | i <- [1..n]
                   , let e  | i <= 1    = 0
                            | otherwise = 1 + minimum (sub1 ++ div2 ++ div3)
                              where sub1 = [ arr ! (i-1) ]
                                    div2 = if mod i 2 == 0 then [ arr ! (div i 2) ] else []
                                    div3 = if mod i 3 == 0 then [ arr ! (div i 3) ] else []
              ]

<强>更新

以下是使用列表实现的相同算法:

stepsToOne' n = arr
  where arr = [ e | i <- [0..n]
                  , let e | i <= 1 = 0
                            | otherwise = 1 + minimum (sub1 ++ div2 ++ div3)
                              where sub1 = [ arr !! (i-1) ]
                                    div2 = if mod i 2 == 0 then [ arr !! (div i 2) ] else []
                                    div3 = if mod i 3 == 0 then [ arr !! (div i 3) ] else []
              ]

test = stepsToOne' 10

请注意arr !! i如何引用arr !! (i-1)以及可能arr !! (div i 2)arr !! (div i 3)。 Haskell将根据它们的相关性来确定评估列表项的顺序。

这与写作相同:

stepsToOne' n = [ s0, s1, s2, s3, s4, s5, s6, ... ]
  where s0 = 0
        s1 = 0
        s2 = 1 + minimum [s1, s1]
        s3 = 1 + minimum [s2, s1]
        s4 = 1 + minimum [s3, s2]
        s5 = 1 + minimum [s4]
        ...