从1-50的生成器生成1-100的随机数

时间:2013-01-02 14:49:24

标签: java optimization random

在最近的一次采访中,我被问到以下问题:

  

使用给定的getrnd50()方法打印1-100中的随机数   从1-50生成随机数。每个随机数   只能以随机顺序打印一次。不使用其他随机数发生器   是允许的,我不被允许改变的定义   getrnd50()

我想出了以下代码,它提供了正确的输出。

import java.util.Random;

public class Test {

public static void main(String[] args) {
    int[] rs = new int[100];
    int count = 0;
    int k;
    while (count != 100) {

        // I decided to simply multiply the result of `getrnd50()` by 2. 
        // But since that would generate only even numbers,

        k = getrnd50() * 2;

        // I decided to randomly subtract 1 from the numbers. 
        // Which i accomlished as follows.          

        if (getrnd50() <= 25) // 25 is to half the possibilities.
            k--;

        // Every number is to be stored in its own unique location 
        // in the array `rs`, the location is `(number-1)`. 
        // Every time a number is generated it is checked whether it has 
        // already been generated. If not, it is saved in its position, printed and 
        // `count` is incremented.

        if (rs[k-1] == 0) {
            rs[k-1] = k;
            count++;
            System.out.print(k + " ");
        }
    }
}
// This is the method and i am not supposed to touch it.
static int getrnd50() {
    Random rand = new Random();
    return (1 + rand.nextInt(50));
}

}

虽然在那一轮被接受,但在下一轮中,面试官告诉我getrnd50()是一种代价高昂的方法,即使在最好的情况下,我也必须为每个生成的数字调用两次。即1-100的200次。在最坏的情况下,它将是无限的,平均情况下数万。他要求我优化代码,以便显着改善平均情况。

当他表示我无法做到这一点时,他给了我一个提示,他说:

  

考虑生成新数据时生成的数字   数。对于前者如果count变为99,我就不必致电getrnd50()我   可以简单地找到剩余的数字并打印出来。

虽然我理解他的漂移我不知道它会如何帮助我,所以显然我被拒绝了。现在我很想知道答案。帮我! Thanx提前!

注意:如果有人懒得写一个冗长的代码只是指出数字生成部分,其余的很容易。我们也不一定要遵循这个提示。

7 个答案:

答案 0 :(得分:6)

关键是不要检查你之前是否已经生成了这个数字,当只查找剩余数字时会变得非常昂贵,但要按顺序生成数字1-100,然后随机播放。

在你的代码中,当你在100个数字中生成99个时,你将循环,生成随机数,直到你找到剩下的1个数字。这就是为什么你的版本中的平均情况如此糟糕。

如果只是改组数组,你只需要拥有与随机数一样多的随机数,并且只需要与你需要数字输出一样多的随机数。

(有关改组的详细信息,请查看Fisher-Yates shuffle,特别是可以生成混洗数组的由内到外的变体。

要生成随机数,您需要一个变量生成器,而不是固定的1-50。您可以通过各种方式处理此问题,但如果您真的希望输出在可能的状态中具有良好的分布,请务必小心地将结果引入偏差。

例如,我建议使用整数位,并使用移位,而不是尝试使用模数。如果值超出了所需的范围,这确实涉及一定量的循环,但是如果不能修改原始的随机数生成,那么你的手就会受到一些限制。

static int bits = 0;
static int r100 = 0;

static int randomInt(int range)
{
    int ret;

    int bitsneeded = 32 - Integer.numberOfLeadingZeros(range - 1);

    do {
            while(bits < bitsneeded)
            {
                    int r = (getrnd50()-1) * 50 + getrnd50()-1;
                    if(r < 2048)
                    {
                            r100 <<= 11;
                            r100 |= r;
                            bits += 11;
                    }
            }
            ret = r100 & ((1 << bitsneeded) - 1);
            bits -= bitsneeded;
            r100 >>=  bitsneeded;
    } while(ret >= range); 

        return ret + 1;
}

此实现将使用150个随机数区域中的某些值作为100值混洗数组。这比模数版本更差,但优于输入范围的2倍,这是原始版本的最佳情况。如果随机生成是真正随机的,那么仍然是无穷大的最坏情况,但随机生成通常不会那样工作。如果确实如此,我不确定在给定约束的情况下,未经证实的结果是否切合实际。

为了说明,由于结果很微妙,这里是我建议的随机例程与模数版本的图表:

Graph of random generators

总而言之,我认为虽然你的随机生成效率有点低,并且可以改进,但是面试官正在寻找的真正大赢家,首先不需要这么多随机数,随着概率不断下降而不是重复搜索。

答案 1 :(得分:3)

由于100/50是一个整数,这很容易。由于50 /(100/50)是一个整数,因此更容易。

如果你没有那么做,这里有一些示例代码:

int rnd1 = getrnd50();
int rnd2 = getrnd50();
if (rnd1 % 2 == 0)
{
    rnd2 += 50;
}
return rnd2;

这是一个大纲:

  • 两个数字,在1到50之间随机选择,称为 a b
  • 如果 a 是偶数,请将<50>添加到 b
  • 返回 b

如果您愿意,可以将其设为单行:

return getrnd50() + getrnd50() % 2 * 50;

但这有点太混淆了。

编辑:我看到问题确实是要求一个混洗列表,而不是一系列随机整数。

这可以通过创建1到100的列表,并进行100次随机交换来完成,例如Fisher-Yates shuffle。我想通过Fisher-Yates shuffle,绝对最小的调用次数是93(用公式ceil(log50(100!))给出),但是使用更简单的算法可以使用200。

简单算法将涉及使用100中的随机元素交换100个元素中的每个元素。选择的数字将使用上述生成器从1-100生成。

例如:

for (int i = 0; i < 100; i++)
{
    swap(i, getrnd100() - 1); // - 1 for zero base!
}

以下是一些完整的代码:

int[] result = new int[100];
for (int i = 0; i < 100; i++)
{
    result[i] = i + 1;
}
for (int i = 0; i < 100; i++)
{
    int j = (getrnd50() + getrnd50() % 2 * 50) - 1;
    int tmp = result[i];
    result[i] = result[j];
    result[j] = tmp;
}
return result;

(免责声明:我不了解Java,我还没有测试过。)

最佳案例200,最差案例200,平均案例200。

答案 2 :(得分:3)

以下是您可以回答的问题。它利用了这样一个事实,

  • 假设您正在使用shuffle来进行O(n)交换“卡片”,模数会随着时间的推移而减少。即从每个值的int[]开始,并像Collections.shuffle()那样将其洗牌。
  • 如果你两次调用getrnd50(),你的随机性比你需要的要多,尤其是当你剩下少于50个值要交换时。

编辑:对于那些不熟悉shuffle工作的人,我已经添加了改组代码

import java.util.*;
import java.lang.*;

class Main {
    public static void main(String... args) {
        int samples = 100;

        // all the numbers [1, 100]
        int[] nums = new int[samples];
        for (int i = 0; i < samples; i++) nums[i] = i + 1;

        for (int i = samples - 1; i > 0; i--) {
            int swapWith = nextInt(i + 1);

            // swap nums[i] and nums[swapWith]
            if (swapWith == i) continue;
            int tmp = nums[swapWith];
            nums[swapWith] = nums[i];
            nums[i] = tmp;
        }
        System.out.println("calls/sample " + (double) calls / samples);
        System.out.println(Arrays.toString(nums));

        int[] count49 = new int[49];
        for (int i = 0; i < 49 * 10000; i++)
            count49[nextInt(49) - 1]++;
        int[] count54 = new int[54];
        for (int i = 0; i < 54 * 10000; i++)
            count54[nextInt(54) - 1]++;
        System.out.println("Histogram check (49): " + Arrays.toString(count49));
        System.out.println("Histogram check (54): " + Arrays.toString(count54));

    }

    // keep track of the range of values.
    static int maxRandom = 1;
    // some random value [0, maxRandom)
    static int rand100 = 0;

    static int nextInt(int n) {
        while (maxRandom < 10 * n * n) {
            maxRandom *= 50;
            rand100 = rand100 * 50 + getrnd50() - 1;
        }
        int ret = rand100 % n;
        maxRandom = (maxRandom + n - 1) / n;
        rand100 /= n;
        return ret + 1;
    }

    static final Random rand = new Random();
    static int calls = 0;

    static int getrnd50() {
        calls++;
        return (1 + rand.nextInt(50));
    }
}

打印

来电/样品0.94

[1,37,4,98,76,53,26,55,9,78,57,58,47,12,44,25,82,2,42,30,88,81,64, 99,16,28,34,29,51,36,13,94,80,66,19,38,20,8,40,89,72,56,75,96,35,100,95,17, 74,69,11,31,86,92,6,27,22,70,63,32,93,84,71,15,23,5,14,62,49,43,87,65,83, 33,45,52,39,91,60,73,68,24,97,46,50,18,79,48,77,67,59,10,7,54,90,85,21,61, 41,3]

直方图检查(49):[10117,10158,10059,10188,10338,9959,10313,10278,10166,9828,10105,10159,10250,10152,9949,9855,10026,10040,9982,10112, 10021,10082,10029,10052,9996,10057,9849,9990,9914,9835,10029,9738,9953,9828,9896,9931,9995,10034,10067,9745,9873,9903,9913,9841,9823, 9859,9941,10007,9765]

直方图检查(54):[10124,10251,10071,10020,10196,10170,10123,10096,9966,10225,10262,10036,10029,9862,9994,9960,10070,10127,10021,10166, 10077,9983,10118,10163,9986,9988,10008,9965,9967,9950,9965,9870,10172,9952,9972,9828,9754,10152,9943,9996,9779,10014,9937,9931,9794, 9708,9978,9894,9803,9904,9915,9927,10000,9838]

在这种情况下,100个号码需要少于100次调用getrnd50

如果您有1000个值要随机播放

calls/sample 1.509

答案 3 :(得分:0)

代码的性能损失在该行

if (getrnd50() <= 25)

您需要找到一种方法从该单个生成的随机数中获取更多信息,否则您将浪费那些昂贵的生成资源。以下是我的建议:

首先想象我们会为数字0-15设置一个随机数生成器。每个数字都可以表示为二叉树中的路径,其中叶子表示数字。所以我们可以说,当我们从根处开始向左走时,我们会评估条件为true

问题在于随机数生成器在一个不以2的幂结束的间隔中生成数字。所以我们需要扩展那棵树。这样做是这样的:
如果随机数在0-31范围内,我们可以使用这些数字的树。如果它在32-47范围内,我们使用0-15中的树作为那些树,在48-49中我们使用树来表示数字0-1。

所以在最坏的情况下,我们并没有使用来自该随机数的更多信息,但在大多数情况下我们都是。所以这应该会显着改善平均情况。

答案 4 :(得分:0)

好的,所以你可以打印最后一个缺少n个数字的数字,而不是由随机数生成器生成的吗?

如果是这样,你可以使用递归并减少每次调用的集合大小,直到你只有n = 2然后调用getrnd50()一次。当你以递归方式返回时,只需在每一组上打印缺失的数字。

答案 5 :(得分:0)

 List<Integer> lint ; 

  public void init(){
      random = new Random();
      lint = new LinkedList<>();
      for(int i = 1 ; i < 101; ++i) {
          lint.add(i); // for truly random results, this needs to be randomized.
      }
  }

  Random random ; 
  public int getRnd50() {
      return random.nextInt(50) + 1;
  }

  public int getRnd100(){

      int value = 0;
      if (lint.size() > 1) {
          int index = getRnd50()%lint.size();
          value = lint.remove(index); 
      } else if (lint.size() == 1 ) {
          value = lint.remove(0);
      }      

      return value;      
  }

将getRnd50()调用99次。它并非真正随机,因为存储在100个整数列表中的数字是顺序的。

答案 6 :(得分:0)

(1)创建一个用{1,...,100}初始化的数组A.保持此数组的变量“长度”。

(2)创建一个随机方法,从1到长度随机生成一个数字。每次调用此方法都会调用getrnd50()不超过2.将返回值称为'index'。

(3)输出A [指数],交换A [长度]为A [指数]和长度 - 。

(4)重复(1) - (3)直到数组为空。