SWE · Coding

频率表排序与加权随机:精确整数区间取样

排名按频次,随机概率按count/总count,二者不是均匀选key。

题目重点:排名按频次,随机概率按count/总count,二者不是均匀选key。

题意与假设

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

  • 题目只给hashmap频率排序/随机追问方向,训练采用静态item→非负整数count。
  • randbelow(total)须均匀产生0..total−1整数;并列排名保留输入插入顺序。

解题路线

  1. 对正频项建立严格递增prefix累计和。随机整数r落入哪个半开频次区间就返回哪item,bisect_right(prefix,r)处理端点。
  2. 某项区间长度正好count,因此概率count/total。零频项剔除,空mass拒绝;不以random%total默认均匀,随机源必须保证无模偏差。
  3. 排序独立按count降序和稳定tie;动态增删频次可用Fenwick更新/按累计次数找rank,静态prefix更新则要重建后续值。

手动走一遍

频次A2、B3,对r0/1返回A,r2/3/4返回B,概率2/5与3/5。r恰2应B,bisect_left会在此边界误给A。

Python 参考实现

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

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

from bisect import bisect_right


class FrequencyTable:
    def __init__(self,frequencies):
        self.items=[];self.prefix=[];total=0
        for item,count in frequencies.items():
            if type(count) is not int or count<0:raise ValueError("nonnegative integer counts required")
            if count:
                total+=count;self.items.append(item);self.prefix.append(total)
        self.total=total

    def sample(self,randbelow):
        if not self.total:raise ValueError("empty frequency mass")
        r=randbelow(self.total)
        if type(r) is not int or not 0<=r<self.total:raise ValueError("invalid random source")
        return self.items[bisect_right(self.prefix,r)]

    def ranked(self):
        counts=[self.prefix[0]]+[self.prefix[i]-self.prefix[i-1] for i in range(1,len(self.prefix))] if self.prefix else []
        return [self.items[i] for i in sorted(range(len(self.items)),key=lambda i:(-counts[i],i))]

复杂度

建prefix O(d),sample O(logd),rank O(dlogd),空间O(d);频次数值大但不展开。

易错点与追问

容易失分的边界

  • 按key等概率抽样忽略频率。
  • 浮点累计概率在巨大计数下丢失小项,整数区间更易精确验证。

继续深挖

  • 动态更新怎么使用Fenwick的prefix selection?
  • 无放回加权抽样时每次取后是否清零、概率合同怎样变化?
✓已复制微信号!