【发布时间】:2016-06-04 23:10:21
【问题描述】:
我正在尝试解决问题:
仅使用 1c、5c、10c、25c 或 50c 硬币有多少种方法可以获得 50 美元?
这是我的代码:
main = print $ coinCombinations [1,5,10,25,50] !! 5000
coinCombinations coins = foldr recurse (1 : repeat 0) coins
where recurse a xs = take a xs ++ zipWith (+) (drop a xs) (recurse a xs)
事实证明,我的 recurse 函数很慢,可能是二次时间或更糟。但我不明白为什么,因为它看起来类似于斐波那契列表
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
【问题讨论】:
-
只是猜测这与您在递归的每一步使用 take 和 drop 有很大关系。这些是“O(a)”函数,也许尝试使用 splitAt 会是更好的选择?另外,请记住 ++ 也是一个“O(a)”操作,因为连接不是使用指针算术完成的,而是通过遍历整个结构来完成的。
-
我以为可能是这样,但后来我尝试了一个更简单的
recurseone xs = head xs : zipWith (+) (tail xs) (recurseone xs),它仍然很慢 -
您首先了解为什么您的代码是正确的吗?通常,您可以从正确性证明(正确性属性的“终止”部分)推断资源使用情况(即复杂性界限)。
-
您没有在对
recurse的调用之间共享任何工作。如果你说recurse a xs = let l = take a xs ++ zipWith (+) (drop a xs) l in l,它更像fibs的例子。 '不是说这会很快,因为您也没有在foldr中的recurse的呼叫之间共享工作。但是,您可以利用惰性来获得动态编程的巧妙实现。 -
@user3217013 在惰性列表中,递归的有效性比有根据更重要。我的意思是,
let x=1:x in take 100 x将终止,因为1:x是高效的。发布的fibs使用了相同的技巧。在 OP 代码中,我猜想take a xs ++ ...部分应该使它富有成效(如果a>0 和xs不为空)。