[映射思维] 710. 黑名单中的随机数

115 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

每日刷题 2022.06.26

题目

  • 给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist 。设计一种算法,从 [0, n - 1] 范围内的任意整数中选取一个 未加入 黑名单 blacklist 的整数。任何在上述范围内且不在黑名单 blacklist 中的整数都应该有 同等的可能性 被返回。
  • 优化你的算法,使它最小化调用语言 内置 随机函数的次数。
  • 实现 Solution 类:
    • Solution(int n, int[] blacklist) 初始化整数 n 和被加入黑名单 blacklist 的整数 * int pick() 返回一个范围为 [0, n - 1] 且不在黑名单 blacklist 中的随机整数

示例

输入
["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 <= 10^9
  • 0 <= blacklist.length <= min(10^5, n - 1)
  • 0 <= blacklist[i] < n
  • blacklist 中所有值都 不同
  • pick 最多被调用 2 * 10^4 次

解题思路

  • 最开始的思路:想要将范围[0 ~ n - 1]之间的数展开形成一个数组记为origin,挑出其中在黑名单中的数字,将其和数组origin中尾部的数值进行交换,最终就会使origin数组尾部开始往前数黑名单数组长度,全部都是黑名单中存在的数,这样的话,只需要随机数取0 ~ n - m(m表示黑名单数组的长度),就能保证 不在黑名单中的每个元素被等概率取到。
  • 在实现的时候,使用的是双指针+map数组映射,成功解决了前67个样例,卡在了最后一个样例68(超时错误)
  • 后期想优化的方法,将双指针的查找方法换成了使用set集合,替换双指针做法,将黑名单中的所有数,都存入到set集合中,再次遍历黑名单的数的时候,往后查找的可以映射的一定是n - m 之后的数值,n - m之后的数值都可以不需要考虑。
    • 简化掉每次调用indexof()方法,因为其会遍历整个数组,导致时间复杂度剧增;提前使用set集合进行预处理,遇到数组只需要判断其是否在set集合中即可。
  • 因为不考虑,前面黑名单中的数到底映射哪个数,因此直接从n - m开始往后映射即可。

AC代码

/**
 * @param {number} n
 * @param {number[]} blacklist
 */
var Solution = function(n, blacklist) {
  this.map = new Map();
  this.set = new Set();
  // 双指针,互换
  let m = blacklist.length;
  for(let i = 0; i < m; i++) {
    this.set.add(blacklist[i]);
  }
  // console.log('set:::', this.set)
  let cur = n - m;
  this.num = n - m;
  for(let i = 0; i < m; i++) {
    if(blacklist[i] < this.num) {
      while(this.set.has(cur)) {
        cur++;
      }
      this.map.set(blacklist[i], cur++)
    }
  }
  // console.log()
};

/**
 * @return {number}
 */
Solution.prototype.pick = function() {
  let ran = parseInt(Math.random() * this.num);
  // console.log(ran)
  if(this.map.has(ran)) {
    return this.map.get(ran);
  }else {
    return ran;
  }
};