如果我有一个矩阵
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子矩阵。
答案 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]
...