找到所有后续元素之间的最大差异

时间:2013-10-04 22:33:24

标签: performance algorithm haskell

我正在尝试解决我在codeingame.com上发现的训练练习

问题如下:您有一个数字列表,希望找到v_small - v_big之间差异的最小值,条件是v_big > v_smallv_big 在列表中 v_small之前。此外,此问题的最长时间为1秒,最大内存使用量为512MB。

对于小型列表,一个天真的算法就足够了:

---------------------------------- try1.hs -------------------------------------
import Control.Applicative ((<$>))

main :: IO ()
main = do _ <- getLine
          v <- g . f . map read . take 1000 . words <$> getLine --or equivalently
--        v <- g . h . map read . take 1000 . words <$> getLine 
          print v

f :: [Int] -> [Int]
f [] =   []
f xx@(x:xs) = (minimum $ map (\y -> y-x) xx) : (f xs)

g :: [Int] -> Int
g [] = 0
g xs = minimum xs

h :: [Int] -> [Int]
h [] = []
h (x:xs) = (foldr (\y' y -> min (y'-x) y) 0 xs): (h xs)

但我认为函数fh生成n*(n+1)/2个元素,其中n是列表的长度。对于最后一个列表需要多少年龄有99999多个元素。

接下来的尝试是找到局部最大值和最小值并仅将最大值与最小值进行比较 - 这应该将算法的成本降低到#maxima * #minima

---------------------------------- try2.hs -------------------------------------
import Control.Applicative ((<$>))
-- import Control.Arrow ((&&&))

data Extremum = Max Int | Min Int deriving (Show)


main :: IO ()
main = do _ <- getLine
          e <- getExtremes
          print e

getExtremes :: IO Int
getExtremes = minimum . concat . myMap f . headextr .
                                         map read . take 1000 .words <$> getLine

myMap :: (a -> [a] -> [b]) -> [a] -> [[b]]
myMap _ [] = []
myMap g xx@(x:xs) = (g x) xx : myMap g xs

f :: Extremum -> [Extremum] -> [Int]
f (Max y) (Max _:xs) = f (Max y) xs
f (Max y) (Min x:xs) = (min 0 (x-y)): f (Max y) xs
f _ _ = []

headextr :: [Int] -> [Extremum]
headextr xx@(x:y:_) | x > y = Max x : extremes xx
                    | x < y = Min x : extremes xx
headextr xx = extremes xx


extremes :: [Int] -> [Extremum]
extremes [] = []
extremes [x] = [Max x, Min x]
extremes [x,y]      | x > y          =       Min y:[]
                    | otherwise      =       Max y:[]
extremes (x:y:z:xs) | x > y && y < z = Min y:extremes (y:z:xs)
                    | x < y && y > z = Max y:extremes (y:z:xs)
                    | otherwise      =       extremes (y:z:xs)

但仍然没有达到1秒的理想时间。 我使用take 1000减少了输入以进行性能分析,但由于我不是专业程序员,因此我无法使用它,这是我发现的唯一信息 - 这很明显 - 在第一个版本中{{1是昂贵的功能,在第二个版本f/h也是罪魁祸首。

此练习的输入文件可以在http://www.codingame.com/ide/fileservlet?id=372552140039找到 - (如果此链接不起作用,可以在www.codingame.com上找到 - &gt; training - &gt;证券交易所损失 - &gt; Test_5_input .TXT / Test_5_output.txt)

那么如何加速这个算法还是有另一种算法更快?

2 个答案:

答案 0 :(得分:9)

前两个解决方案很慢,因为对于列表中的每个元素,它们会执行一个访问所有后续元素的计算。这是O(n ^ 2)。我还没有完全理解你的第二个解决方案,但它似乎是这样的:只过滤掉局部极值(其中局部极值意味着它比它的两个邻居大或小于它的两个邻居),然后运行一个O(n ^ 2)极值列表上的算法。不幸的是,在最坏的情况下,每个元素都可以是极值,因此整体也是O(n ^ 2)。 (事实上​​,在随机列表中,我们期望大多数元素都是局部极值,所以这不仅仅是对事物的悲观。)

让我们看看我们是否可以发明一种O(n)算法。

我们将从略微改写的O(n ^ 2)算法开始。这个算法的想法是这样的:首先,不确定地选择列表中的一个位置作为v_big。然后,不确定地选择列表中的后续位置作为v_small。在所有这些不确定性选择中取最大值。在代码中:

f_spec xs = maximum $ do
    later@(v_big:_) <- tails xs
    v_small:_       <- tails later
    return (v_big - v_small)

现在,我们需要两个独立的见解来将其转换为O(n)算法。首先,我们只需要拆分一次:一旦我们选择了v_small,我们知道选择v_big的正确方法是选择列表中最大的元素。我们可以这样实现该算法:

f_slow xs = maximum $ do
    earlier@(v_small:_) <- tails (reverse xs)
    let v_big = maximum earlier
    return (v_big - v_small)

这几乎是“O”(n):它只做出一个不确定的选择;但是一旦我们做出那个选择就进行必要的计算仍然是O(n),导致总运行时间为O(n ^ 2)。第二个见解是我们可以记住在我们的非确定性选择之后所需的计算,因此这个计算是O(1)。我们可以非常有效地建立所有最大值的列表,如下所示:

maximums xs = scanl1 max xs

maximum类似,此函数需要O(n)时间;与maximum不同,此值返回xs的所有前缀的最大值,而不仅仅是整个列表的最大值。现在,当我们做出不确定性选择时,我们可以同时选择 v_smallv_big

f_fast xs = maximum $ do
    (v_big, v_small) <- zip (maximums xs) xs
    return (v_big - v_small)

从那里你只需要一些美化来获得看起来非常漂亮并且仍然在O(n)时间运行的东西:

f xs = maximum $ zipWith (-) (maximums xs) xs

答案 1 :(得分:1)

这是一个使用MonoidBiggestDrop的解决方案,可以跟踪数字中的最大跌幅。它记得第三条信息,一系列数字中的最小信息。这将允许我们将数据集拆分成碎片,处理这些碎片,然后组合碎片以获得答案。下面的示例代码没有利用这一点;它只会在数据集中折叠Monoid的{​​{1}}一次。

可能有更好的方法来编写更快的mappend

我尝试使用'管道'库,因为它似乎适合这个问题,但我认为它没有为解决方案添加任何东西。

Monoid

我使用'pipes'库将上面的代码放在一起,希望了解'pipes-bytestring'。我不得不放弃并写一个制作人来阅读文件中的文字。从文件中读取的块大小只是猜测。