【710. 黑名单中的随机数】

来源:力扣(LeetCode)

描述:

给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法,从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。

优化你的算法,使它最小化调用语言 内置 随机函数的次数。

实现 Solution 类:

  • Solution(int n, int[] blacklist) 初始化整数 n 和被加入黑名单 blacklist 的整数
  • int pick() 返回一个范围为 [0, n - 1] 且不在黑名单 blacklist 中的随机整数

示例 1:

输入
["Solution", "pick", "pick", "pick", "pick", "pick", "pick", "pick"]
[[7, [2, 3, 5]], [], [], [], [], [], [], []]
输出
[null, 0, 4, 1, 6, 1, 0, 4]

解释
Solution solution = new Solution(7, [2, 3, 5]);
solution.pick(); // 返回0,任何[0,1,4,6]的整数都可以。注意,对于每一个pick的调用,
                 // 0、1、4和6的返回概率必须相等(即概率为1/4)。
solution.pick(); // 返回 4
solution.pick(); // 返回 1
solution.pick(); // 返回 6
solution.pick(); // 返回 1
solution.pick(); // 返回 0
solution.pick(); // 返回 4

提示:

  • 1 <= n <= 109
  • 0 <= blacklist.length <= min(105, n - 1)
  • 0 <= blacklist[i] < n
  • blacklist 中所有值都 不同
  • pick 最多被调用 2 * 104

方法 :黑名单映射

blacklist 的长度为 m

考察一个特殊的例子:所有黑名单数全部在区间 [n − m, n) 范围内。此时我们可以直接在 [0, n − m) 范围内取随机整数。

这给我们一个启示,对于在 [0, n − m) 范围内的黑名单数,我们可以将其映射到 [n − m, n) 范围内的非黑名单数(白名单数)上。每次 pick() 时,仍然可以在 [0, n − m) 范围内取随机整数(设其为 x),那么:

如果 x 不在黑名单中,则直接返回 x
如果 x 在黑名单中,则返回 x 映射到 [n − m, n) 范围内的白名单数。
我们可以在初始化时,构建一个从 [0, n − m) 范围内的黑名单数到 [n − m, n) 的白名单数的映射:

[n − m, n) 范围内的黑名单数存入一个哈希集合 black
初始化白名单数 w = n − m
对于每个 [0, n − m) 范围内的黑名单数 b,首先不断增加 w 直至其不在黑名单中,然后将 b 映射到 w 上,并将 w 增加一。

代码:

class Solution {
private:
    unordered_map<int, int> mp;
    int bound;

public:
    Solution(int n, vector<int> &blacklist) {
        int m = blacklist.size();
        bound = n - m;
        unordered_set<int> black;
        for (int b: blacklist) {
            if (b >= bound) {
                black.emplace(b);
            }
        }

        int w = bound;
        for (int b: blacklist) {
            if (b < bound) {
                while (black.count(w)) {
                    ++w;
                }
                mp[b] = w++;
            }
        }
    }

    int pick() {
        int x = rand() % bound;
        return mp.count(x) ? mp[x] : x;
    }
};

执行用时:108 ms, 在所有 C++ 提交中击败了89.64%的用户
内存消耗:68.6 MB, 在所有 C++ 提交中击败了65.49%的用户
复杂度分析
时间复杂度:初始化为 O(m), pick() 为 O(1),其中 m 是数组 blacklist 的长度。在初始化结束时, [n − m, n) 内的每个数字要么是黑名单数,要么被一个黑名单数所映射,因此 white 会恰好增加 m 次,因此初始化的时间复杂度为 O(m)。
空间复杂度: O(m)。哈希表需要 O(m) 的空间。
author:LeetCode-Solution

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值