生成一个包含N个唯一的随机数的数组,使得所有数字相加等于N。

3
假设N = 3,我想编写一个函数来生成3个唯一的随机数,如果所有数字相加等于3。例如:
numbers = [1, 0, 2]
numbers = [2, -4, 5]

我已经有自己的JavaScript解决方案,如下所示:
let i = 0;
let arr = []

function getRandomInt(number) {
  return Math.floor(Math.random()*(number*2)) - number;
}

function generateArray(i, arr, number) {
  let lastIndex = number-1;

  while (i < lastIndex) {
    let randomNumber = getRandomInt(number);
    if (arr.indexOf(randomNumber) > -1) {
      continue;
    } else {
      arr[i] = randomNumber;
    }
    i++;
  }

  let summed = arr.reduce((a, b) => a+b);
  let lastNumber = number - summed;
  if (arr.indexOf(lastNumber) > -1) {
    return generateArray(lastIndex-1, arr, number);
  } else {
    arr[lastIndex] = lastNumber;
    return arr;
  }
}

但是我仍然有一个问题,最后一个索引往往会偏离得相当大。例如,当N = 10时,我可能会得到如下结果:

numbers = [2, -1, 3, 4, -4, 0, -5, -8, -6, 15] 

我想知道你们是否有更好的解决方案,同时性能更好。谢谢!


@ScottMarcus 我认为这是一个很好的问题 - codereview 不接受需要调试的代码(比如这个),而且 OP 听起来像是在寻求一个全新(更好)的算法来解决问题,这也听起来对于 codereview 来说太过宽泛。 - CertainPerformance
@CertainPerformance 实际上,基于你提到的所有原因,它绝对属于代码审查。它对于SO来说太过广泛,也没有真正提出任何具体问题。 - Scott Marcus
3
这个问题没有问题。我不支持任何进一步的关闭投票。 - Geuis
2
仅澄清一下,Insan,您是在说当N = 10时,数组的所有值都应该加起来等于10吗? - Geuis
随机数的期望范围是什么? - Nina Scholz
显示剩余9条评论
2个回答

1
这是一段代码片段,首先用范围在-N到N之间的N个唯一数字填充数组。
然后将数组中的最后一个值替换为总和=N的值。
如果重新计算的最终值已经存在于数组中,则函数会进行递归操作以避免最后一个值不唯一。
当最终值偏离太多时,它也会递归。

function getArrayRandomNumbersInRange(min, max, N) {
   let arr = [];

   while(arr.length < N){
      let num = Math.floor(Math.random() * (max - min + 1)) + min;
      if(arr.indexOf(num) > -1) continue;
      arr[arr.length] = num;
   }
   let total = arr.reduce(function(accum, val) {return accum + val});
   let lastElem =  arr[arr.length-1] - total + N;
   if(lastElem < min || lastElem > max || (total !== N && arr.indexOf(lastElem) > -1)) {
          //console.log(lastElem + ' -> recurse');
          arr = [];
          return getArrayRandomNumbersInRange(min, max, N);
   }
   arr[arr.length-1] = lastElem;
   return arr;
}

function getArrayRandomNumbers(N){
   return getArrayRandomNumbersInRange(-N, N, N);
}

function sumArray(arr){
   return arr.reduce(function(accum, val) {return accum + val})
}

let randomUniqueArray = getArrayRandomNumbers(5);

console.log("Total:\t" + sumArray(randomUniqueArray));
console.log(randomUniqueArray);


1

编辑:(我再想了一下,并且认为我找到了一种更好的方法,如下所述)

这是我想出来的方法。它使列表看起来更随机,虽然可能会有一些元素比其他元素偏离更多,但是偏离的元素可能来自任何索引。它只需要一个小改变。

你原本有:

let randomNumber = getRandomInt(number);

替换此处为:
let randomNumber = getRandomInt(number) + i - currentSum;

其中currentSum是数组的滚动总和,而i是一个递增的变量,从零开始每次通过时递增一次,两者都需要在while循环的else块中更新。 (换句话说,这将替换您作为此项将跟踪数组生成随机数字时的总和的summed变量)。此更改旨在使随机数归一化,以使总和不会远离所期望的滚动值。要将n个数字求和加到n中,“平凡”的解决方案是使每个数字都为1(即滚动总和仅为数组的索引)。上述代码更改所做的就是创建一个随机数,该随机数在我刚才描述的预期滚动总和周围生成随机数。因此,如果我运行代码一百万次,则数组中每个值的平均值将为1,这非常适合您所描述的列表。我在Java中快速测试了这种方法,并且似乎可以实现您想要的效果,因此希望这个“快速修复”有所帮助。

另一个想法(我没有测试过)进一步减少偏差的方法是,在上述更改的基础上,使generateRandomInt()函数生成较小范围内的数字。目前,该函数生成的数字范围为2 * number,这可能会产生比所需更大的数字。
以下是当我运行更改后的代码时得到的一些测试数组(number = 10):
[-3, 10, 0, -4, -1, 6, -5, 5, -7, 9]
[-6, -2, 4, 6, -8, 8, 3, -4, 7, 2]
[-2, 4, -10, 1, 6, 13, -3, -6, 12, -5]

我希望你能理解这个想法;希望这有所帮助!
附言:我认为你发布的代码应该将 ++ 命令放在else块内,否则你可能无法填满整个数组。

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