面灵AI→

华为AI开发一面:向量数据库选型与并查集手撕

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 做一下自我介绍。
  2. 讲一下项目的流程,以及不同模块的功能。
  3. 你们使用的是什么向量数据库?为什么选它?
  4. 还了解哪些向量数据库?
  5. 除了 HNSW 还知道什么索引?
  6. 关系型数据库用的什么?
  7. 会看慢 SQL 吗?
  8. 用了哪些开源的框架和组件?具体怎么用的?
  9. 自己做的项目上线用了吗?
  10. is 和 == 的区别是什么?
  11. 对我们产品线有哪些了解?为什么选择我们?
  12. 手撕:并查集(给了 25 分钟)。

《参考解析》

向量数据库选型怎么讲:回答要落到「约束 → 候选 → 排除理由」。常见候选的能力边界:Milvus 是分布式原生、适合亿级规模与多租户,生态完整(支持多种索引与标量过滤),代价是部署重、运维成本高,小规模场景属于杀鸡用牛刀;Qdrant 是 Rust 实现,单机性能好、过滤能力(payload filter)强,部署轻,适合中小规模与需要复杂元数据过滤的场景;Weaviate 自带模块化与混合检索能力,开箱体验好但资源占用偏高;pgvector 直接把向量能力加进 PostgreSQL,最大优势是与现有关系数据同库同事务、不用引入新组件,代价是超大规模下的性能与索引选项有限;FAISS 是库不是服务,性能极强但要自己实现持久化、并发与过滤,适合嵌入到应用里做检索内核;Chroma 轻量、适合原型与本地开发。选型判断维度就是四条:数据规模与增长预期、是否需要复杂元数据过滤、是否要求与现有关系库强一致、以及团队能不能承担运维成本。讲的时候最后补一句「当时的规模是百万级、需要按用户和文档类型过滤,所以选了 X;如果规模涨到十亿级会重新评估 Y」,比单说「我们用 Milvus 因为性能好」有说服力得多。

向量索引除了 HNSW 还有什么:主要几类——IVF 系(倒排文件,先聚类分桶、检索时只查最近若干桶,用 nprobe 调召回)、PQ 与 IVFPQ(乘积量化,把向量分段量化压缩,省内存但精度有损,常与 IVF 组合)、Flat(暴力精确检索,作为基线与小规模首选)、DiskANN(磁盘驻留图索引,解决内存装不下的超大规模)、以及 IVF-HNSW 这类复合索引(先用 IVF 粗筛再用小图精查)。选型逻辑是三条约束的权衡:内存预算(PQ 省内存、Flat/HNSW 吃内存)、召回率要求(Flat 100%、近似索引可调)、以及查询延迟(HNSW 快、PQ 需要重排)。面试里能把这层「没有最好的索引,只有最合适的组合」讲出来就很稳。

慢 SQL 怎么看:先定位再看执行计划,最后按类型修。定位靠慢查询日志(slow_query_log、long_query_time)与 performance_schema,线上更实用的是 APM 的 SQL 耗时分布。看 EXPLAIN 的重点字段:type(要避免 ALL 全表扫描,ref/range/const 较好)、key 与实际 key_len(反推用到了联合索引的几列)、rows(预估扫描行数,与实际差太多说明统计信息过期)、filtered、以及 Extra(Using filesort 与 Using temporary 是重点优化信号,Using index 说明覆盖索引生效)。常见修法按收益排序:补或改联合索引让条件与排序都走索引、消除隐式类型转换、把 SELECT * 收窄成需要的列以促成覆盖索引、把复杂子查询改写成 join 或反之、深分页改游标分页、以及把统计类查询挪到离线或 OLAP。要补一句:EXPLAIN ANALYZE(MySQL 8.0.18+)能看到真实执行耗时,比 EXPLAIN 的估算更可靠。

is 与 == 的区别:== 比较的是「值是否相等」,会调用对象的 __eq__;is 比较的是「是否是同一个对象」,等价于比较 id()。因此两条要注意:一是 is 不能用来比较数字、字符串的内容——虽然 CPython 对小整数(−5 到 256)与部分字符串做驻留(interning)会让人误以为能用,但那是实现细节,超出范围或运行时拼接的字符串就不成立;二是判断 None、True、False、以及哨兵对象时必须用 is,因为它们是单例,is None 比 == None 更快也更安全(自定义类可能重写 __eq__ 让 == None 返回 True)。可变对象上两者的区别更直观:两个内容相同的 list,== 为 True、is 通常为 False。写代码的规范是「值比较用 ==,标识比较用 is」。

并查集手撕:核心是「用一个 parent 数组表示森林,每个元素指向自己的父节点,根节点指向自己」。三个操作:find(x) 沿父指针找到根,路径压缩(把沿途节点直接挂到根上)把均摊复杂度降到接近 O(1);union(a, b) 先找两根,相同则已在同一集合,不同则把一棵树挂到另一棵下,按秩合并(或按大小合并)避免树退化;connected(a, b) 判断两根是否相同。模板:

class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        while self.parent[x] != x:          # 路径压缩(迭代写法,避免深递归)
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False                    # 已在同一集合
        if self.rank[ra] < self.rank[rb]:
            ra, rb = rb, ra
        self.parent[rb] = ra
        if self.rank[ra] == self.rank[rb]:
            self.rank[ra] += 1
        return True

常见考点与坑:用「按秩合并 + 路径压缩」后单次操作均摊 O(α(n))(反阿克曼函数,实际可视为常数);只做路径压缩不做按秩合并也是均摊 O(log n),笔试里通常够用。典型应用是连通分量计数、判断无向图是否有环、Kruskal 最小生成树、朋友圈/账户合并这类「把等价的元素归到一起」的题。写的时候最容易错的两处:find 写成递归且数据规模大导致递归超限(改成迭代最稳);以及在 union 里忘记先比较根而直接比较元素本身。

「自己的项目上线用了吗」怎么答:诚实是第一位的,然后给出可验证的信息。如果上线了,说清上线形态(内部工具、给多少用户用、跑了多久)、以及你观察到的真实数据(调用量、成功率、用户反馈)——哪怕规模很小,真实运行的经历也比 demo 有说服力。如果没上线,就说明卡在哪一步(没有真实用户、合规限制、依赖的服务拿不到),以及你用什么方式逼近真实(做过小范围内测、压测、灰度演练),并讲清如果给你条件你会怎么推上线。这个问题背后考的是「你有没有把东西做到能被别人用」的意识,比技术细节更重要。

「对我们产品线有哪些了解」怎么答:这类问题必须提前做功课,现场编一定露馅。准备方式是查公司业务的公开信息(年报、官网、产品线、近期发布),挑与岗位相关的一到两条讲清楚:这条产品线解决什么问题、客户是谁、技术上的关键挑战可能是什么。然后把它和你的经历接上——比如你做过多模态/Agent/高并发系统,说明在哪个环节能直接用上。不要泛泛地说「贵公司平台大、发展好」,也不要为了讨好而夸大自己对公司内部细节的了解(说错反而更糟)。收尾可以问一个具体的、只有真研究过才问得出的问题,这会显著加分。