编写一种方法,每次调用时都会输出一个不同的uniqe排列数

时间:2017-11-01 19:47:53

标签: java algorithm random hashtable permutation

我收到了这个面试问题,我仍然对它很困惑。 问题就像标题所示,我会解释。

您将获得一个随机创建功能。

函数输入是整数n。假设我用3来称呼它。

它应该给我1到3的数字的排列。所以例如它会给我2,3,1。

我再次调用该函数后,它不会给我相同的排列,现在它会给我1,2,3,例如。

现在,如果我打电话给n = 4.我可能会得到1,4,3,2。

再次调用3将不会输出2,3,1或1,2,3,因为之前已经输出了,它会给出3个不同的排列!可能的排列。

我对这个问题感到困惑,现在我还是。在正常运行时间内如何实现?在我看来,必须有一些静态变量来记住在函数完成执行之前或之后调用的内容。 所以我的想法是创建一个静态哈希表(键,值),它将输入作为键,值是n!的长度数组。 然后我们使用random方法从这些实例中输出一个随机实例并将此实例移到后面,这样它就不会被再次调用,从而使输出保持唯一。

时空复杂性对我来说似乎很大。 我在这个问题上遗漏了什么吗?

2 个答案:

答案 0 :(得分:1)

您可以将静态变量存储为下一个排列的种子

在这种情况下,我们可以使用int更改每个数字将放入哪个插槽(例如,这是硬编码为4个数字的集合)

private static int seed = 0;

public static int[] generate()
{
    //s is a copy of seed, and increment seed for the next generation
    int s = seed++ & 0x7FFFFFFF; //ensure s is positive
    int[] out = new int[4];

    //place 4-2
    for(int i = out.length; i > 1; i--)
    {
        int pos = s % i;
        s /= i;
        for(int j = 0; j < out.length; j++)
            if(out[j] == 0)
                if(pos-- == 0)
                {
                    out[j] = i;
                    break;
                }
    }

    //place 1 in the last spot open
    for(int i = 0; i < out.length; i++)
        if(out[i] == 0)
        {
            out[i] = 1;
            break;
        }

    return out;
}

这是一个将大小作为输入的版本,并使用HashMap存储种子

private static Map<Integer, Integer> seeds = new HashMap<Integer, Integer>();

public static int[] generate(int size)
{
    //s is a copy of seed, and increment seed for the next generation
    int s = seeds.containsKey(size) ? seeds.get(size) : 0; //can replace 0 with a Math.random() call to seed randomly
    seeds.put(size, s + 1);
    s &= 0x7FFFFFFF; //ensure s is positive
    int[] out = new int[size];

    //place numbers 2+
    for(int i = out.length; i > 1; i--)
    {
        int pos = s % i;
        s /= i;
        for(int j = 0; j < out.length; j++)
            if(out[j] == 0)
                if(pos-- == 0)
                {
                    out[j] = i;
                    break;
                }
    }

    //place 1 in the last spot open
    for(int i = 0; i < out.length; i++)
        if(out[i] == 0)
        {
            out[i] = 1;
            break;
        }

    return out;
}

此方法有效,因为种子存储了要放置的每个元素的位置

尺寸4:

  1. 获得基数为4的最低位数,因为剩余4个插槽
  2. 在该位置放置4
  3. 移动数字以删除使用的数据(除以4)
  4. 获得基数3中的最低位数,因为剩余3个插槽
  5. 在该位置放置3
  6. 移动数字以删除使用的数据(除以3)
  7. 获得基数2中的最低位数,因为剩余2个插槽
  8. 在该位置放置2
  9. 移动数字以删除使用的数据(除以2)
  10. 只剩下一个插槽
  11. 在该位置放置1
  12. 此方法可扩展至12个!对于整数,13!溢出,或20!为多头(21!溢出)

    如果您需要使用更大的数字,您可以使用BigInteger s替换种子

答案 1 :(得分:1)

Jonathan Rosenne的答案被低估了,因为它只是链接,但在我看来,它仍然是正确的答案,因为这是一个众所周知的问题。您还可以在维基百科中看到最小的解释:https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order

为了解决您的空间复杂性问题,在词典排序中生成排列具有O(1)空间复杂度,除了当前排列之外,您不需要存储任何其他内容。该算法非常简单,但最重要的是,它的正确性非常直观。想象一下,您拥有所有排列的集合,并按字典顺序排序。按顺序前进到下一个然后循环返回将为您提供最大循环而不重复。这个问题同样是空间复杂性,因为你需要存储所有可能的排列;该算法为您提供了一种获取下一个排列而无需存储任何内容的方法。可能需要一段时间才能理解,但是一旦我明白它就会很有启发性。