小红书

大模型算法一面、二面面经

完整信息

31 条问法 · 30 道题

本篇目录

写在前面

【WeThinkIn出品】栏目专注于分享Rocky的认知思考与经验感悟,范围涵盖但不限于AI行业。

干货AIGC、AI Agent、LLM算法工程师/开发工程师面试面经秘籍分享(持续更新):WeThinkIn/AIGC-Interview-Book欢迎大家Star~

大家好,我是Rocky Ding。

内容社区的大模型算法,需要同时回答三类问题:模型是否理解内容,推荐是否创造真实用户价值,系统是否能在成本与延迟约束下稳定运行。Rocky认为,面试中最有区分度的能力,是把数据、模型、评测和线上反馈连接起来,并说明每个结论成立的条件。

目录

笔试(机考)

一面

二面

HR 面与反问

手撕

笔试(机考)

1. 给一批笔记互动数据,按加权热度倒排再去重,输出前 N。

回答:

先确定数据语义:每条记录是一篇笔记的互动快照,还是一条独立互动事件?去重键是笔记 ID,还是内容指纹?如果是同一篇笔记的多次快照,不能直接累加累计点赞数;如果是独立事件,则需要先按事件 ID 去重,再按笔记聚合。下面按题目字面实现:对输入快照计算热度,降序排序,同一笔记保留排序后第一次出现的记录,最终返回 N 篇不同笔记。

设点赞、收藏、评论、分享的权重由题目给定,热度为:

$H_i=w_lL_i+w_fF_i+w_cC_i+w_sS_i$

权重不能擅自写成某个“行业标准”。若题目未规定并列规则,可以说明采用原输入顺序保证稳定性。特别注意:不能先截取前 N 条再去重,否则会漏掉后面本应补入的不同笔记。

from math import isfinite

def top_notes(records, n, weights):
    if type(n) is not int or n < 0:
        raise ValueError("n must be a nonnegative integer")
    if n == 0:
        return []
    if not weights:
        raise ValueError("weights must not be empty")

    def valid_number(value):
        try:
            return (type(value) in (int, float)
                    and isfinite(value) and value >= 0)
        except OverflowError:
            return False

    if not all(valid_number(w) for w in weights.values()):
        raise ValueError("invalid weight")

    scored = []
    for pos, row in enumerate(records):
        if not isinstance(row.get("note_id"), str) or not row["note_id"]:
            raise ValueError("note_id must be a nonempty string")
        values = [row.get(key, 0) for key in weights]
        if not all(valid_number(v) for v in values):
            raise ValueError("invalid interaction count")
        try:
            score = sum(w * v for w, v in zip(weights.values(), values))
        except OverflowError as exc:
            raise ValueError("nonfinite score") from exc
        if not valid_number(score):
            raise ValueError("nonfinite score")
        scored.append((score, pos, row))

    scored.sort(key=lambda item: (-item[0], item[1]))
    seen, result = set(), []
    for _, _, row in scored:
        if row["note_id"] in seen:
            continue
        seen.add(row["note_id"])
        result.append(row)
        if len(result) == n:
            break
    return result

输入 M 条记录时,时间复杂度为 O(M log M),额外空间为 O(M)。空输入返回空列表;N 大于不同笔记数时返回全部可用结果。代码约定缺失互动字段为 0,负数、布尔值与非有限数为非法输入;实际机考应服从题目的输入约束。

若数据量很大,在“同 ID 保留最高分、并列保留最早输入”的规则下,可以先用哈希表为每个 ID 保留最优记录,再用大小为 N 的小顶堆取 TopN,达到 O(M + U log N),其中 U 为不同 ID 数、1 ≤ N ≤ U。这一等价变换依赖保留规则;若要求保留最新快照,必须先按时间选快照再评分。

用反例确认操作顺序。 假设按热度排序后的记录依次是 A:100、A:99、B:98,要求返回两篇不同笔记。先截断再去重只能得到 A;正确结果是 A 和 B。如果先去重时随意保留 A 的低分快照,也可能改变最终排名。代码的稳定性依赖“同分时保留原输入位置”,在分布式读取没有确定顺序时,应改成题目认可的时间戳与唯一键组合。

数值范围同样属于接口契约:Python 整数乘法可以生成极大整数,但转换到浮点有限性检查时仍可能溢出;混合整数与浮点数的求和也可能触发溢出。这里把这两类情况统一为 ValueError,拒绝超出约定计算范围的输入。若业务要求精确比较超大整数或十进制定点金额,应重新定义数值类型与比较规则。n=0 走快速返回,不继续扫描记录字段。

2. 图文匹配题,判断一条评论属于哪篇笔记,给特征思路。

回答:

这是评论与候选笔记的关联匹配。首先确认是否允许使用真实线程 ID:如果线上本来就有准确的父笔记 ID,直接做关系查询最可靠;如果它正是待预测标签,就不能把它或其派生字段作为输入,否则会形成标签泄漏。

可以分三层构造特征。文本语义层编码评论、笔记标题、正文、话题和 OCR 文本,计算语义相似度,同时识别实体、商品、地点、数量与否定关系。视觉层提取图片整体和局部区域表征,处理“这双鞋”“左边那个人”等指代。上下文层在授权且预测时可用的范围内,引入发布时间差、可见对话上下文、语言与兴趣类别;不能使用评论发布后的互动结果来帮助预测。

系统上采用“召回 + 精排”。第一阶段用关键词和双塔向量检索从大量笔记中召回候选;第二阶段让交叉编码器或多模态模型共同读取评论与候选笔记,判断细粒度关联。CLIP 类对比学习可以提供图文语义起点,但通用图文相似度不等于“评论归属”的概率,仍需任务数据适配。

训练正样本来自可靠归属关系;负样本同时包含随机负例与同主题、同商品、相近时间的困难负例。对“好看”“求链接”这类通用评论,多个候选都可能合理,应允许不确定、拒答或返回候选集合,避免强行唯一匹配。

评价召回 Recall@K、精排 MRR/Top1、拒答后的覆盖率与错误率,并按短评论、跨模态指代、长尾商品分桶。若目标是泛化到新笔记,按笔记及时间划分集合,避免同一笔记的近重复评论跨训练集和测试集。

3. 一个生成式排序,要求按时间衰减,怎么设计打分?

回答:

先澄清“生成式排序”的具体含义:它可能是模型生成候选物品 ID,也可能是生成式模型为候选输出相关性评分。无论哪种形式,都需要把语义相关性、行为价值与时效性分开定义,避免把模型文本回答的自信程度直接当排序分。

若模型给出可比较的物品概率,可以定义:

$S(u,i,t)=\log p_\theta(i\mid h_u,c)-\lambda\max(0,t-t_i)+\beta Q_i$

其中 $h_u$ 是用户在请求时可见的历史,$c$ 为请求上下文,$t_i$ 是物品发布时间,$Q_i$ 是经过校准的质量信号。时间单位必须与 $\lambda$ 对应。在不考虑质量项时,这等价于概率乘以指数衰减:

$p_\theta(i\mid h_u,c)\exp(-\lambda\Delta t),\qquad \lambda=\frac{\ln 2}{T_{1/2}}$

半衰期应按场景验证。新闻资讯强调新鲜度,知识教程具有更长寿命,不能共用一个激进衰减参数。时间戳异常应监控,公式里的截断只是不让未来时间获得额外奖励。已有模型若把时间充分建模,再叠加强衰减可能造成重复惩罚,需要消融。

对自回归生成的多 token 物品 ID,其序列 log-probability 为各 token 条件 log-probability 之和;ID 长度和编码结构不同会带来比较偏差。优先使用一致的物品编码或单独校准;长度归一化是启发式,归一化之后不能继续称为严格概率。还需用合法 ID 约束、可服务物品过滤和去重保证候选有效。

HSTU 等生成式推荐方法体现了序列建模路线,但“使用生成式模型”不自动解决曝光偏差、时间泄漏和业务多目标。上线应联合看用户满意度、长尾覆盖、新内容发现、长期留存及延迟,不能只看新内容占比。

半衰期如何落到排序变化? 仅作说明,设两篇笔记的模型概率分别为 0.8 和 0.5,前者已发布两天,后者刚发布,半衰期为一天,且质量项相同。指数衰减后的分数分别为 $0.8\times 2^{-2}=0.2$ 与 $0.5$,较新的笔记会排在前面。这表达了一项明确的时效偏好,并不证明它更相关。需要在固定候选集上消融半衰期,再用线上实验检验该偏好是否增加真实用户价值。

4. 多模态特征怎么融合,早期融合还是晚期?

回答:

选择取决于跨模态交互强度、缺失情况和服务预算。早期融合让不同模态较早共同参与建模,晚期融合组合各模态独立输出的分数或决策,中间融合则先编码再用交叉注意力等模块交互。面试中要先说明命名口径:把独立编码后的特征拼接叫“早期融合”属于相对决策层而言的说法,不等于原始像素与文本直接拼接。

方案 核心机制 适用情况与代价
早期/联合 token 建模 较早学习图像、文本、音频之间的关系 交互能力强,对数据对齐与计算预算要求高
中间融合 独立编码后使用拼接、门控或交叉注意力 兼顾复用与细粒度交互,常用于精排和内容理解
晚期融合 校准后合并各模态分数 易缓存、易降级,但难表达复杂的局部对应关系

内容社区可以在召回阶段使用独立编码器和向量索引,在精排阶段使用更强交互模型。对于“文字说红色,图中实际是蓝色”这类矛盾,只有独立语义分数相加往往不够,需显式对齐实体、属性或区域。

缺失模态要有 mask 和训练覆盖,可用 modality dropout 模拟图像不可用、OCR 错误等情况;门控还应考虑质量信号,防止某一强势模态长期压制其他模态。评测必须包括单模态基线、模态缺失、模态冲突及额外延迟,证明融合收益来自有效信息,而非数据泄漏或更大参数量。

5. 机考别急着写,题干里藏的边界先圈出来。

回答:

这条提醒可以落实为编码前的约束检查:输入代表什么、输出是什么、允许怎样修改数据、规模多大,以及结果如何判定。先拿最小样例手工走一遍,比实现完成后推翻算法更省时间。

对热度排序,圈出空输入、N=0、N 超过候选数、重复 ID、同分规则、负数与溢出、先去重还是先聚合。对图文匹配,确认是否允许多个正确答案、是否存在无匹配、哪些特征在预测时可用。对链表,确认空链表、奇数长度、能否只交换值、是否可能有环。

复杂度也属于题目约束。若输入可达千万级,就要考虑内存、流式聚合和堆;若要求原地交换节点,就不能用复制整条链表的方法回避指针操作。最后用正常样例、极小样例、重复样例和反例检验不变量,而不是只运行题目给出的一个例子。

一面

6. 多模态模型遇到图文不一致的样本,训练和上线分别怎么处理?

回答:

图文不一致不一定是脏数据。它可能来自采集错配,也可能是讽刺、对比、商品局部展示,或者任务本身要求识别的误导内容。先定义任务标签:如果做跨模态检索,错配通常应作为负例;如果做内容审核,图文冲突可能正是有价值的正例,直接删掉会损害能力。

训练侧先做数据溯源与抽样审查,识别机械拼接、下载错图和标签误标。对不确定样本使用分层复核、样本权重或单独的噪声建模;构造只改变一个关键属性的困难负样本,训练实体、OCR、属性与区域级对齐,而不只是全局相似度。对真实矛盾样本保留“是否一致”及具体矛盾证据,避免让模型学到“文字永远比图片可信”。

上线侧分别提取图像事实、文本主张和可验证关系,再判定一致、冲突、信息不足。商品颜色、数量、包装版本等关键事实可通过 OCR、检测器与规则复核。低置信度时减少自动执行、请求补充或转人工,不应让模型编造一套解释把冲突抹平。

评测要分正常配对、采集错配、隐含关联、讽刺和恶意误导,分别统计误报漏报。对高风险业务,不一致分数只是一个信号,最终动作还受证据完整性、风险级别与授权策略约束。

7. 做内容理解时类别太多标注不够,你怎么办?

回答:

先治理标签体系,再增加模型复杂度。检查它是单标签还是多标签,类别是否互斥,层级关系是否合理,同义类是否重复,标注员能否依据一致规则区分相邻类别。类别定义混乱时,增加样本也会把冲突放大。

可以建立“粗类 -> 细类”的层级分类,让共享表征利用跨类别知识;用文本类别描述做零样本或少样本初始化,必要时训练检索式标签匹配。LLM 生成弱标签能提高起步速度,但需要高质量人工金标集、抽样审查和噪声估计,不能把生成标签原样当真值。

有限标注预算优先覆盖业务高风险类、长尾类和模型易混淆区域。主动学习不能只挑熵最高的样本,否则容易集中到异常和噪声;应结合不确定性、代表性、类别覆盖与样本成本。伪标签按类别设置可靠性门槛,保留低置信度未标注样本,防止头部类别吞掉长尾。

训练可使用类别重采样、损失重加权和适度的数据增强,但要检查校准变化与过拟合。长尾指标看 macro-F1、各类召回及高风险类漏报;部署增加未知类或拒识出口,并持续发现新簇。真正的目标是降低单位标注成本下的业务错误,不是让模型机械地覆盖更多标签名称。

主动学习还存在一个容易忽略的评测问题:被模型挑出来的难例不再服从自然流量分布。可以把标注预算拆成两部分,一部分用于主动选择高价值训练样本,另一部分独立随机抽样,维持稳定的评测集;高风险切片另外报告。如果把“专挑最难”的标注池直接当整体测试集,可能误判总体性能下降;如果只评模型愿意接收的样本,又可能高估质量。

8. 长文本分类,截断和摘要你选哪个,为什么?

回答:

先看决定标签的证据分布。若标题和开头通常足够,截断是低成本且容易复现的基线;若关键证据可能出现在后半段,单纯截断会产生系统性盲区。摘要能压缩信息,却可能遗漏稀有实体、否定词和少量决定性句子,也可能生成原文没有的事实。

因此不能在未评测时直接二选一。建立头部截断、首尾拼接、滑动窗口分类、抽取式证据压缩以及长上下文模型等基线。滑窗后用最大值、attention pooling 或层级网络聚合;当标签由“任一危险句出现”决定时,平均池化可能稀释关键信号。摘要路线优先保留可回指的原文证据,并让摘要目标服务于分类任务。

如果使用生成式摘要器,训练、验证和线上应使用相同版本及输入口径;不能用真实类别指导测试集摘要再评分类,否则等于把答案注入输入。也不能仅凭通用摘要指标挑模型,必须测摘要之后的分类性能和证据召回。

最终按文档长度、证据位置、多标签和稀有类别分桶,比较漏报、成本与 p95 延迟。长上下文能装下全文,也不等于模型一定利用了全文。对低风险场景可先做便宜截断,对可能含关键遗漏的样本再升级为分块或长上下文处理。

9. LoRA 讲讲,和全量微调你怎么选?

回答:

LoRA 的核心是冻结基座权重,只训练一个低秩增量。对线性层,可以写成:

$y=W_0x+\frac{\alpha}{r}BAx,\qquad A\in\mathbb{R}^{r\times d_{\mathrm{in}}},\quad B\in\mathbb{R}^{d_{\mathrm{out}}\times r}$

可训练参数由单矩阵的 $d_{\mathrm{out}}d_{\mathrm{in}}$ 变为 $r(d_{\mathrm{in}}+d_{\mathrm{out}})$。常见初始化是 A 随机、B 为零,初始增量为零,起点保持基座输出;两者同时初始化为零会导致这两个矩阵的梯度都为零。这里的低秩是假设任务更新能在较小子空间内表达,并不意味着基座矩阵本身低秩。

比较项 LoRA 全量微调
训练自由度 受 rank、目标模块和层选择约束 可更新全部参数
状态显存 显著减少可训练梯度与优化器状态 通常更高,常需分片
数据与任务 适合低成本验证、多任务适配和多版本管理 适合充分数据下更广泛的能力调整
上线形态 可保留适配器,也可条件允许时合并 通常部署独立完整权重

决策上,先明确业务差异、数据规模、遗忘风险和预算,建立同数据、可比较算力预算的实验。LoRA 若在关键长尾桶已满足要求,不必为了参数更多而全量训练;若扩大 rank 和目标层仍欠拟合,再评估部分解冻或全量微调。全量微调也不保证效果更好,数据噪声和过拟合可能让它更差。

QLoRA 进一步量化冻结基座,降低权重存储成本,同时训练低秩适配器;它不是所有前反向都用 4 bit。PEFT 还提供 rsLoRA、DoRA 等配置:rsLoRA 调整 rank 缩放,DoRA 区分幅值与方向,它们都需要实测,不能凭方法更新就断言收益。

尤其要明确:LoRA 主要减少可训练状态,不会自动消除基座权重、激活和长序列注意力的成本。 合并适配器可消除独立低秩分支,但量化权重的反量化、重合并与再量化可能改变数值精度,导出后应重跑评测。

配图文字:

LoRA:训练低秩增量,保留基座通路
以一个4096×4096的线性层、rank=16为例;仅作参数量说明。
输入x,4096维。冻结W₀,4096×4096。训练A,16×4096,压缩到16维。训练B,4096×16,映回输出空间。输出y,两路相加。
增量参数:16×(4096+4096)=131,072,占基座矩阵约0.78%。
低秩支路乘α/r;可训练参数减少,不等于整机峰值显存按同比例下降。

把参数量与显存拆开算。 对 $4096\times4096$ 的单个线性矩阵,基座有 16,777,216 个参数;rank 为 16 时,A、B 合计 131,072 个参数,比例约 0.78%。若仅统计这两个矩阵的 BF16 权重、BF16 梯度和两个 FP32 Adam 状态,按每参数 12 字节估算约 1.5 MiB;若额外保留 FP32 主权重,约 2 MiB。这里不包含基座、激活、通信缓冲及其他可训练层,不能把 0.78% 直接当作整机显存比例。

冻结权重也不代表基座前向成本消失;为了给更早的可训练模块传梯度,常规实现仍可能经过冻结层的反向计算。是否能省去特定梯度或激活,要看适配器位置、计算图和实际实现。

10. 显存炸了你有哪几招救?

回答:

先找 OOM 发生阶段:加载权重、第一次前向、反向、优化器更新,还是生成时 KV Cache 增长。记录 batch、序列长度、图像分辨率、allocated/reserved 显存及峰值,结合 profiler 或显存快照定位;不同阶段不能套同一组参数。

训练峰值可概括为权重、梯度、优化器状态、激活和临时通信/算子缓冲之和。作为估算示例,若权重和梯度各 2 字节、FP32 主权重 4 字节、Adam 两个状态共 8 字节,则模型状态约 16 字节/参数,尚未包含激活;真实实现可能没有独立主权重或采用其他 dtype,不能当成固定公式。

压力位置 优先措施 需要承担的代价
激活与长序列 缩小微批次、梯度检查点、合理截断/分桶、FlashAttention 类算子 重计算、更多步骤或输入信息损失
权重与训练状态 LoRA/QLoRA、FSDP/ZeRO、优化器状态量化、CPU offload 精度、通信带宽与吞吐折中
推理 KV Cache 控制并发 token 预算、输出长度、KV 量化和缓存调度 延迟、可接纳并发和精度变化
非预期占用 释放保留计算图的引用、正确推理模式、排查碎片与缓存 需要定位,不能靠反复重启掩盖

梯度累积通过更小微批次保持有效 batch,但不会减少固定模型状态;FSDP/ZeRO 也不能把激活峰值自动均分。FlashAttention 减少注意力中间矩阵的显存与读写,标准全注意力的计算量仍随长度近似二次增长。

empty_cache() 只能释放分配器中未被张量使用的缓存,无法释放仍被引用的张量。推理要用 inference_mode() 等关闭梯度,并限制同时在途的总 token,而非只限制请求数。优化后重新测质量、吞吐和 p99,防止“能跑”变成“非常慢且效果退化”。

推理 KV Cache 可以先做数量级估算。 对各层配置一致、K/V 维度相同的普通注意力实现,缓存主体近似为:

$M_{KV}=2L H_{KV}d_h b\sum_{j=1}^{B}T_j.$

其中 2 表示 K 和 V,L 为层数,$H_{KV}$ 为 KV 头数,$d_h$ 为头维度,b 为每个缓存元素的字节数,$T_j$ 为第 j 个请求实际保留的 token 数。设 32 层、8 个 KV 头、头维度 128、BF16 每元素 2 字节,8 个请求各保留 8192 个 token,则缓存主体约 8 GiB,尚未包括模型权重、页块浪费和工作区。这里用 KV 头数,不能误用 GQA 的查询头数。

这是普通 KV 组织的算例。滑窗、前缀共享、不同层配置、缓存量化与 MLA 等压缩表示需要改用对应实现估算。生成期间 token 会持续增长,因此准入预算应为输出预留空间;初始 batch 能加载,并不能保证生成到最大长度时仍不 OOM。

11. 你训过最大的模型多大,分布式怎么搭的?

回答:

这道题应真实报告个人经历,不能把公开训练方案讲成自己做过。可以按“模型规模与架构 -> 实际训练方式 -> 数据与硬件 -> 并行策略 -> 监控与结果 -> 个人负责范围”回答。若只做过适配器微调,就明确说明基座总参数量和实际可训练参数量,不要说成从零预训练。

例如用真实数据填入:“我实际训练的是[模型/参数量],做[全量微调或 LoRA],处理[数据与序列长度],使用[GPU 型号、数量与节点数]。我负责[明确模块],记录 tokens/s、峰值显存、验证集效果和断点恢复结果。”没有多机经验时可说明已完成的单机实验与理解的扩展方案。

技术上,DDP 在各卡复制模型,通过不同数据批次提升吞吐,不能解决单卡装不下完整模型状态的问题。FSDP 或 ZeRO 把不同训练状态分片,按需通信;张量并行拆层内计算,流水线并行拆网络层,适合更大规模,但要承担通信和流水线空泡。应根据单卡容量、机内互联、跨机带宽与序列长度选组合,而不是默认“卡多就全开”。

以 PyTorch FSDP2 为例,使用 fully_shard 对子模块及根模型分片,参数用 DTensor 表达;计算前聚合所需参数,反向中对梯度做 reduce-scatter,优化器维护分片状态。通常先做分片再构造优化器,配合分布式 checkpoint。代码与混合精度策略必须以实际 PyTorch 版本为准。

还需解释数据如何跨 rank 划分、随机种子如何管理、变长序列如何按 token 归一化损失、故障恢复如何避免样本漏读或重复。分布式训练的能力证明,应同时包含正确性、性能和可恢复性。

12. 过拟合你怎么发现,又怎么治?

回答:

典型信号是训练损失继续下降,但独立验证集指标停止改善或恶化,且差距超过随机波动。先排除数据泄漏、划分偏差、标签噪声、预处理不一致与验证集过小:它们会造成看似过拟合或掩盖真实过拟合。训练和验证任务目标不一致时,也不能仅凭损失差判断。

内容数据应按时间、作者、近重复内容等维度设计划分,具体取决于目标泛化场景;不能一边把同一篇笔记的改写放训练集,一边把原文放测试集。多随机种子、学习曲线和分桶评测能区分模型记住头部样本与真正学到通用规律。

干预顺序可以是:清洗错误与重复数据,补充高价值长尾样本;早停、减小训练轮次与学习率;使用合适的 weight decay、dropout 和语义保持的数据增强;再减少模型自由度、LoRA rank 或解冻层数。标签平滑可能改变概率校准,重采样可能改变有效类别先验,调整后需重新验证阈值。

不能把所有问题都归因于模型太大。若训练和验证都差,更可能是欠拟合、目标错误或数据缺信息;若验证好但线上差,要检查分布漂移、特征不一致和服务策略。每次只改变少数可解释因素,保存基线及回滚版本,避免用反复看测试集的方式“调出”一个失真的好成绩。

13. 评测指标除了准确率你还看什么?

回答:

指标要跟随决策后果。类别极不均衡时,把所有样本判成多数类也可能有很高准确率;分类、检索、生成和推荐不能共用一个总分替代分析。

场景 主要指标 容易忽略的解释边界
分类/审核 Precision、Recall、macro/micro-F1、PR-AUC、混淆矩阵 高风险类需单独约束漏报,PR-AUC 随正例比例变化
检索/排序 Recall@K、MRR、NDCG@K、分用户 AUC 无相关项查询、单类别用户的统计口径应明确
概率输出 Log loss、Brier score、可靠性图和 ECE 排序好不等于概率准,ECE 受分桶方式影响
生成内容 事实正确、指令遵循、完整性、安全性、人工偏好 文本相似度和模型评审分都不能直接代表事实正确
在线系统 满意度、留存、负反馈、p95/p99 延迟、单次成本 离线提升不构成线上因果收益证明

NDCG 通过位置折扣衡量相关内容是否靠前:

$\mathrm{DCG}@K=\sum_{j=1}^{K}\frac{2^{r_j}-1}{\log_2(j+1)},\qquad \mathrm{NDCG}@K=\frac{\mathrm{DCG}@K}{\mathrm{IDCG}@K}$

相关性等级的定义必须固定,IDCG 为零时明确约定跳过还是记为零。还应按新老用户、内容类型、长尾和模态质量分桶,并给出置信区间;相关样本按用户或文档聚类重采样,不能假装每次曝光都独立。

最终建立一个主目标、一组不可突破的护栏和辅助诊断指标。指标越多不等于评测越成熟,关键是知道哪种失败会被哪个指标发现,以及它会触发什么动作。

给指标一个可检查的算例。 若前三个结果的相关性等级为 [3, 1, 2],则 DCG@3 约为 $7+1/\log_2 3+3/2=9.131$;理想排序 [3, 2, 1] 的 IDCG@3 约为 9.393,NDCG@3 约为 0.972。位置折扣能区分相同候选的不同排列,但标签若只来自旧策略曝光,它仍可能偏爱旧策略看过的内容,数值归一化不会自动消除选择偏差。

14. 模型上线后被人反馈"答非所问",你怎么查?

回答:

先把用户感受转换成可复现的错误类型:意图识别错、主体或时间指代错、检索不到证据、上下文截断、工具结果错、生成忽略约束,还是展示层映射了别人的回答。不能一上来就微调模型。

在必要授权和脱敏条件下,按 trace ID 还原输入、对话状态、检索候选、重排分数、模板版本、模型版本、工具参数与返回值、截断位置和最终输出。重点检查缓存键是否包含用户/权限/上下文版本,是否有跨请求状态污染,是否把工具报错当成正常证据。

再做组件替换实验:提供人工确认的正确意图、正确检索材料或正确工具结果,观察问题在哪一层消失。若补充正确证据仍答错,关注生成与约束执行;若人工证据能修复,优先修召回、重排或知识时效。对于多模态,还要检查 OCR、图片顺序、裁剪和视觉输入是否实际送达。

把失败样本归成可维护的回归集,按严重性与覆盖面排优先级;修复后进行离线验证、影子流量和小流量灰度。对事实不足的请求提供澄清或拒答,比稳定地产生不相关内容更有价值。评估修复时同时观察误拒绝和正常任务质量,防止规则过严使系统失去可用性。

15. 数据漂移你怎么监测?

回答:

先区分输入分布变化、标签比例变化和输入到标签关系变化。输入漂移是 $P(X)$ 改变;标签漂移通常指 $P(Y)$ 改变并附带类条件分布相对稳定的假设;概念漂移涉及 $P(Y\mid X)$ 改变。它们可能同时出现,仅观察特征直方图无法证明模型质量下降。

数据层监控缺失率、异常值、类别覆盖、文本长度、图片质量、embedding 分布以及数据新鲜度。数值特征可用 KS 检验、Wasserstein 距离,离散特征可用频率差和 JS 散度;高维特征可用固定投影或两样本分类器。统计显著性受样本量影响,阈值应结合业务效应量和误报成本。

模型层监控分数分布、拒答率、置信度、召回覆盖和分桶性能。没有及时标签时,这些是预警代理,不能替代真实效果;对延迟到达的收藏、投诉、留存等标签,要按成熟窗口回填,并通过抽样人工评测弥补盲区。

比较基线应匹配星期、时段、节假日和流量入口,避免把季节性当故障。embedding 模型升级后,其分布坐标系已改变,应建立新基线或双跑对照。告警后先区分数据管道问题、产品流量变化和模型失配,再决定修管道、调阈值、补标或重训;不能看到漂移就自动上线重训模型。

二面

16. 推荐模型上线后核心指标掉了,你从哪几个环节定位?

回答:

先确认是真实退化还是测量异常:埋点是否完整、分母定义是否变化、实验是否分流失衡、标签是否尚未成熟、同期是否有运营或供给变化。若负反馈、安全或严重稳定性指标触线,应先回滚或缩流量,再持续定位。

按请求链路逐层对照:流量进入与画像 -> 多路召回 -> 过滤与去重 -> 特征服务 -> 粗排/精排 -> 重排规则 -> 缓存与展示 -> 反馈回传。记录各层数量、延迟、错误、默认值率和分布。候选从上千条异常降到几十条,优先查召回及过滤;模型输出正常但展示异常,查重排与缓存,而非继续训练。

模型侧核对权重、tokenizer、特征版本、归一化、量化、导出算子与训练目标。用同一批可重放请求对照新旧链路,分别固定候选集和排序器,定位是候选覆盖变差还是排序偏移。离线回放可帮助定位,但由于没有反事实用户行为,不能当成线上因果效果。

最后按用户活跃度、入口、主题、新内容、设备和时间分桶,检查总体指标是否受流量构成变化影响。若点击上涨但收藏和满意度下降,可能是目标设计偏向吸引点击;若延迟增加导致超时降级,实际问题是服务预算。结论必须对应可执行修复与可证伪实验。

17. 召回和排序分别怎么优化,先动哪头?

回答:

召回负责在大库中保留足够多的潜在好内容,排序负责在已召回内容中安排位置。候选里根本没有正确内容,再强的排序器也无法补救;但召回已覆盖而排序错位时,继续扩召回只会增加成本与噪声。

先做漏斗和上限分析:看各召回通道 Recall@K、独有覆盖、去重后增益与延迟;对现有候选集做理想排序或人工评估,估计排序仍有多少改善空间。真实标签有曝光偏差,“未互动”不能自动判负;需要随机探索样本、人工判定或适当的反事实估计辅助分析。

召回侧可结合协同过滤、双塔、关键词与多模态检索,改进困难负采样、新物品表征、索引更新和 ANN 检索参数;同时关注过滤前后差异。排序侧改进特征可用性、目标与损失、序列建模、校准和多目标建模,再通过蒸馏或模型压缩满足服务预算。

例如长尾笔记未进入候选,先补召回;相关内容已在候选但被高点击低质量内容挤走,先调目标与排序;单次请求等待过久导致返回兜底结果,先修延迟。多阶段系统中应以单位延迟和单位成本带来的最终增益决定资源分配,而不是默认某个模型阶段更“高级”。

配图文字:

推荐系统:沿候选漏斗定位质量损失
每一层都有自己的上限、成本与失败方式;离线指标需要线上实验验证。
1 召回与过滤:内容库→候选集C。相关内容是否进入候选?检查索引时效、过滤损失。
2 粗排与精排:候选集C→有序列表。同一候选集比较排序器。检查特征、目标与校准。
3 重排与交付:去重/多样性/展示。是否触发超时兜底?检查列表规则和最终响应。
候选上限:理想排序也受召回约束。10个相关项,仅6个进入候选。召回最多覆盖60%;排序无法补回缺项。
反馈闭环:测量的是用户真实体验。固定候选回放→定位排序差异。随机实验+护栏→验证因果收益。
示例需有可靠相关性标签;未曝光内容没有点击记录,不代表不相关。

用集合表达召回瓶颈更直观。设可靠标注的相关集合为 R,召回候选为 C,最终返回 K 项,则理想排序下相关项数量也不超过 $\min(K,|R\cap C|)$。相应的 Precision@K 上界为该数量除以 K,Recall@K 上界为该数量除以 $|R|$;分别要求 K 大于零、R 非空,并明确返回不足 K 项的口径。候选内 NDCG 使用候选内的理想排序作分母,可能已经很高,却完全不反映召回之外漏掉了多少好内容。

18. 冷启动你有什么办法,新用户新内容都算。

回答:

新用户缺少行为历史,新内容缺少曝光反馈,这是两个不同的信息缺口。共同原则是利用已有内容信息建立先验,再通过受控探索获得新证据,避免把“没有观察到”理解为“不受欢迎”。

新用户可利用自愿选择的兴趣、当前搜索或会话行为、入口与语言等必要上下文,结合多样化优质候选快速学习偏好。不要把敏感属性或未经授权的跨域数据当默认特征。随着点击、停留、收藏和负反馈积累,应快速更新短期兴趣,并防止少量偶然点击把用户锁死在单一主题。

新内容依靠文本、图像、作者可信度与主题表征获得初始召回位置,先通过质量底线,再给出有预算的探索曝光。不同内容类型采用不同反馈成熟窗口;发布几分钟没有收藏,不能和曝光充分的老内容直接比较累计互动量。

可用上下文 bandit、UCB 或 Thompson sampling 在价值与不确定性之间分配探索,但奖励延迟、位置偏差和内容间竞争会影响估计。需要记录曝光概率,建立探索流量边界和反作弊检测。离线 IPS 等评估也要求行为策略有支持覆盖,极小 propensity 会带来高方差。

最终同时看新用户留存与负反馈、新内容获得有效曝光的时间、优质内容发现率、创作者覆盖和老用户体验。冷启动不是给所有新内容无限保量,而是在质量与生态约束下高效减少不确定性。

为什么探索日志必须保留概率? 若日志策略为 $\mu$、待评估策略为 $\pi$,在单步决策、奖励定义一致、记录概率正确且有支持覆盖等条件下,IPS 估计可写为:

$\widehat V_{IPS}(\pi)=\frac1n\sum_{j=1}^{n}\frac{\pi(a_j\mid x_j)}{\mu(a_j\mid x_j)}r_j.$

目标策略会选择的动作,日志策略必须有非零概率选择。如果历史策略从不展示某类新内容,单靠已有日志无法恢复那部分收益。裁剪重要性权重可以压低方差,但会引入偏差;自归一化也改变有限样本性质。真实推荐往往一次展示整张列表,物品之间、位置之间还会相互作用,不能把单物品曝光概率未经说明地当成完整列表动作概率。

19. AB 实验你怎么设计,指标冲突听谁的?

回答:

先明确可证伪假设、实验对象、主指标、护栏、最小可检测效应和停止规则,再开始分流。通常按稳定用户 ID 随机分桶并保持组别,统计分析与随机化单元一致;推荐内容供给会相互影响时,需要评估网络干扰,考虑集群随机化或 switchback,而不是默认普通用户级实验一定无偏。

样本量由基线波动、期望效应、显著性水平和检验功效共同决定。先检查埋点和 A/A,实验过程中监控 sample ratio mismatch、跨组污染、曝光不完整与延迟标签。按完整业务周期观察,固定时点检验不要反复看普通 p 值并随时宣布胜利;需要随时监控时使用适当的序贯方法。

对于同一用户多次访问,应按用户聚合或使用聚类稳健分析。比率指标的分子分母可能同时变化,应使用合适的 delta method 或按随机化单元 bootstrap。CUPED 等方法可以用实验前变量降低方差,不能把受实验影响的后验变量随意拿来校正。

指标冲突由预先定义的决策原则处理:主指标必须体现产品长期价值,安全、严重负反馈、延迟等护栏不可随意交换;点击与收藏或留存冲突时,拆分人群、观察成熟窗口并核对机制。如果目标本身确实冲突,由业务、产品、算法共同确认可接受的权衡,记录决策依据,不能挑一个上涨指标就宣告成功。

除统计显著性,还要看收益量级、置信区间、实现成本和持续性。小流量提升未必能外推全量,尤其涉及创作者供给、竞价或社区反馈的策略,需要分阶段放量验证。

样本量先看绝对提升,再看相对提升。 对每个独立用户只有一个二元结果、两组等样本量、基线转化率近似相同的简化设计,每组所需样本量可粗估为:

$n\approx\frac{2p(1-p)(z_{1-\alpha/2}+z_{1-\beta})^2}{\delta^2}.$

取基线 p=0.10、双侧显著性水平 0.05、功效 0.80,希望检测绝对提升 0.005,也就是从 10% 到 10.5%,使用分位数约 1.96 与 0.84,可得每组约 5.65 万用户。这是简化正态近似,实际设计应使用两组备择比例、历史方差和流失等因素精算;每用户重复曝光、集群分流、多个主指标或序贯分析也需要相应修正。不能把 0.5 个百分点误写成相对增长 0.5%,也不能把曝光次数直接当作独立用户样本量。

20. 特征穿越是什么,你怎么防?

回答:

特征穿越是在训练或评估中使用了预测时不可能获得的信息。例如预测用户是否点击时使用点击后的停留时长,预测笔记曝光价值时使用曝光后累计收藏,或者用今天修正过的画像回填昨天的样本。结果是离线成绩很好,线上无法复现。

正确性至少要约束两个时间:事件何时发生,以及特征何时真正可用。对请求时刻 t,合法特征通常应满足:

$t_{\mathrm{event}}\le t,\qquad t_{\mathrm{available}}\le t$

某个行为发生在 10:00,但 10:10 才写入在线特征,不能把它用于重建 10:05 的请求。聚合窗口要相对样本时间向前截取,处理同时间事件顺序、迟到数据、时区和回填版本;标签可以使用未来观察窗口,特征不能跟着用未来信息。

训练集构建应使用 point-in-time join、带版本的历史快照或真实请求特征日志。Feast 当前文档也区分事件时间和创建时间过滤:仅按事件时间回看,未必排除后续回填修正;使用相应过滤选项前,还要确认存储支持,以及创建时间确实表示线上可用时间。工具名称不能替代数据语义审计。

目标编码、归一化统计量、词表和负采样也可能泄漏。先划分训练与验证,再在训练部分拟合统计;有监督的目标编码需要折外或严格时间处理。建立自动检查:特征时间不得晚于请求、同实体历史不可越界,并抽样重放真实线上请求验证。

配图文字:

特征时间一致性:发生过,不代表当时可用
训练样本必须复原请求当时的信息集合;后续回填不能改写历史输入。
10:00 行为发生;10:05 预测请求;10:10 特征上线可读。
仅按事件时间过滤:会泄漏。10:00≤10:05,错误地纳入历史。离线能看到未来才写入的更新。线上当时无法复现该值。
加入可用时间:重建合法输入。10:10>10:05,应排除本次更新。使用当时已可用的较早版本。记录实体键、版本与窗口边界。
合法特征需同时满足:event_time≤request_time,available_time≤request_time。
存储创建时间只有在语义确实等于“线上可用时间”时,才能直接用于此判断。

一个最小验收样例可以保存同一实体的两个版本:旧版本在 09:59 已可用,新版本事件发生于 10:00、直到 10:10 才在线可读。重建 10:05 请求时必须选旧版本,重建 10:11 请求时才可能选新版本。还要测试同时间戳并列、迟到撤回、实体键重用与缓存延迟。原始数据写入仓库的时间不一定等于特征服务可读时间,平台参数只能在这些语义已经对齐后使用。

21. 离线训练和在线服务特征不一致,怎么对齐?

回答:

先把“不一致”拆成定义不同、时间不同、版本不同和数据缺失。比如离线按天统计、线上按滑动 24 小时统计;离线缺失填 0、线上填 -1;离线 tokenizer 和线上不是同一版本。这些都会让同一模型面对不同输入分布。

为每个特征建立契约:实体键、dtype、单位、时间窗口、时区、可用时间、空值规则、枚举版本和过期策略。尽量共享转换实现,或者从同一声明式定义生成批处理与流处理;使用同一个特征平台有帮助,但不能自动消除不同执行引擎的语义差异。

上线请求抽样记录实际输入值及版本,离线按相同请求时刻重算,逐字段比较缺失率、误差、分布和延迟。离散特征通常要求严格一致,浮点聚合使用可解释容差;已知迟到数据应作为时间一致性问题单独统计,不能简单忽略。

模型、特征 schema、embedding 模型、词表、预处理和服务镜像应组成可回滚的版本包。新版本先影子运行,兼容期间双写双读或适当回填;发布时避免先上新模型却仍喂旧特征。监控在线新鲜度、默认值占比、缓存命中与缺失特征告警,确保对齐持续发生。

验收不仅看字段是否相同,还要比较模型中间输出与最终分数。如果同输入仍不同,继续排查量化、算子、数值精度和导出图。这样才能把训练到服务的一致性检查覆盖到最后一层。

22. 生成内容质量怎么评估,人工和自动两条线怎么搭?

回答:

先定义质量维度及不可接受错误。内容生成通常包括指令遵循、事实正确、完整性、表达、风格与安全;图像或视频还要加入主体一致性、局部可控、视觉缺陷、音画同步等维度。不能让一个综合分掩盖严重事实错误或关键约束失败。

自动线分为确定性校验、任务指标和模型评审。确定性检查负责格式、长度、实体保留、禁用条件和媒体规格;有参考答案的任务可看精确匹配、语义相似与事实支持,但 BLEU/ROUGE 等表面重合度不能证明真实性。LLM-as-a-judge 可做维度评分或成对偏好,但需校准位置偏差、冗长偏好与自偏好,固定版本并以人工样本检验一致性。

人工线采用清晰 rubric、盲评和随机化顺序,覆盖普通、高风险、长尾与对抗样本。部分样本双人标注,分歧交由仲裁,统计一致性并用分歧反向完善规则。评审者需要看到必要证据,不能仅凭语言流畅判断正确。

两条线通过固定金标集与持续回流集连接:自动评估用于快速回归,人工用于发现盲区、核查阈值和评估新能力。评测器本身也要防止被候选文本中的指令诱导,把生成内容当待评数据而非执行指令。

上线用成对实验或产品 A/B 验证编辑率、任务完成率、满意度与负反馈。分享或点击可能来自争议内容,不能直接当质量真值;复杂目标应报告各维度、最差桶和成本,保留可追溯的失败样本。

23. 多目标排序你怎么融合,权重谁定?

回答:

先确定目标的语义与依赖关系。点击、收藏、转化、停留和负反馈对应不同价值,量纲与取值范围也不同。若模型预测的是“点击后转化概率”,曝光转化概率需结合点击概率;不能把条件概率直接当作无条件转化率相加。

一个可解释基线是在统一预期效用口径下融合:

$S(u,i)=\sum_{k=1}^{K}w_k\hat v_k(u,i)$

其中 $\hat v_k$ 是校准后的期望价值,可包含负反馈的负效用。若直接加不同量纲的原始分数,权重会同时承担价值判断与尺度校正,模型升级后很容易失效。也可使用乘法或分段效用,但零值、量纲和可解释性问题需要额外处理。

更明确的业务形式是约束优化:

$\max_\pi\ \mathbb{E}{\pi}[U] \quad\mathrm{s.t.}\quad \mathbb{E}{\pi}[C_j]\le b_j$

例如在负反馈和延迟不超过边界的前提下提升长期满意度。单条物品打分还不能满足列表多样性、作者曝光和相似内容去重,需要列表级重排;某些安全底线应直接约束候选资格,而非只赋一个可被高点击抵消的惩罚项。

权重由业务目标、产品取舍与实验结果共同决定,算法负责校准、敏感性分析、Pareto 权衡和稳定实现。多任务网络的训练损失权重,与线上价值融合权重是不同参数,不能混为一谈。动态权重也要在边界内个性化,避免长期反馈把系统推向单一刺激目标。

多任务学习还要分清两个优化层次。训练损失中的权重决定共享参数接受哪些梯度,例如点击样本量远多于收藏时,点击梯度可能主导共享表示;线上融合权重表达业务对不同结果的价值取舍。调整前者可能改变后者对应的概率校准,不能用一次修改同时声称解决训练冲突和商业目标冲突。MMoE、PLE 或梯度平衡可以作为实验方案,但应与单任务基线对照,再按用户和目标分桶检查负迁移。

例如候选 A 的点击概率更高,候选 B 的收藏概率更高,最终顺序必须依赖明确效用和护栏;如果缺少价值定义,模型结构本身无法给出唯一正确的排序。可以先绘制点击、收藏、负反馈和成本之间的 Pareto 前沿,剔除被其他方案全面支配的配置,再对剩余方案开展受控实验。

24. 你做过最失败的一次模型迭代是什么?

回答:

用真实案例讲清楚错误假设及修复过程,比虚构一个漂亮的失败故事更有说服力。建议按“目标与基线 -> 当时的假设 -> 做了什么 -> 哪些证据证明失败 -> 自己承担什么责任 -> 如何止损 -> 沉淀了什么机制”回答。

可套用的表达框架是:“那次我希望改善[真实指标],判断瓶颈在[真实假设],于是修改了[模型/数据/目标]。离线[指标及分桶]发生了变化,但在[影子验证/灰度/回归集]中出现[真实问题]。我负责排查的部分是[范围],最终发现[证据支持的原因],随后[回滚或修复]。”所有数据和经历都需来自实际项目。

例如“离线 AUC 上涨但线上满意度下降”可以作为分析题:可能是数据穿越、曝光偏差、校准变化、长尾退化或延迟增加,不能仅凭结果就断言目标错了。应说明如何通过时间重放、分桶、组件替换或实验逐个排除,并报告最终证据。

复盘重点是可复用改进:补齐上线护栏、建立时间一致性检查、设置分桶验收、缩小发布批次、明确回滚负责人。不要把失败完全归咎于产品、数据或同事;同时也不必把所有问题都揽成个人失误,应准确表达自己的决策与协作范围。

HR 面与反问

25. 你为什么想来我们这,对业务了解多少?

回答:

从产品理解、岗位问题与真实能力匹配三层回答。小红书作为内容社区,用户既会主动搜索问题与经验,也会在信息流中发现图文和视频;内容理解、搜索推荐、创作者供给和可信体验彼此影响。不要在不了解具体团队时断言其内部模型架构、指标或组织安排。

可以这样组织:“我对内容社区感兴趣,因为图文语义、用户意图和推荐反馈会直接影响内容是否被准确理解、是否被需要它的人看到。我希望参与[与招聘描述一致的方向],自己的[真实项目]在[多模态/检索/训练部署]方面有相关经验,也希望进一步学习从离线效果到线上用户价值的验证方法。”

准备时亲自体验不同入口,挑一个具体问题,例如长尾搜索、新内容发现或图文不一致,描述观察与可能机制,并说明这只是外部产品观察。再结合岗位要求解释能承担什么、仍需补齐什么。兴趣、能力和工作内容能互相对应,比笼统赞美平台更可信。

26. 你期望的团队氛围是什么样的?

回答:

把抽象偏好转为可观察的协作方式。例如:“我希望目标和验收标准清楚,技术讨论能基于实验和事实,不同观点可以充分表达;遇到不确定任务时能及时反馈风险,出问题先定位与恢复,再复盘责任和流程。”

算法团队还需要允许合理试错,但试错应有预算、时间边界与可复现记录。可以补充对 code review、实验评审、文档和导师反馈的期待,同时说明自己会如何贡献:提前对齐假设、同步阻塞、留下交接材料,尊重工程和产品约束。

若有工作节奏或实习安排的真实限制,应坦诚表达,不必为了显得投入而承诺无法做到的工作方式。团队氛围的匹配是双向选择,可以用具体追问核实“开放沟通”和“重视成长”在日常如何体现。

27. 反问:这个组的模型迭代节奏多快,有没有评测基建?

回答:

这是向面试官获取信息的问题,不能代替对方编造团队答案。可以把它展开成对迭代质量的追问:“一次模型改动通常经历哪些阶段?离线回归、人工评测、影子流量、A/B 和全量发布分别有什么门槛?”

再根据回答了解评测集如何更新、困难样本如何沉淀、模型和数据版本能否追溯、失败是否有回滚机制,以及实习生能负责哪些环节。迭代周期短不必然成熟;如果高频发布却没有稳定指标,结果可能无法归因。反之,高风险任务的审核较慢也可能是合理约束。

判断重点是团队是否形成“提出假设 -> 可复现实验 -> 风险受控上线 -> 反馈修正”的循环,而非只比较训练频率。反问保持开放与尊重,根据面试官回答追问一两个关键点即可。

28. 反问:算法和工程在这边怎么协作?

回答:

可以询问一条模型需求从提出到上线的责任分工:谁定义数据和指标,谁维护特征与训练,谁负责服务接口、容量、灰度和故障回滚。进一步了解算法同学是否参与线上问题定位,以及工程约束在立项时还是上线前才进入讨论。

成熟协作通常会明确模型输入输出契约、特征 schema、版本兼容、延迟预算、监控与验收负责人;这是一组判断标准,不是对具体团队现状的断言。若对方提到模型交付给工程后经常效果不一致,可以追问是否有同输入重放和联合验收机制。

也可以说明自己的偏好:“我希望对训练到服务的结果负责,愿意参与联调和回归;遇到成本、延迟与效果冲突时,希望共同基于业务约束做取舍。”表达合作意愿的同时,确认岗位实际边界和可获得的指导。

29. 同时接三个需求你先做什么?

回答:

先确认是否存在生产故障、安全风险或不可延期的外部承诺,这类需求通常需要优先止损。其他需求比较用户价值、覆盖范围、截止时间、依赖、实现成本与不确定性,并与负责人对齐优先级,而不是按谁催得最急排序。

一个可操作流程是:先用短时间澄清三个需求的目标与验收标准;识别能复用的公共工作及关键路径;对高不确定需求安排小实验,避免直接投入完整开发;将可延期部分拆成后续版本。可借用 RICE 等评分辅助讨论,但不要把主观输入算出的分数当客观真理。

随后给出明确排期、交付边界与风险,保证在途任务数量受控。若资源只能完成其中两个,应及时提出取舍、减范围或协作方案,而非对三个都承诺原期限。优先级发生变化时同步影响并保存决策依据,减少隐性切换带来的成本。

手撕

30. 【手撕】手写链表两两交换节点,注意别把指针弄断,并说清空链表、单节点这两个边界。

回答:

题目要求交换节点连接关系,不是只交换节点值。使用哑节点 dummy 统一处理头节点变更;prev 始终指向下一组待交换节点的前驱。若当前结构是 prev -> first -> second -> rest,保存 rest 后依次改成 prev -> second -> first -> rest,最后让 prev 移到 first。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def swap_pairs(head):
    dummy = ListNode(next=head)
    prev = dummy
    while prev.next is not None and prev.next.next is not None:
        first = prev.next
        second = first.next
        rest = second.next

        first.next = rest
        second.next = first
        prev.next = second
        prev = first
    return dummy.next

循环不变量是:prev 之前的所有完整二元组已经交换,prev.next 开始的后缀仍保持原顺序。每轮只修改当前两个节点及前驱的连接,不创建替代节点,也不丢失剩余后缀;循环结束时后缀不足两个节点,因此无需交换。

输入 1 -> 2 -> 3 -> 4 -> 5,输出 2 -> 1 -> 4 -> 3 -> 5。空链表返回 None;单节点直接返回原节点;奇数长度最后一个节点保留;重复值不影响正确性,因为交换的是节点身份。假设输入是有限无环单链表,若可能有环,需先检测并定义处理策略。

时间复杂度 O(n),额外空间 O(1)。验证时除了比较值序列,还要确认节点对象一个不少、没有新环、最后一个奇数节点未变,且连续交换两次可以恢复原始节点顺序。这样才能防止“值看起来对了,但指针结构已经错了”的隐藏错误。

配图文字:

链表两两交换:保存后缀,再改连接
节点身份保持不变;用dummy统一处理头节点,prev记录下一组前驱。
交换前:prev→first→second→rest。
先保存rest,再执行first.next=rest、second.next=first、prev.next=second。
交换后:prev→second→first→rest。
完成后prev移到first;每轮只处理一对,剩余不足两个节点时结束。
验证节点对象无增减、无环、值未修改;连续交换两次应恢复原始节点顺序。

本篇目录
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06
  7. 07
  8. 08
  9. 09
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
    【手撕】手写链表两两交换节点,注意别把指针弄断,并说清空链表、单节点这两个边界。

同作者面经

展开其余 6 篇收起其余 6 篇

相关公司