【算法题】在一个长度为n的数组里的所有数字都在1到n-1的范围内。 有一个数字重复若干次,找出这个数字。

本文介绍了一种使用字典记录已出现数字来查找重复数字的方法,并提供了一种更节省空间复杂度的方法,通过交换数字到其应在的位置来找到重复的数字。

一般的方法还是见一个字典保存已经出现的数字,然后判断后面的数字是否出现在字典中。
 
def get_dup(lst):
    cnt={}
    for i in lst:
        if i in cnt:
            return i
        else:
            cnt[i]=1

节省空间复杂度的方法,利用保存着1~N-1这个条件。具一个特例,重复的数字出现两次,那么1~N-1都至少出现一次,当这些数字在自己的位置即a[n]=n,此时出现a[0]的数字就是重复的。

a[0]保存的肯定不是0,
① 假设a[0]=3, a[3]=5, 将a[0]与a[3]交换,a[3]=3
② 现在a[0]=5,  再将a[0]与a[5]交换,a[5]=5
③ a[0] = x, 再将a[0] 与 a[x]交换   a[x] = x
直到交换到, 该位置中的数与我想换进去的数相等则跳出。

代码如下: 

def get_dup(lst):
    key = lst[0]
    while not key==lst[key]:
        key, lst[key] = lst[key], key
    return key

<think>我们参考上述引用的方法,特别是引用[2]和引用[3]提供了两种常见的方法。 要求:在1到40的范围内随机生成10个不重复数字。 方法一:使用洗牌算法(Fisher-Yates) 1. 生成一个包含1到40的数组。 2. 打乱该数组(洗牌)。 3. 取前10个元素。 方法二:使用Set(当需要的数字数量远小于范围时,效率较高,但这10/40=25%,不算特别小,两种方法都可) 1. 创建一个空Set。 2. 循环生成随机数,直到Set中有10个元素(注意避免重复)。 3. 将Set转为数组。 然而,当需要的数量接近范围时,方法二可能因为频繁碰撞而效率降低。这10个在40范围内,碰撞概率不算太高,但为了确保性能稳定,我们更推荐方法一(洗牌算法)。 另外,引用[4]中使用了排序时随机比较函数的方法,但这种方法不是真正均匀的洗牌,不推荐使用(因为它依赖于排序算法的内部实现,可能导致分布不均)。 因此,我们采用洗牌算法(方法一)来实现。 步骤: 1. 创建一个数组,包含1到40。 2. 使用Fisher-Yates洗牌算法打乱数组。 3. 截取前10个元素。 代码实现: ```javascript function generateRandomArray(min, max, count) { // 创建从min到max的数组 let array = Array.from({ length: max - min + 1 }, (_, i) => i + min); // 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]]; } // 返回前count个元素 return array.slice(0, count); } // 调用示例:生成1-40范围内10个随机数 const randomArray = generateRandomArray(1, 40, 10); console.log(randomArray); ``` 另一种方法(引用[2]的方法)是每次随机取一个删除,也可以,但当数组较大时,splice操作可能较慢。但这数组只有40个,所以也可以使用。 代码实现(引用[2]的方法): ```javascript function generateUniqueRandomNumbers(min, max, count) { // 创建一个包含min到max的数组 const numbers = Array.from({ length: max - min + 1 }, (_, i) => i + min); const result = []; for (let i = 0; i < count; i++) { // 如果数组为空则提前结束 if (numbers.length === 0) break; // 随机索引 const index = Math.floor(Math.random() * numbers.length); result.push(numbers[index]); numbers.splice(index, 1); // 移除该元素 } return result; } const randomArray = generateUniqueRandomNumbers(1, 40, 10); console.log(randomArray); ``` 两种方法都可以,第一种洗牌算法在性能上更优,特别是当需要取出的数量接近总数时。这我们更推荐洗牌算法。 注意:如果要求返回的数组需要排序,可以在返回前排序,但题目没有要求,所以不需要。 因此,我们提供两种方法供用户选择。 相关问题: 1. 如何确保生成的随机数不重复? 2. 如果需要生成的随机数数量接近范围上限(比如在1-40中生成39个),哪种方法更好? 3. 如何生成一个指定范围内的随机整数(单个)? 4. 洗牌算法(Fisher-Yates)的原理是什么? </think>根据您的需求,在 JavaScript 中生成 1-40 范围内包含 10 个不重复随机数字数组,以下是两种推荐方法: ### 方法一:洗牌算法(推荐) ```javascript function getRandomArray(length, min, max) { // 创建基础数组 const array = Array.from({ length: max - min + 1 }, (_, i) => i + min); // 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.slice(0, length); } // 调用:生成1-40范围内10个随机数 const randomNumbers = getRandomArray(10, 1, 40); console.log(randomNumbers); // 示例输出:[15, 3, 28, 7, 12, 39, 22, 5, 31, 18] ``` ### 方法二:动态选取法 ```javascript function getRandomArray(min, max, count) { const numbers = []; const result = []; // 创建1-40的数组 for (let i = min; i <= max; i++) { numbers.push(i); } // 随机选取数字 for (let i = 0; i < count; i++) { const index = Math.floor(Math.random() * numbers.length); result.push(numbers[index]); numbers.splice(index, 1); // 移除已选数字 } return result; } // 调用 console.log(getRandomArray(1, 40, 10)); // 示例输出:[32, 9, 21, 40, 6, 15, 27, 3, 18, 11] ``` ### 方法对比 | **特性** | 洗牌算法 | 动态选取法 | |----------------|--------------------------|--------------------------| | 适用场景 | 需要完整随机序列时 | 只需少量随机数时 | | 性能 | 更高效(O(n)) | 每次 splice 有开销 | | 随机分布 | 均匀分布 | 均匀分布 | | 推荐范围 | 特别适合大范围 | 适合1-40这类小范围 | > **推荐选择**:由于 1-40 是小范围,两种方法均可,但洗牌算法更符合最佳实践,尤其在需要多次生成时性能更优[^2][^3]。 ### 注意事项 1. 范围验证:需确保 `count <= (max - min + 1)` 2. 边界包含:上述代码包含最小值和最大值 3. 随机性质量:`Math.random()` 满足基本需求,但如需加密级随机性需使用 `crypto.getRandomValues()`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值