SWE · VO Coding

LinkedIn 十亿级有序数组:按重复段二分跳跃统计不同值

当 N 很大、不同值 K 很小时,按段首二分下一个值;区别随机访问复杂度与实际 IO。

题目重点:当 N 很大、不同值 K 很小时,按段首二分下一个值;区别随机访问复杂度与实际 IO。

题意与假设

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

  • 数组可随机访问且升序(允许重复);返回不同值个数,空数组为 0。题目未说明数据是在内存、mmap、远程块存储还是只能流式读取。
  • 训练代码假设比较和下标访问为 O(1);若远程读,必须重新估算 IO/延迟。

解题路线

  1. 从 i=0 开始取当前值 v,二分搜索 [i+1,N) 中第一个 >v 的位置 j,也就是当前连续重复段的右边界;计数+1 并令 i=j。
  2. 边界不存在时 j=N,结束。每轮至少跳过一个完整 distinct 段,恰好 K 轮。单轮二分搜索至多 log N 次比较,总 O(K log N)。
  3. 可用指数搜索先找段边界上界再二分,时间 O(Σ log run_length);当段长差异很大时减少早期无谓比较。
  4. 若数组只能顺序扫描,则随机二分不可行,必须 O(N) 读或先建块索引;若存储按块且重复跨块,记录块首尾及 distinct 摘要可减少 IO。题目的分治递归在全异值时必须看完所有叶子,最坏 O(N)。

手动走一遍

[1,1,1,4,4,9,9,9,9]:i=0 取 1,二分跳到 3;取 4 跳到 5;取 9 跳到 9,答案 3。全相同只跑一次二分;全不同有 K=N,可能比线性扫描更慢,应检测或直接扫描。

Python 参考实现

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

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

def count_distinct_sorted(array) -> int:
    """Random-access sorted sequence; counts distinct values without scanning runs."""
    n = len(array)
    count = 0
    i = 0
    while i < n:
        value = array[i]
        lo, hi = i + 1, n
        while lo < hi:
            mid = (lo + hi) // 2
            if array[mid] <= value:
                lo = mid + 1
            else:
                hi = mid
        i = lo
        count += 1
    return count

复杂度

随机访问模型下 O(K log N) 时间、O(1) 额外空间;朴素扫描 O(N)。指数搜索可写成 O(Σ log rᵢ),rᵢ 各段长度。训练代码与 20,000 组随机有序数组的 set 大小对照通过。

易错点与追问

容易失分的边界

  • 二分找最后一个等值位置后没有 +1,陷入死循环。
  • 把随机访问 O(1) 假设直接套到远程十亿元素文件。
  • 说分治写法始终 O(K log N),忽略全不同退化。
  • K 接近 N 仍坚持每个元素再做二分。
  • 未处理空数组。

继续深挖

  • 只能顺序访问时还能比 O(N) 快吗?
  • 分块存储如何设计块级目录?
  • 数组持续追加且保持排序时如何增量维护 distinct?
  • 若数组非降序但允许数据质量错误,如何验证?
✓已复制微信号!