题目描述
这是 LeetCode 上的 710. 黑名单中的随机数 ,难度为 困难。
Tag : 「前缀和」、「二分」、「离散化」、「随机化」
给定一个整数 n
和一个 无重复 黑名单整数数组 blacklist
。
设计一种算法,从 范围内的任意整数中选取一个 未加入 黑名单 blacklist
的整数。任何在上述范围内且不在黑名单 blacklist
中的整数都应该有 同等的可能性 被返回。
优化你的算法,使它最小化调用语言 内置 随机函数的次数。
实现 Solution
类:
-
Solution(int n, int[] blacklist)
初始化整数n
和被加入黑名单blacklist
的整数 -
int pick()
返回一个范围为且不在黑名单blacklist
中的随机整数
示例 1:
提示:
-
blacklist
中所有值都 不同 -
pick
最多被调用
前缀和 + 二分
为了方便,我们记 blacklist
为 bs
,将其长度记为 m
。
问题本质是让我们从 范围内随机一个数,这数不能在 bs
里面。
由于 的范围是 ,我们不能在对范围在 且不在 bs
中的点集进行离散化,因为离散化后的点集大小仍很大。
同时 的范围是 ,我们也不能使用普通的拒绝采样做法,这样单次 pick
被拒绝的次数可能很大。
一个简单且绝对正确的做法是:我们不对「点」做离散化,而利用 bs
数据范围为 ,来对「线段」做离散化。
具体的,我们先对 bs
进行排序,然后从前往后处理所有的 ,将相邻 之间的能被选择的「线段」以二元组 的形式进行记录(即一般情况下的 ,),存入数组 list
中(注意特殊处理一下两端的线段)。
当处理完所有的
我们可以对 list
数组做一遍「线段所包含点数」的「前缀和」操作,得到 sum
数组,同时得到所有线段所包含的总点数(前缀和数组的最后一位)。
对于 pick
操作而言,我们先在 范围进行随机(其中 代表总点数),假设取得的随机值为 ,然后在前缀和数组中进行二分,找到第一个满足「值大于等于 」的位置(含义为找到这个点所在的线段),然后再利用该线段的左右端点的值,取出对应的点。
代码:
- 时间复杂度:在初始化操作中:对
bs
进行排序复杂度为;统计所有线段复杂度为;对所有线段求前缀和复杂度为。在pick
操作中:随机后会对所有线段做二分,复杂度为 - 空间复杂度:
最后
这是我们「刷穿 LeetCode」系列文章的第 No.710
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour… 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。