SWE · Phone Interview

Apple 按人口权重随机抽国家:前缀和与二分边界

把整数人口映射为等大小 ticket 区间,用 bisect_right 精确选择并验证零权重边界。

题目重点:把整数人口映射为等大小 ticket 区间,用 bisect_right 精确选择并验证零权重边界。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 人口作为非负整数权重,所有人口总和必须为正;国家可重复命名但输出仅返回名字,若需要区分同名实体应引入唯一 ID。
  • 固定权重可多次抽样;本文用均匀整数 ticket ∈[0,total),避免浮点随机数刚好落在 total 时越界。
  • 随机源 `randrange(total)` 按均匀整数抽取;测试注入确定性 ticket。密码学用途须另评估随机源。

解题路线

  1. 按输入顺序累加权重,prefix[i] 是前 i+1 个权重之和;第 i 个国家拥有整数区间 [prefix[i−1],prefix[i]),长度恰为其人口。
  2. 均匀抽 0..total−1 的 ticket,用 `bisect_right(prefix,ticket)` 找第一个严格大于 ticket 的前缀值,返回对应国家。
  3. 零权重会产生相同的前缀值,但不拥有任何 ticket;`bisect_right` 的严格大于规则自然跳过它。
  4. 用把每个国家名称按权重重复展开的独立参考桶,穷尽短输入的每个 ticket 核对归属。

手动走一遍

A:2、B:0、C:3 的前缀是 [2,2,5]。ticket 0/1 给 A;ticket 2/3/4 给 C;B 概率为 0。若误用 `bisect_left`,ticket=2 会错落到 A 或零权重 B 的边界。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from bisect import bisect_right
from collections.abc import Callable
from random import randrange


class WeightedCountrySampler:
    def __init__(self, countries: list[tuple[str, int]]):
        if not countries:
            raise ValueError("at least one country is required")
        self.names: list[str] = []
        self.prefix: list[int] = []
        total = 0
        for name, population in countries:
            if population < 0:
                raise ValueError("population must be non-negative")
            total += population
            self.names.append(name)
            self.prefix.append(total)
        if total == 0:
            raise ValueError("at least one population must be positive")
        self.total = total

    def sample(self, draw: Callable[[int], int] = randrange) -> str:
        ticket = draw(self.total)  # Uniform integer in [0, total).
        if ticket < 0 or ticket >= self.total:
            raise ValueError("draw must return a ticket inside [0, total)")
        return self.names[bisect_right(self.prefix, ticket)]

复杂度

预处理 O(n) 时间与空间,单次抽样 O(log n) 时间、O(1) 额外空间。若权重频繁修改,前缀数组每次更新需 O(n),可改 Fenwick 树或线段树达 O(log n) 更新/抽样。

易错点与追问

容易失分的边界

  • 随机数取闭区间 [0,total],total 无对应国家。
  • 零权重项与重复前缀使用错误二分方向。
  • 没有拒绝负人口或总权重为零。
  • 用有偏 `random()%total` 代替均匀整数源。
  • 把动态更新仍说成前缀数组 O(log n) 更新。

继续深挖

  • 有千万个国家但只抽一次时,预处理是否值得?
  • 权重动态增减时如何用 Fenwick binary lifting 抽样?
  • 如何支持无法放进固定宽度整数的总人口?
  • 如何用统计测试发现随机源或区间映射偏差?
✓已复制微信号!