LeetCode398-随机数索引

原题链接:【398. 随机数索引】:https://leetcode-cn.com/problems/random-pick-index/

题目描述:

给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。

注意:

数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。

示例:

int[] nums = new int[] {1,2,3,3,3};
Solution solution = new Solution(nums);

// pick(3) 应该返回索引 2,3 或者 4。每个索引的返回概率应该相等。
solution.pick(3);

// pick(1) 应该返回 0。因为只有nums[0]等于1。
solution.pick(1);

解题思路:

解法1:哈希表。

暴力解法。使用哈希表存储数组中每个元素值到其所有的下标索引的映射。

即key为元素值,value为元素值对应的索引下标列表。

再用random函数,从下标列表中随机选择一个索引返回即可。

这个解法非常占用额外空间O(N),如果数组很大,很容易oom。

解法2:动态数组存储索引列表。

定义一个存储索引下标的索引列表indexList,用动态数组,方便扩展。

遍历数组,如果元素跟target目标值相等,则把索引值添加进indexList列表。

取随机数,rand范围为【0,indexList.length ),然后将对应位置的索引值返回即可。

概率为:1 / indexList.length

这里相比上一种解法,已经降了一个数量级,但是如果N很大,或者数字分布不均匀,indexList也可能非常大,也非常占用额外空间,空间消耗也可能会达到O(N),可能会oom。

解法3:蓄水池抽样算法。

算法是:要从超大数组N中取k个元素,这里是取一个元素,即K=1

那么每次只取一个样本值,那么逐个遍历数组,当遇到第i个数时,就以 1 / i 的概率抽取它做样本值,以(i - 1)/ i 的概率保留原来的样本值,不替换。

我们不需要保存索引下标列表,只需要一个变量cnt计数,统计匹配到了多少个相同的数字,方便后去取随机数,算概率。

遍历数组,如果元素跟target目标值相等,则cnt++,计数器+1。

然后取随机数,rand范围为【1,cnt】。

因为这里只取一个数,就是K=1的案例。

所以如果rand == 1,则取当前元素的下标,替换为结果值。

如果rand > 1,则保留之前的结果,继续往下走。知道结束返回结果值。

特点:这里只需要遍历一遍数组,而且没有额外的空间消耗,只有一个计数器变量cnt。空间复杂度为O(1)。(非常推荐)

题解代码:

代码如下:(java版)

//给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。 
//
// 注意: 
//数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。 
//
// 示例: 
//
// 
//int[] nums = new int[] {1,2,3,3,3};
//Solution solution = new Solution(nums);
//
//// pick(3) 应该返回索引 2,3 或者 4。每个索引的返回概率应该相等。
//solution.pick(3);
//
//// pick(1) 应该返回 0。因为只有nums[0]等于1。
//solution.pick(1);
// 
// Related Topics 水塘抽样 哈希表 数学 随机化 
//  125  0


import java.util.Random;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {

    // 需要把nums传递给pick方法,所以我们需要定义一个类变量
    int[] nums;
    public Solution(int[] nums) {
        this.nums = nums;
    }
    
    public int pick(int target) {
        // 定义一个变量,存储结果值
        int result = 0;
        // 定义一个变量cnt,计数器,标记匹配到了几个相同的数字,方便后面取随机数范围
        int cnt = 0;
        // 循环遍历链表
        for (int i=0; i<nums.length; i++) {
            // 匹配跟target元素是否相等
            if (nums[i] == target) {
                // 匹配到了,则计数器+1
                cnt++;    
                // 取随机数rand范围为[1, cnt],注意这里边界是cnt
                int rand = new Random().nextInt(cnt) + 1;
                // 取每个数的概率都是 1 / cnt,也就是说有1 / cnt的概率,更新结果下标值
                if (rand == 1) {
                    // rand等于1,则取当前元素的下标,更新结果值
                    result = i;
                }
            }

        }

        // 返回结果下标
        return result;
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(nums);
 * int param_1 = obj.pick(target);
 */
//leetcode submit region end(Prohibit modification and deletion)

posted @ 2021-12-09 11:40  doublexi  阅读(181)  评论(0编辑  收藏  举报