在最近的一次采访中,我被问到以下问题:
使用给定的
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提前!
注意:如果有人懒得写一个冗长的代码只是指出数字生成部分,其余的很容易。我们也不一定要遵循这个提示。
答案 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倍,这是原始版本的最佳情况。如果随机生成是真正随机的,那么仍然是无穷大的最坏情况,但随机生成通常不会那样工作。如果确实如此,我不确定在给定约束的情况下,未经证实的结果是否切合实际。
为了说明,由于结果很微妙,这里是我建议的随机例程与模数版本的图表:
总而言之,我认为虽然你的随机生成效率有点低,并且可以改进,但是面试官正在寻找的真正大赢家,首先不需要这么多随机数,随着概率不断下降而不是重复搜索。
答案 1 :(得分:3)
由于100/50是一个整数,这很容易。由于50 /(100/50)是一个整数,因此更容易。
如果你没有那么做,这里有一些示例代码:
int rnd1 = getrnd50();
int rnd2 = getrnd50();
if (rnd1 % 2 == 0)
{
rnd2 += 50;
}
return rnd2;
这是一个大纲:
如果您愿意,可以将其设为单行:
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)
以下是您可以回答的问题。它利用了这样一个事实,
int[]
开始,并像Collections.shuffle()那样将其洗牌。编辑:对于那些不熟悉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)直到数组为空。