返回

海量数据随机数的选取方法,黑名单中的随机数计算题详解

后端

题目

这是 LeetCode 上的 710. 黑名单中的随机数,难度为 困难。

Tag :「前缀和」、「二分」、「离散化」、「随机化」

给定一个整数 n 和一个 无重复 黑名单整数数组 blacklist,设计一个算法从 [0, n - 1] 范围内的整数中选取一个随机整数,使得它不在黑名单中。也就是说,对于每个不在黑名单中的整数,随机选到的概率是相同的。

可以保证黑名单长度小于等于 100000。它给出了一个办法是先排序,然后将不在黑名单中的整数的序号映射到新的序号,新序号就是新的随机数。不过没有详细的实现过程。

问题剖析

这道题乍一看似乎很难,但如果我们仔细分析,就会发现它其实是一个经典的「前缀和 + 二分」问题。

首先,我们可以将不在黑名单中的整数的序号映射到新的序号,新序号就是新的随机数。这样,我们就可以将随机数的选取问题转换为在新的序号范围内选取一个随机数的问题。

其次,我们可以使用「前缀和」来计算出每个序号的概率,然后使用「二分」来找到满足条件的序号。

具体实现

离散化

首先,我们需要对不在黑名单中的整数进行「离散化」,即将这些整数映射到新的序号。我们可以使用一个哈希表来实现离散化。

def discrete(nums):
  """
  离散化函数

  Args:
    nums: 不在黑名单中的整数列表

  Returns:
    哈希表,键为不在黑名单中的整数,值为新的序号
  """

  # 创建哈希表
  hash_table = {}

  # 序号
  idx = 0

  # 遍历不在黑名单中的整数
  for num in nums:
    # 将不在黑名单中的整数映射到新的序号
    hash_table[num] = idx

    # 序号加 1
    idx += 1

  # 返回哈希表
  return hash_table

前缀和

接下来,我们需要计算出每个序号的概率。我们可以使用「前缀和」来计算出每个序号的概率。

def prefix_sum(probs):
  """
  前缀和函数

  Args:
    probs: 每个序号的概率列表

  Returns:
    前缀和列表
  """

  # 创建前缀和列表
  prefix_sums = []

  # 前缀和
  prefix_sum = 0

  # 遍历每个序号的概率
  for prob in probs:
    # 前缀和加概率
    prefix_sum += prob

    # 将前缀和添加到前缀和列表中
    prefix_sums.append(prefix_sum)

  # 返回前缀和列表
  return prefix_sums

二分

最后,我们需要使用「二分」来找到满足条件的序号。

def binary_search(prefix_sums, target):
  """
  二分函数

  Args:
    prefix_sums: 前缀和列表
    target: 目标值

  Returns:
    满足条件的序号
  """

  # 左边界
  left = 0

  # 右边界
  right = len(prefix_sums) - 1

  # 二分查找
  while left <= right:
    # 中间位置
    mid = (left + right) // 2

    # 如果前缀和大于目标值
    if prefix_sums[mid] > target:
      # 右边界变为中间位置 - 1
      right = mid - 1
    # 如果前缀和小于目标值
    elif prefix_sums[mid] < target:
      # 左边界变为中间位置 + 1
      left = mid + 1
    # 如果前缀和等于目标值
    else:
      # 返回中间位置
      return mid

  # 返回满足条件的序号
  return left

代码实现

import random

def get_random(n, blacklist):
  """
  从 [0, n - 1] 范围内的整数中选取一个随机整数,使得它不在黑名单中。

  Args:
    n: 整数 n
    blacklist: 黑名单整数数组

  Returns:
    不在黑名单中的随机整数
  """

  # 不在黑名单中的整数列表
  nums = [i for i in range(n) if i not in blacklist]

  # 离散化
  hash_table = discrete(nums)

  # 计算每个序号的概率
  probs = [1 / len(nums) for _ in range(len(nums))]

  # 计算前缀和
  prefix_sums = prefix_sum(probs)

  # 选取一个随机数
  target = random.random()

  # 二分查找
  idx = binary_search(prefix_sums, target)

  # 返回不在黑名单中的随机整数
  return hash_table[nums[idx]]

结语

以上就是这道题的详细解题思路和具体实现。希望对大家有所帮助。