js生成不重复的随机数

今天在做蓝桥杯模拟赛题目的时候看到了这个题目,特此做个记录,主要就是利用的递归还有判断语句就能实现

const getRandomNum = function (min, max, countNum) {
  let arr = [];
  // 在此处补全代码
  function produceNum(){
    let num=Math.floor((Math.random()*(max-min))+min)
    if(arr.indexOf(num)==-1){ //判断不存在就push
      arr.push(num)
    }else{
      produceNum() //存在了重新调用
    };
    if(arr.length<countNum){ //判断数组长度是否小于所需长度
      produceNum();
    }
  }
  produceNum()
  return arr;
};

然后在看到了一个朋友的代码,觉得更加方便,也再次记录

function produceNum(){
  let arr=[];
  for(let i=0;i<arr.length;i++){
    let num=Math.floor((Math.random()*(max-min))+min);
    if(arr.indexOf(num)==-1){ //不存在推入
      arr.push(num);  
    }else{
      i--; //存在了使i-1增加一次循环次数
    }
  }
  return arr;
}

<think> 我们被要求生成重复随机数。注意“永重复”意味着在生成随机数序列中,每个数字只出现一次,且后续生成的数字会与之前生成的任何数字重复。这通常需要一个足够大的范围,并且需要记录已经生成的数字。 但是,根据引用的内容,有几种方法可以实现生成重复随机数: 1. 方法一(引用[1]和[3]):使用循环和数组检查。每次生成一个随机数,检查它是否已经在数组中,如果在则加入,直到数组达到指定长度。 2. 方法二(引用[2]):同样使用循环和数组检查,但封装成函数,使用indexOf或includes检查是否重复。 3. 方法三(引用[4]):使用数组随机排序。首先生成一个顺序数组,然后对这个数组进行随机排序,这样得到的就是一个重复的随机序列。 注意:方法一和方法二在范围较小而需要生成的数字数量接近范围大小时,效率会降低(因为越到后面,生成重复数字的概率越高)。方法三的效率较高,因为它只进行一次随机排序。 然而,用户要求的是“永重复”,这意味着可能需要一个非常长的序列,甚至超过了一个固定范围(比如从1到100)的情况。如果要求在一个固定范围内生成重复随机数,那么生成的数量能超过范围的大小(即max-min+1)。如果超出,则无法生成重复随机数(如引用[3]中的错误处理)。 但是,如果用户希望在一个非常大的范围(比如整个整数范围)内生成重复随机数,那么我们需要考虑使用同的方法,比如使用随机数生成器(如线性同余生成器)并记录状态,或者使用加密学安全的伪随机数生成器,但这通常用于生成重复序列,因为随机数本身就有重复的可能。 实际上,我们通常理解的重复随机数是在一个有限范围内生成所有数字的随机排列(即每个数字恰好出现一次)。如果要求无限长的重复序列,那么需要确保每个数字只出现一次,这通常需要无限大的范围(例如,使用递增序列加上随机置换,或者使用足够大的随机数空间如UUID,但UUID是数字)。 根据问题,我假设用户需要的是在一个固定范围内生成重复随机数序列,且序列长度超过范围的大小。 因此,我们可以提供以下方法: 方法1:使用洗牌算法(即方法三的改进) - 首先生成一个顺序数组,包含指定范围内的所有整数。 - 然后对这个数组进行随机排序(洗牌)。 - 这样得到的数组就是该范围内的重复随机数序列。 方法2:使用集合(Set)来避免重复(类似于方法一和方法二,但使用Set提高检查效率) - 创建一个空集合。 - 循环生成随机数,如果随机数在集合中,则加入集合,直到集合大小达到要求。 方法1在需要整个范围的随机排列时效率最高,方法2在需要部分随机数时可能更节省内存(当范围很大而只需要少量随机数时),但如果需要接近整个范围的随机数,方法2的效率会急剧下降。 考虑到用户要求“永重复”,如果用户需要生成随机数数量很大(接近整个范围),推荐使用方法1;如果用户需要生成随机数数量远小于范围大小,则方法2更合适。 下面我们将分别实现这两种方法,并给出示例代码。 注意:我们无法真正生成无限重复随机数(因为随机数的范围有限,且随机数生成器有周期),但可以在一个足够大的范围内生成重复的序列。 根据引用,我们参考了以下方法: 引用[1]、[2]和[3]使用的是循环检查法(对应方法2)。 引用[4]使用的是随机排序法(对应方法1)。 下面我们分别给出两种方法的实现: 方法1:洗牌算法(Fisher-Yates shuffle) 步骤: 1. 创建一个数组,包含从最小值到最大值的所有整数(按顺序)。 2. 从最后一个元素开始,向前遍历,随机选择一个当前位置之前的元素(包括当前位置)进行交换。 3. 这样得到的数组就是随机排列的。 方法2:使用Set(或数组)来存储已生成随机数,每次生成一个随机数,检查它是否在Set中,在则加入。 但是,请注意,方法2在需要大量随机数时(比如接近范围大小时)可能会很慢,因为最后几个数可能需要多次尝试才能生成一个未出现过的数。因此,我们推荐使用方法1。 然而,如果范围非常大(比如1到1000000),而我们只需要10个重复随机数,那么方法2更合适,因为方法1需要创建一个非常大的数组(1000000个元素),而方法2只需要存储10个数字。 因此,我们需要根据需求选择合适的方法。 下面我们提供两个函数: 1. 生成指定范围内指定数量的重复随机数(使用Set,适用于数量较少的情况): function getRandomUniqueNumbers(count, min, max) { if (count > max - min + 1) { throw new Error('无法生成指定数量的重复随机数!'); } const set = new Set(); while (set.size < count) { const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; set.add(randomNumber); } return Array.from(set); } 2. 生成整个范围的重复随机序列(洗牌算法): function shuffleArray(min, max) { const array = []; for (let i = min; i <= max; i++) { array.push(i); } for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } // 如果只需要部分,可以截取,但注意这样能保证剩下的部分也是随机的,所以如果只需要部分,建议使用方法1。 但是,如果我们只需要部分随机数,但范围很大,使用洗牌算法会消耗大量内存,因此我们建议使用洗牌算法生成部分随机数。 因此,我们可以提供一个更通用的函数,它根据所需数量与范围大小的比例来选择方法: 然而,为了简单,我们可以提供两个函数,由用户根据情况调用。 另外,引用[4]中的洗牌算法使用的是sort方法,但这种方法并是均匀随机的,因为排序算法依赖于比较函数,而比较函数返回随机值会导致排序稳定,且每个元素被比较多次,一定能得到均匀分布。因此,我们使用Fisher-Yates洗牌算法,它更高效且随机性更好。 下面给出代码示例: 方法1(适用于数量少的情况): */ function getRandomUniqueNumbers(count, min, max) { if (count > (max - min + 1)) { throw new Error('无法生成指定数量的重复随机数!'); } const set = new Set(); while (set.size < count) { const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; set.add(randomNumber); } return Array.from(set); } // 方法2(适用于需要整个范围或大部分范围的情况): function getFullShuffledArray(min, max) { const array = []; for (let i = min; i <= max; i++) { array.push(i); } // Fisher-Yates 洗牌算法 for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } // 如果只需要部分,可以这样: // 注意:如果只需要部分,且部分数量接近整个范围,那么我们可以生成整个范围的洗牌数组,然后截取前count个。但是,如果范围很大而需要的数量很少,这样会浪费内存和计算时间。因此,我们建议: // 当 count 接近 (max-min+1) 时(比如超过一半),使用洗牌然后截取。 // 当 count 远小于范围大小时,使用Set方法。 // 我们可以写一个智能函数: function generateUniqueRandomNumbers(count, min, max) { const range = max - min + 1; if (count > range) { throw new Error('无法生成指定数量的重复随机数!'); } // 如果需要的数量超过范围的一半,我们使用洗牌算法(因为这样效率更高,避免Set方法后期重复次数过多) if (count > range / 2) { // 生成整个范围的洗牌数组,然后取前count个 const shuffled = getFullShuffledArray(min, max); return shuffled.slice(0, count); } else { // 使用Set方法 const set = new Set(); while (set.size < count) { set.add(Math.floor(Math.random() * range) + min); } return Array.from(set); } } // 但是,洗牌算法在范围很大时(比如1到1000000)且只需要少量(比如10个)的时候,会创建一个大数组,这样划算。所以上面的智能函数在count>range/2时使用洗牌,否则使用Set。这样在count较少时避免了大数组,在count较多时避免了Set的重复尝试问题。 // 然而,洗牌算法在范围很大且count较少时,我们并生成整个数组,所以我们可以优化:使用Map或对象来存储部分交换的洗牌算法(即Knuth的算法),但这样实现起来稍复杂。 // 另一种高效的方法是使用哈希表(即Set)的改进,但我们已经使用了Set。 // 因此,上面的智能函数在count较小时使用Set,在count较大时使用洗牌算法(此时洗牌算法虽然创建整个数组,但避免了Set方法的高重复率,所以效率可以接受)。 // 测试: // 示例1:生成10个1到100之间的重复随机数 // console.log(generateUniqueRandomNumbers(10, 1, 100)); // 示例2:生成90个1到100之间的重复随机数(使用洗牌算法) // console.log(generateUniqueRandomNumbers(90, 1, 100)); // 注意:洗牌算法返回的数组包含整个范围,但我们只需要前90个,这样是没问题的。 // 但是,如果我们只需要90个,那么洗牌算法生成100个数字,然后我们取前90个。这比使用Set方法在90%的填充率下要高效(因为Set方法在最后10%的时候重复概率很高)。 // 因此,我们推荐使用这个智能函数。 // 现在,根据用户的问题,他要求“永重复”,如果用户希望在一个序列中永重复,那么他可能需要一个全局的存储来记录已经生成过的数字,这样在多次调用时也能保证重复。但是,这需要持久化存储(比如数据库、文件等),而JavaScript通常运行在浏览器或Node.js环境中,我们可以利用全局变量,但页面刷新后就会重置。 // 因此,如果用户需要跨会话的永重复,那么需要将已生成随机数持久化存储。但问题没有说明,我们只考虑单次运行。 // 综上所述,我们提供上述函数。 // 下面我们根据引用的示例,给出一个使用洗牌算法的简单示例(如引用[4]的改进版)和Set方法的示例(如引用[2][3])以及智能函数。 // 另外,注意引用[1]中的代码存在效率问题,因为它在循环中调用函数,且函数内又循环检查,当数组变大时效率低。引用[2]使用了indexOf,在数组较大时效率也高,而使用Set可以提升效率(Set的查找是O(1))。 // 因此,我们使用Set来优化。 // 最后,我们按照用户要求,用中文回答,并生成相关问题。 // 代码部分我们将用代码块展示,并附有注释。 // 回答如下: </think> ### JavaScript生成重复随机数的实现方法 在JavaScript生成重复随机数需要结合随机数生成重复检测机制。以下是三种高效可靠的方法: #### 方法1:使用Set数据结构(推荐) ```javascript function generateUniqueRandomNumbers(count, min, max) { if (count > (max - min + 1)) { throw new Error('无法生成指定数量的重复随机数!'); } const uniqueNumbers = new Set(); while (uniqueNumbers.size < count) { const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; uniqueNumbers.add(randomNumber); } return Array.from(uniqueNumbers); } // 示例:生成5个1-10之间的重复随机数 const randomNumbers = generateUniqueRandomNumbers(5, 1, 10); console.log(randomNumbers); // 例如:[3, 7, 2, 9, 5] ``` **原理**:利用`Set`自动去重的特性,通过循环添加随机数直到集合达到指定大小[^2][^3]。 #### 方法2:Fisher-Yates洗牌算法 ```javascript function generateUniqueRandom(min, max, count) { const pool = Array.from({length: max - min + 1}, (_, i) => i + min); for (let i = pool.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [pool[i], pool[j]] = [pool[j], pool[i]]; } return pool.slice(0, count); } // 示例:生成0-9范围内的5个重复随机数 console.log(generateUniqueRandom(0, 9, 5)); // 例如:[7, 3, 0, 2, 9] ``` **原理**:创建有序数组后随机打乱顺序,取前N个元素[^4]。 #### 方法3:标记法(适用于大范围) ```javascript function getUniqueRandoms(count, min, max) { const range = max - min + 1; if (count > range) throw new Error('范围足'); const map = {}; const result = []; while (result.length < count) { const num = Math.floor(Math.random() * range) + min; if (!map[num]) { map[num] = true; result.push(num); } } return result; } ``` ### 关键注意事项 1. **范围验证**:确保所需随机数数量超过范围大小(max - min + 1) 2. **效率考量**: - 小样本推荐`Set`方法(O(n)) - 大样本推荐洗牌算法(O(n)) 3. **随机性质量**:`Math.random()`适用于一般场景,密码学场景需用`crypto.getRandomValues()` 4. **“永重复”的实现**:需结合持久化存储记录已用随机数,如: ```javascript const usedNumbers = new Set(); function getPermanentUniqueRandom(min, max) { let num; do { num = Math.floor(Math.random() * (max - min + 1)) + min; } while(usedNumbers.has(num)); usedNumbers.add(num); return num; } ``` ### 性能对比 | 方法 | 时间复杂度 | 适用场景 | |-------------|------------|------------------------| | Set检测法 | O(n) | 小范围或数量少时 | | 洗牌算法 | O(n) | 需要完整随机序列时 | | 标记对象法 | O(n) | 超大范围(内存优化) | > 当需要生成随机数数量超过范围大小的50%,推荐使用洗牌算法;否则使用Set检测法更高效[^3][^4]。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值