SWE · VO Coding

LinkedIn O(1) 增删随机删除:重复值索引集合与末尾交换

数组存每次出现,value→索引集合支持删指定值,随机下标支持按 occurrence 均匀删除。

题目重点:数组存每次出现,value→索引集合支持删指定值,随机下标支持按 occurrence 均匀删除。

题意与假设

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

  • 训练版允许重复值,`remove(value)` 删除任意一个 occurrence,`remove_random()` 在所有 occurrence 中均匀选一个并返回其值;若题目要求不同 value 等概率,算法需改。
  • Hash/set 操作按平均 O(1) 讨论,随机下标由均匀随机源提供;最坏 hash 冲突不保证常数。

解题路线

  1. 数组 values 存每个 occurrence;map[value] 存该值的全部数组下标。add 在尾部追加并将新下标放集合。
  2. 删指定值时从其索引集合取任一位置 i,把数组最后一个值搬到 i,更新被搬值的索引集合:删旧末尾索引,加 i;再弹数组尾并清理空集合。若 i 本就是尾部,不能多做一次重映射。
  3. remove_random 先按数组长度均匀取下标,再复用同一个 `_remove_at(i)`;数组每个位置对应一次 occurrence,因此 occurrence 等概率。
  4. 若删除的 value 与末尾 value 相同,索引集合的删除/加入顺序尤其容易写错;验证应比较所有 value 的索引集合与数组实际位置。

手动走一遍

values=[A,B,A],indices[A]={0,2}。删下标 0 的 A,搬末尾 A 到位置 0,再删尾位置 2;结果 [A,B],A 的索引集合只剩 {0}。

Python 参考实现

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

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

from collections import defaultdict
from random import randrange


class RandomizedMultiset:
    def __init__(self):
        self.values = []
        self.indices = defaultdict(set)

    def add(self, value):
        index = len(self.values)
        self.values.append(value)
        self.indices[value].add(index)

    def remove(self, value) -> bool:
        if not self.indices.get(value):
            return False
        index = next(iter(self.indices[value]))
        self._remove_at(index)
        return True

    def _remove_at(self, index):
        value = self.values[index]
        last_index = len(self.values) - 1
        last_value = self.values[last_index]
        self.indices[value].remove(index)
        if index != last_index:
            self.values[index] = last_value
            self.indices[last_value].remove(last_index)
            self.indices[last_value].add(index)
        self.values.pop()
        if not self.indices[value]:
            del self.indices[value]
        return value

    def remove_random(self, choose_index=randrange):
        if not self.values:
            raise IndexError("empty collection")
        return self._remove_at(choose_index(len(self.values)))

复杂度

add/remove/remove_random 平均 O(1) 时间,存储 O(N) occurrences;随机删除均匀性依赖 choose_index 均匀。5,000 组随机操作序列与独立 Counter/索引重算 oracle 对照。

易错点与追问

容易失分的边界

  • 删除中间项只弹数组,后续索引全部错位。
  • 搬末尾后忘了更新其索引集合。
  • 末尾值与被删值相同时重复移除同一索引。
  • 随机从 distinct key 中选,误变成 value 等概率。
  • 声称 hash 表严格最坏 O(1)。

继续深挖

  • 如果要按不同 value 均匀随机,结构如何变?
  • remove(value) 必须删最早插入 occurrence 时还能 O(1) 吗?
  • 并发 add/remove_random 如何锁?
  • 支持权重随机时如何扩展?
✓已复制微信号!