目录
正在加载目录...

TikTok MLE OA|题型变了,7 选择 + 3 道大模型/算法实现

刚做完 TikTok MLE 的 OA,发现今年题型变了——以前都是纯 4 道 LC 风格题,这次变成 7 道选择题加 3 道机器学习算法实现题,格局完全不一样。选择题考的是 ML 基础和 CS 基础的结合,实现题按题面把核心步骤补全即可。整体难度不算高,稳过没问题,但得提前准备对方向——L1/L2 正则化、F1 Score 的手算、self-attention 的复杂度这些,进去前没想过是会考的东西。

TikTok MLE OA|题型变了,7 选择 + 3 道大模型/算法实现

前七题:选择题还原

七道选择题分两个方向——CS 基础和 ML 基础各占一半左右。我做的时候每题大概 1 到 2 分钟,留够时间给后面三道实现题。

Q1:TCP 和 UDP 的区别

题目问在以下哪个场景下应该选择 UDP 而不是 TCP——视频直播、文件下载、银行转账、邮件发送。

答案是视频直播。UDP 不保证顺序和可靠性,但延迟低,实时性要求高的场景(直播、在线游戏、语音通话)用 UDP 更合适;文件下载和金融交易对数据完整性要求高,必须用 TCP。

Q2:数据库索引

题目问 B 树索引和哈希索引的适用场景有什么区别,以下说法哪个正确——A. 哈希索引支持范围查询;B. B 树索引只适合等值查询;C. 哈希索引适合等值查询,B 树索引适合范围查询;D. 两者性能完全一样。

答案是 C。哈希索引对等值查询 O(1),但不支持范围查询(因为哈希之后顺序信息丢失了);B 树是有序结构,天然支持范围查询和排序。

Q3:进程和线程

题目问以下关于进程和线程的说法哪个正确——A. 进程共享同一块内存空间;B. 线程切换的开销比进程切换更大;C. 同一进程内的线程共享堆内存,但各自有独立的栈;D. 线程崩溃不会影响其所属的进程。

答案是 C。进程有独立的内存空间,进程内的线程共享堆(代码段、全局变量、文件句柄),但每个线程有自己的栈和寄存器状态。线程崩溃可能拖垮整个进程(比如空指针异常),所以 D 是错的。

Q4:L1 和 L2 正则化

题目问 L1 正则化和 L2 正则化的核心区别是什么——A. L1 产生稀疏权重,L2 使权重均匀缩小;B. L1 使权重均匀缩小,L2 产生稀疏权重;C. 两者都会产生稀疏权重;D. L2 正则化比 L1 更容易过拟合。

答案是 A。L1(绝对值惩罚)的梯度是常数,会把小权重直接推到 0,产生稀疏解,适合特征选择;L2(平方惩罚)的梯度和权重大小成正比,权重越大惩罚越强,最终使权重均匀缩小但不会变成精确的 0。

Q5:模型评估指标

题目给了一个二分类模型的混淆矩阵——TP=90,FP=10,FN=30,TN=70,问 F1 Score 是多少,选项是 A. 0.75,B. 0.818,C. 0.80,D. 0.857。

Precision = 90 / (90+10) = 0.9;Recall = 90 / (90+30) = 0.75;F1 = 2 × 0.9 × 0.75 / (0.9+0.75) = 1.35 / 1.65 ≈ 0.818。答案是 B。

这道题进去之前最好把 F1 的公式背熟,考场上现推容易算错。

Q6:梯度消失问题

题目问以下哪种方法不能缓解深度神经网络的梯度消失问题——A. 使用 ReLU 代替 Sigmoid 激活函数;B. 增加网络层数;C. 使用残差连接(ResNet);D. 使用 BatchNorm。

答案是 B。增加网络层数反而会加剧梯度消失,因为反向传播时梯度需要经过更多层的连乘,每层梯度小于 1 就会指数级衰减。ReLU 避免了 Sigmoid 的饱和区问题,残差连接提供了梯度的”高速公路”,BatchNorm 稳定了每层的输入分布,三者都是有效的缓解手段。

Q7:Self-attention 的计算复杂度

题目问 Transformer 中 Self-attention 的时间复杂度是多少,其中 n 是序列长度,d 是特征维度——A. O(n×d);B. O(n²×d);C. O(n×d²);D. O(n²+d)。

答案是 B。Self-attention 要对每对位置计算相似度(Q×Kᵀ),这一步是 O(n²×d)——n 个 query,每个和 n 个 key 做点积,每次点积是 O(d)。这也是为什么长序列 Transformer 的计算开销很大,各种高效 attention 变体(Linformer、FlashAttention)都在试图降低这个复杂度。

第 8 题:最长连续相同字符子串

TikTok MLE OA|题型变了,7 选择 + 3 道大模型/算法实现

题意

给定小写字符串 source,找由同一字符构成的最长连续子串;若有多段长度相同,取最靠右的那段。返回该字符加长度,例如 “c3″。

示例:bbacccdbbab → 最长是连续 3 个 c,输出 “c3″。

思路

在字符串末尾加一个哨兵(如 #),一次遍历收尾:维护当前连续段长度,以及全局最长答案(字符加长度)。当 s[i] != s[i-1] 时,用刚结束的那段更新答案——长度优先,同长度取更靠右的(所以用 >=)。哨兵保证最后一段也会被结算。

def longest_consecutive(source):
    s = source + '#'
    best_char, best_len = s[0], 0
    cur_len = 1
    for i in range(1, len(s)):
        if s[i] == s[i-1]:
            cur_len += 1
        else:
            if cur_len >= best_len:  # >= 保证同长取最右
                best_len = cur_len
                best_char = s[i-1]
            cur_len = 1
    return f"{best_char}{best_len}"

O(n) 扫完,注意同长度取最靠右的用 >= 而不是 >。

第 9 题:线性模型 / 梯度更新

TikTok MLE OA|题型变了,7 选择 + 3 道大模型/算法实现

题意

补全线性回归的训练代码:根据均方误差(MSE)算梯度,不断调整权重和偏置,使预测逐渐接近真实值。

思路

线性回归梯度下降的四步——前向、损失、反传、更新:

def train_step(X, y, w, b, lr):
    n = len(y)
    # 前向
    y_pred = X @ w + b
    # 损失(MSE)
    loss = np.mean((y_pred - y) ** 2)
    # 反传(MSE 对 w、b 的梯度)
    error = y_pred - y
    dw = (2 / n) * X.T @ error
    db = (2 / n) * np.sum(error)
    # 更新
    w -= lr * dw
    b -= lr * db
    return w, b, loss

注意样本维度:X 是 (n, d),y 是 (n,),w 是 (d,),矩阵乘法方向别写反。MSE 的梯度系数是 2/n,有些题面写的是 1/n(用 MAE 或不带 2 的 MSE),按题面给的公式来。

第 10 题:决策树(熵、信息增益、预测)

TikTok MLE OA|题型变了,7 选择 + 3 道大模型/算法实现

题意

从零实现决策树分类的关键部分(不额外导包):用熵衡量节点纯度,用信息增益选最佳划分,预测时按特征值走左/右子树。

公式:

熵:E = -Σ pᵢ log₂ pᵢ

信息增益:IG = E_parent – (l/n × E_left + r/n × E_right)

思路

import math
from collections import Counter

def entropy(labels):
    n = len(labels)
    if n == 0:
        return 0
    counts = Counter(labels)
    return -sum((c/n) * math.log2(c/n) for c in counts.values() if c > 0)

def best_split(X, y):
    parent_entropy = entropy(y)
    best_ig, best_feat, best_val = -1, None, None
    n = len(y)
    for feat_idx in range(len(X[0])):
        thresholds = set(row[feat_idx] for row in X)
        for val in thresholds:
            left_y = [y[i] for i in range(n) if X[i][feat_idx] <= val]
            right_y = [y[i] for i in range(n) if X[i][feat_idx] > val]
            if not left_y or not right_y:
                continue
            ig = parent_entropy - (
                len(left_y)/n * entropy(left_y) +
                len(right_y)/n * entropy(right_y)
            )
            if ig > best_ig:
                best_ig, best_feat, best_val = ig, feat_idx, val
    return best_feat, best_val

def predict_one(node, x):
    # node 是 InnerNode 或叶子,按题面结构走
    while hasattr(node, 'feature_idx'):
        if x[node.feature_idx] <= node.split_value:
            node = node.left
        else:
            node = node.right
    return node.label  # 叶子节点返回类别

题面已给出 InnerNode 和叶子节点的结构,按步骤补全 fit / predict 即可。注意熵计算时 p = 0 的项要跳过(log₂0 是负无穷)。

补充:Bagging 实现题

部分场次会额外出现 Bagging 补全题:

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from collections import Counter

class BaggingClassifier:
    def __init__(self, n_estimators=10, random_state=42):
        self.n_estimators = n_estimators
        self.random_state = random_state
        self.estimators = []

    def fit(self, X, y):
        rng = np.random.RandomState(self.random_state)
        n = len(y)
        for _ in range(self.n_estimators):
            # Bootstrap:有放回抽样
            indices = rng.choice(n, size=n, replace=True)
            X_boot, y_boot = X[indices], y[indices]
            clf = DecisionTreeClassifier()
            clf.fit(X_boot, y_boot)
            self.estimators.append(clf)

    def predict(self, X):
        # 各基分类器投票,取多数类
        all_preds = np.array([clf.predict(X) for clf in self.estimators])
        result = []
        for col in all_preds.T:
            result.append(Counter(col).most_common(1)[0][0])
        return np.array(result)

注意随机种子要在 fit 里统一管理,平票规则按题面处理(most_common 默认取第一个)。

几点体会

这场最大的变化是选择题里 Transformer 相关内容的比重明显增加——self-attention 的计算方式、多头注意力的作用、位置编码的意义都出现过,和 TikTok 这两年在 LLM 推荐系统上的投入方向一致。

三道实现题的核心是”能不能按定义把经典算法写对”,不是刷 LC Hard。把熵的公式、MSE 梯度、Bootstrap 采样这几个模板提前写熟,进去主要是读题和对接口,时间完全够用。

有在准备 TikTok MLE 或其他大厂 MLE 岗的同学,可以来找我们聊聊。InterviewShow 整理了 TikTok、字节跳动、Google 这些公司的 MLE 面试题型,决策树、梯度下降、Transformer 基础这几块都有专门覆盖,有需要的来聊。

END