我有一个有趣的问题,我有一段时间无法应付。我们有N个字母和N对应于它们的信封,这意味着所有的字母和信封都被寻址(具有相同的排列)。任务是找到没有固定点的字母的可能排列数 - 每个字母都在信封中,与所写的字母不同。当字母(和包络)通过一些N置换来解决时,问题非常容易。然后,我们所要做的就是找到N-derange的数量(http://en.wikipedia.org/wiki/Derangement)。
但总的来说这个问题可能会更有趣。我们给出了数字N和N的数字 - 第i个数字表示第i个字母(和信封)的地址。第一个数字是0(所以我们有字母和信封用[0,...,N-1]编号)。那我们有多少紊乱?例如,我们给出了:
5
1 1 2 2 3
然后答案是4,因为我们只有4种情况,当每个字母都没有相应的信封时,它们是:
2 2 1 3 1
2 2 3 1 1
2 3 1 1 2
3 2 1 1 2
(所以我们不区分具有相同地址的字母。)
有关:
6
0 0 0 1 1 1
答案是1.因为:1 1 1 0 0 0是唯一的方法。
我差点忘了...... 1< = N< = 15就足够了。
我想知道是否有任何方法可以很快地计算它以及如何做到这一点。
我应该如何使用算法?
答案 0 :(得分:2)
首次尝试,将其视为直接的动态编程问题。
所以你从左到右工作,对于信封列表中的每个位置,对于你可以留下的每个可能的字母组,找出你可以到达那个点的方式。前进很容易,你只需要一套,你知道有多少种方法可以到达那里,然后对于你可以放在下一个信封中的每个字母,你可以通过方法的数量增加结果集的总和。达到这一点。当您到达信封列表的末尾时,您将找到剩余0个字母的方式,这是您的答案。
在第二个例子中,可以按如下方式进行:
Step 0: next envelope 1
{1: 2, 2: 2, 3: 1}: 1
-> {1: 2, 2: 1, 3: 1}
-> {1: 2, 2: 2}
Step 1: next envelope 1
{1: 2, 2: 1, 3: 1}: 1
-> {1: 2, 2: 1}
-> {1: 2, 3: 1}
{1: 2, 2: 2}: 1
-> {1: 2, 2: 1}
Step 2: next envelope 2
{1: 2, 2: 1}: 2
-> {1: 1, 2: 1}
{1: 2, 3: 1}: 1
-> {1: 1, 3: 1}
-> {1: 2}
Step 3: next envelope 2
{1: 1, 2: 1}: 2
-> {2: 1}
{1: 1, 3: 1}: 1
-> {1: 1}
-> {3: 1}
{1: 2}: 1
-> {1: 1}
Step 4: next envelope 3
{2: 1}: 2
-> {}
{1: 1}: 2
-> {}
{3: 1}: 1
// Dead end.
Step 5:
{}: 4
这样可以使您了解所要求的计算范围。在15,你有2 ^ 15 = 32768个可能的子集来跟踪哪个是非常可行的。在20左右的某个地方你会开始耗尽内存。
我们可以改善吗?答案是我们可以。我们的绝大部分精力用于记住,比如说,到目前为止,我们是否使用了标有8的信封和标有9的信封。但我们并不关心这一点。决定完成方式的因素不在于我们是否使用了信封8或9.而是模式。有多少个标签有x个信封和y个字母。不是哪个标签,只有多少。
因此,如果我们跟踪这些信息,我们可以在每一步抓住一个信封,信封上留有最多的信封,如果有领带,我们会选择剩下最少字母的信封(和如果仍然领带,我们真的不关心我们得到的那个)。并像以前一样进行计算,但中间状态要少得多。 (我们不会随着你的信封排成一行。但是用信封对最后的信件做一个稳定的分类,然后你就会找回你上面的信件。)
让我们使用符号[x y]: z
来表示z
个带有x
个信封和y
个字母的标签。我们有一个此类标签的列表然后您的1 1 2 2 3
示例可以表示为{[2 2]: 2, [1 1]: 1}
。对于过渡,我们会使用其中一个[2 2]
标签,并使用另一个标签(转发给{[2 1]: 1, [1 2]: 1, [1 1]: 1}
)或者我们将其中一个[2 2]
标签,并使用[1 1]
标签中的字母(转换为{[2 2]: 1, [1 2]: 1, [1 0]: 1}
)。
让我们进行计算。我将列出状态,到达目的地的计数以及你所做的转变:
Step 1:
{[2 2]: 2, [1 1]: 1}: 1
-> 1 * {[2 1]: 1, [1 2]: 1, [1 1]: 1}
-> 1 * {[2 2]: 1, [1 2]: 1, [1 0]: 1}
Step 2:
{[2 1]: 1, [1 2]: 1, [1 1]: 1}: 1
-> 1 * {[1 1]: 3}
-> 1 * {[1 1]: 1, [1 2]: 1, [1 0]: 1}
{[2 2]: 1, [1 2]: 1, [1 0]: 1}: 1
-> 1 * {[1 2]: 1, [1 1]: 1, [1 0]: 1}
Step 3:
{[1 1]: 3}: 1
// This is 2x because once the label is chosen, there are 2 letters to use.
-> 2 * {[0 1]: 1, [1 0]: 1, [1 1]: 1}
{[1 1]: 1, [1 2]: 1, [1 0]: 1}: 2
-> 1 * {[1 0]: 1, [1 2]: 1, [0 0]: 1}
-> 1 * {[1 1]: 2, [0 0]: 1}
{[1 2]: 1, [1 1]: 1, [1 0]: 1}: 1
-> 1 * {[1 1]: 2, [0 0]: 1}
-> 1 * {[1 2]: 1, [1 0]: 1, [0 0]: 1}
Step 4:
{[0 1]: 1, [1 0]: 1, [1 1]: 1}: 2
-> 1 * {[1 1]: 1, [0 0]: 2}
-> 1 * {[1 0]: 1, [0 1]: 1, [0 0]: 1}
{[1 0]: 1, [1 2]: 1, [0 0]: 1}: 2
-> 1 * {[1 1]: 1, [0 0]: 2}
{[1 1]: 2, [0 0]: 1}: 2
-> 1 * {[1 0]: 1, [0 1]: 1, [0 0]: 1}
Step 5:
{[1 1]: 1, [0 0]: 2}: 4
// dead end
{[1 0]: 1, [0 1]: 1, [0 0]: 1}: 4
-> 1 * {[0 0]: 3}
所以答案是4。
这可能看起来像是一项疯狂的工作 - 远远超过枚举。它就是!
然而它会扩展。尝试使用100个字母和信封,它应该快速运行。
答案 1 :(得分:0)
15“正常”排列需要1.3 * 10 ^ 9次强力尝试,但是使用相同的键我们的排列要少得多。
让我们使用你的例子:5张牌1 1 2 2 3
设置排列数组
5 4 3 2 1
并准备通过从右到左递减来迭代它 像一个数字。忽略所有非排列的组合。
5 4 3 2 1忽略
5 4 3 2 0忽略
5 4 3 1 5忽略
...
5 4 3 1 2好
相比之下,通过更好的排列可以大大改善这一点 算法,但这不容易,我必须考虑一下。
下一步是生成您的比较。 置换数组选择一个置换:
5 4 3 1 2表示3 2 2 1 1
这将测试紊乱。有一件事会大大加快 您的比较将是您可以跳过无效组合。
如果开头5选择了错误的项目,则可以全部跳过 5 X X X X排列并继续4 5 3 2 1。
每次发现紊乱时,请增加一个计数器。 完成。