20220425力扣每日一题

398. 随机数索引

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

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

示例:

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);

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/random-pick-index
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

基本思想

        第一反应是使用哈希表存储所有出现数字及对应的数字下标,需要查找时,直接按照target数字查哈希表,对于所有可能的下标随机选一个即可,即使用rand() % n的方式就能做到均匀随机访问,构造需要的时间对应的复杂度为O(n),访问的时间复杂度为O(1)但是空间复杂度为O(n),这显然不合题意中使用较少额外空间的题意。

        备用方法是使用水塘抽样的思路,这种方法是一种线上解法,也即扫描过边扫描数组边更新返回结果,但是官解似乎给的还是先把数字拷贝到Solution类对象中,构造数组所花的时间复杂度为O(n)(?),可能本意应该是每次pick都根据之前的nums数组来?

下面介绍使用水塘抽样的原理:

水塘抽样简介:

水塘抽样(Reservoir Sampling) - 知乎 (zhihu.com)

主要思想就是“以时间换空间”,在文件大到无法一次装入内存时,仍然可以等概率的获取相同值的变量。

具体描述

前置原理:

假设该文件中存在数据流,该数据流中所有整型变量的个数为N,我们需要的target值大小的整型变量可能有若干个,假设有n个,为了保障能等概率地所有选到的数,首先容易想到:所有可能发生事件的一个划分为:target_{i}为选中第i个值为target的变量的事件,加overline的则为没选中第i个值为target的变量的事件。

\Omega = \sum_{i = 1}^{n}(target_{i}\cdot \prod_{j=i+1}^{n} \overline{target_{j}})

选中第n个target的概率为1/n,这里可以直接rand() % n,前面n-1个数字选到与否都无关最后结果,因为被第n个target选择到的结果“污染”了,所以对n而言,选择到的概率为:

        P(target_n)=\frac{1}{n}                        (1)

选中第n-1个target的概率也应该为1/n,这里对应的随机事件为:①选中第n-1个target并且,②没有选中第n个target。①②两个事件相互独立,所以对应的概率描述如下:

P(target_{n-1} \cdot \overline{target_{n}})=P(target_{n-1}) \cdot P(\overline{target_{n}}) = \frac{1}{n}                (2)

而根据前式(1)可得:

        P(target_{n-1})=\frac{1}{n-1}

以此类推,可以不断得到对于第i个target选中的事件如下(第i次选中,后边所有出现的target均不选取):

P(target_{i}\cdot \prod_{s=i+1}^{n} \overline{target_{s}}) = \frac{1}{n}

P(target_{i}) = \frac{1}{i}

那么该如何实现呢?可以使用cnt变量存储访问到target的次数,每次访问到target,先cnt++,然后选中第cnt位target的概率要为1/cnt,则只需rand() % cnt即可,过程还是比较简单的。

实现代码(C++)

class Solution {
private:
    vector<int> nums;
public:
    Solution(vector<int>& nums) {
        this->nums = nums;
    }
    
    int pick(int target) {
        int res;
        int cnt = 0;
        for(int i = 0; i < nums.size(); i++) {
            if(target == nums[i]) {
                cnt++;
                if(rand() % cnt == 0) {
                    res = i;
                }
            }
        }
        return res;
    }
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(nums);
 * int param_1 = obj->pick(target);
 */

最终结果

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值