我正在从Project Euler处理问题24,如下所示:
排列是对象的有序排列。例如,3124 是一个可能的数字1,2,3和4的排列。如果所有的 排列按数字或字母顺序列出,我们称之为 词典顺序。字典排列0,1和2是:
012 021 102 120 201 210
数字0,1,2的百万分之一的词典排列是多少? 3,4,5,6,7,8和9?
我正在尝试使用Haskell来解决这个问题,并以蛮力方法开始:
import Data.List
(sort . permutation) [0..9] !! 999999
但是这需要太长时间,我想知道是不是因为程序首先得到所有的排列,然后对它们进行排序,然后最终得到第一百万个元素,这比它需要做的工作要多得多。
所以我想我可以加快速度,如果我要编写一个函数来枚举已经按字典顺序排列的排列,那么我们就可以停在第一百万个元素并得到答案。
我想到的算法是首先对输入列表x
进行排序,然后取第一个(最小的)元素,并以字典顺序将其添加到剩余元素的排列中。这些排序可以通过递归调用现在已经排序的x
尾部的原始函数来找到(这意味着我们的原始函数应该有一种标记输入列表是否已排序的方法)。然后我们继续使用x
的下一个最大元素,依此类推,直到我们得到完整的有序列表。不幸的是我仍然是Haskell的初学者,我写这个函数的尝试都失败了。关于如何做到这一点的任何提示?
答案 0 :(得分:3)
我认为评论的时间太长,但完全不是一个有效的解决方案。不过,这是一个应该立即开展工作的计划。
从按字典顺序生成排列开始。使用递归算法很容易做到这一点。首先,选择可用的最少元素,并递归地生成剩余元素的排列,将所选元素预先添加到每个排列。然后按字典顺序选择第二个元素并继续。
对于它的价值,如果输入列表按递增顺序排序,这是您在Haskell教学材料中经常发现的基于标准的非确定性 - 基于select
的排列算法。它是不 Data.List.permutations
使用的算法,它被设计为通过无限输入更快,更高效。
但你可以做得更好。您不需要在目标之前生成所有排列。你可以跳过,结果很简单。
您需要做的只是查看您定位的排列数量,让我们称之为k
,并使用它来索引排列。如果输入按字典顺序排序,则结果的第一个元素是索引q
处的元素,然后是索引r
处的剩余元素的排列,给定(q, r) = divMod k (fact(n - 1))
。
我确信有很多方法可以让它比这更快,但这应该可以使它基本上立即成为像一百万这样的小数字。