当允许重复时,如何获得第n个排列?

6

Project Euler 24: 按字典顺序排列的数字0、1、2、3、4、5、6、7、8和9的第100万个排列是什么?

如果允许重复,如11111111111223344457等,我该如何获得包括重复在内的第100万个排列。

请注意,输入仍然相同。输入中没有重复。

我想生成长度为10的所有可能密码。密码可以包含重复字符,因此我希望我的函数也适用于这种情况。

以下是一个给出字符串的第n个排列的代码。它利用了这样一个事实:对于n个元素,有n!种排列。在字典排序的排列中,前(n-1)!个排列以第一个数字开头,依此类推。

我该如何修改这个代码来获得包含重复字符的字符串?是否有任何特定的算法可以使用?

澄清一下,我不仅需要第100万个排列。我需要所有可能的排列。我可以通过在此函数上运行for循环来获取不重复的所有排列。但我无法获得带有重复的排列。我想要带有重复的排列。因为我想要获取所有可能的密码。如果只允许数字,想象一下你可以拥有的所有10个字母密码的可能性。10^10。我想要它们全部。

import java.util.*;

public class NthPermutation{

    private static int Factorial(int n){  
        if (n < 0)
            return 0;
         int ans = 1;
         for (int i=1;i<=n;++i)
            ans *= i;
         return ans;
     }

     public static String getNth(List<Integer> original, int permNum){

         List<Integer> numbers = new ArrayList<Integer>(original);  
         String nth = "";
         permNum--;
         int N = numbers.size();  

         for (int i=1;i<N;++i){
             int j = permNum / Factorial(N - i); 
             permNum = permNum % Factorial(N - i);
             nth = nth + numbers.get(j);
             numbers.remove(j);

         if (permNum==0)
             break;
         }

         for (int i=0; i<numbers.size();i++)
             nth = nth + numbers.get(i);

         return nth;
      }

      public static void main(String[] args){

          List<Integer> numbers = new ArrayList<Integer>();     
          for (int i = 0; i < 10; i++) 
              numbers.add(i);

          System.out.println(getNth(numbers,1000000)); 
       }
}

除非我误解了,否则您不应考虑重复。 - Atuos
@Atuos 我想生成所有可能的密码。而且密码可以重复。 - user3834119
是的,它可以。我使用列表 [1,1,2,3] 尝试了您的程序:排列 #1 输出 1,1,2,3;排列 #2 输出 1,1,3,2;排列 #24 输出 3,2,1,1 - גלעד ברקן
请阅读修改后的问题,之前没有表述清楚,抱歉。我不想在输入中重复。我想给出相同的输入,输出应该生成带有重复的排列。现在我希望我的意思已经表达清楚了。 - user3834119
3个回答

2
如果允许重复,那么:
  • 第一个排列是 0000000000
  • 第二个排列是 0000000001
  • 第十个排列是 0000000009
  • 第一百个排列是 0000000099
  • 第一千个排列是 0000000999
  • 第一百万个排列是 0000999999
等等。 所有这些都只是数字n-1在左边填充足够多的零以使整个字符串长度为10。 因此,要获取实际的第n个组合,您只需要执行以下操作(下面的Python代码片段,您可以轻松转换为Java):
>>> def find_nth_combination(n):
...     print "0" * (10-len(str(n-1))) + str(n-1)
... 
>>> find_nth_combination(1)
0000000000
>>> find_nth_combination(100)
0000000099
>>> find_nth_combination(9062300000)
9062299999
>>> find_nth_combination(12300000)
0012299999

如果您想通过重复来解决这个问题,您可以在这里看一下(代码使用Python编写)。
要获取所有排列,只需遍历所有数字。
因此,您需要执行类似以下操作:
for x in xrange(1, 1001):
    find_nth_combination(x)

这将输出:
0000000000
0000000001
...
...
0000000997
0000000998
0000000999

这完全不对:000000000001根本不是排列,因为排列应该恰好包含0-9的所有数字。 - Neithrik
2
@Riko 好吧,OP确实期望输出包括1111111111,那你怎么解释呢?不过你说得对,这不是技术上的排列,但问题本身就是无效的。 - Anshul Goyal

2
我们需要先了解阶乘数系统(或因子数系统)来解决这个问题。阶乘数系统使用阶乘值而不是数字的幂(二进制系统使用2的幂,十进制使用10的幂)来表示位值(或基数)。
位值(基数)为 -
5!= 120 4!= 24 3!= 6 2!= 2 1!= 1 0!= 1 等等。 零位上的数字始终为0。第一位上的数字(基数=1!)可以是0或1。第二位上的数字(基数=2!)可以是0、1或2,依此类推。一般来说,第n位上的数字可以取任何值在0-n之间。
前几个用因子数表示的数字为 -
0 -> 0 = 0*0!
1 -> 10 = 1*1! + 0*0!
2 -> 100 = 1*2! + 0*1! + 0*0!
3 -> 110 = 1*2! + 1*1! + 0*0!
4 -> 200 = 2*2! + 0*1! + 0*0!
5 -> 210 = 2*2! + 1*1! + 0*0!
6 -> 1000 = 1*3! + 0*2! + 0*1! + 0*0!
7 -> 1010 = 1*3! + 0*2! + 1*1! + 0*0!
8 -> 1100 = 1*3! + 1*2! + 0*1! + 0*0!
9 -> 1110
10-> 1200

字符串的第n个字典序排列与其因子分解表示之间存在直接关系。

例如,这是字符串“abcd”的排列。

0  abcd       6  bacd        12  cabd       18  dabc
1  abdc       7  badc        13  cadb       19  dacb
2  acbd       8  bcad        14  cbad       20  dbac
3  acdb       9  bcda        15  cbda       21  dbca
4  adbc       10  bdac       16  cdab       22  dcab
5  adcb       11  bdca       17  cdba       23  dcba

我们可以仔细观察这个模式。第一个字母在每个第六个(3!)排列后改变。第二个字母在2(2!)排列后改变。第三个字母在每次(1!)排列后改变,第四个字母在每次(0!)排列后改变。我们可以利用这个关系直接找到第n个排列。
一旦我们将n表示为factoradic表示法,我们考虑其中的每个数字,并从给定的字符串中添加一个字符到输出中。如果我们需要找到“abcd”的第14个排列。14的factoradics是->2100。
从第一个数字开始-> 2,字符串是'abcd'。假设索引从0开始,在字符串中取第2个元素并将其添加到输出中。
Output                    String
  c                         abd
  2                         012

下一个数字 -> 1。字符串现在是 'abd'。再次取出位置为1的字符并将其添加到输出中。
Output                    String
 cb                         ad
 21                         01

下一个数字 -> 0。字符串为'ad'。将位置1处的字符添加到输出中。
Output                   String
 cba                        d
 210                        0

下一个数字 -> 0。字符串为 'd'。将位置0处的字符添加到输出中。
Output                   String
 cbad                      ''
 2100

将给定的数字转换为阶乘数系统,需要将该数字连续地除以1、2、3、4、5等,直到商为零。每一步的余数构成其因子表示。

例如,要将349转换为阶乘数系统,

              Quotient        Remainder        Factorial Representation

349/1            349               0                             0
349/2            174               1                            10
174/3            58                0                           010
58/4             14                2                          2010
14/5             2                 4                         42010
2/6              0                 2                        242010

349的 Factoradic 表示是242010。


哦,我不知道是否应该给你点赞。你的帖子实际上回答了问题,但是提问者似乎使用排列这个术语有误(因为他谈到了重复)。此外,这个问题是两年前提出的。非常遗憾,因为你的答案很好,但我认为没有人会通过搜索找到它。编辑:我给你点赞,因为你的回答是这里唯一真正的排列答案。 - Aziuth
几年前我在一次面试中被问到了这个问题。面试官很有耐心地听我在面试中推导。虽然我没有得到那份工作,但后来我完成了我的白板代码。现在这个问题和维基百科的文章让它变得更加清晰了。https://en.wikipedia.org/wiki/Factorial_number_system 现在我想再试一次那个面试,因为我知道它只是简单地通过递增的数字进行除法并保存余数。 - No Refunds No Returns

0

无重复:

由于您只需要第1000000000个排列,因此可以使用暴力方法解决此问题。从字符串“0123456789”开始,并使用C++的next_permuation等效函数,您可以在此处获取函数:http://codeforces.ru/blog/entry/3980。只需迭代一百万次,您就会得到解决方案。

还有一种更高级的贪心解决方案,运行非常快,您可以在此处阅读有关它的信息:https://math.stackexchange.com/questions/60742/finding-the-n-th-lexicographic-permutation-of-a-string

有重复:

由于这个问题等同于仅取数字并在开头填充0,因此使用重复非常简单。

1st permuation: 000000001
15th permuation 000000015
etc.

2
我不仅需要第1000000000个排列,我需要所有可能的排列。我的代码可以给出所有没有重复的排列。现在我想要有重复的排列,因为我想得到所有可能的密码。想象一下如果只允许数字,你可以拥有10个字母的所有可能密码。10^10。我想要它们全部。 - user3834119
我的两个答案都可以用来生成它们。在第一种情况下,只需从“0123456789”开始迭代即可,在第二种情况下使用我写的内容 :) 希望这可以帮助到您。 - Neithrik
2
我不理解第二部分,即“WITH REPETITIONS”。你能进一步解释一下吗?或者给我提供一些有用的链接来阅读。我不明白我们如何能够涵盖所有排列组合,例如1122334455、1111000000等。 - user3834119
如果你想生成所有的10位数字组合,你需要迭代这个数字范围 [0-9999999999]。一旦你完成了这个过程,你可以简单地用缺少的0的数量来填充每个数字,使其长度达到10位数字。我们使用0进行填充,因为在一个数字的整数表示中,它们被省略(无意义),数字中的其他每个数字都是有意义的。 - Anshul Goyal
但这样不会很有效率,对吧?我将一千万个密码分成十份,让十台电脑同时运行程序,以提高效率。第十台电脑只需要计算从九百万到一千万的排列组合,而不是遍历所有数字[0-9999999999],这样才是最有效率的。 - user3834119
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接