主题切换
20.1 数学与统计面试题
量化面试中数学和统计是最基础也最核心的考察维度。面试官不仅考察你能否算出正确答案,更看重你的推理过程和概率直觉。本章系统梳理高频考点,附 Python 模拟验证代码,帮助你建立从理论到实践的理解。
概率题
条件概率与贝叶斯定理
条件概率是量化面试中出现频率最高的数学题型之一。贝叶斯定理是解决条件概率问题的核心工具:
$$P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)} = \frac{P(B|A) \cdot P(A)}{P(B|A)P(A) + P(B|\neg A)P(\neg A)}$$
典型题:疾病检测问题
一种疾病在人群中的发病率为 1%。检测方法的准确率为 99%(即患者检测为阳性的概率为 99%,健康人检测为阴性的概率为 99%)。问:一个人检测为阳性,他真正患病的概率是多少?
这道题的核心陷阱在于忽略先验概率。直觉上很多人会回答 99%,但正确概率只有约 50%:
$$P(\text{患病}|\text{阳性}) = \frac{0.99 \times 0.01}{0.99 \times 0.01 + 0.01 \times 0.99} = 0.5 = 50\%$$
原因是健康人群基数(99%)远大于患病人群(1%),即使假阳性率只有 1%,产生的假阳性人数也等于真阳性人数。
此处有展示代码展开 ▼
python
import numpy as np
def simulate_disease_test(n_population: int = 100000,
prevalence: float = 0.01,
sensitivity: float = 0.99,
specificity: float = 0.99) -> dict:
"""蒙特卡洛模拟疾病检测问题"""
# 生成人群
n_sick = int(n_population * prevalence)
n_healthy = n_population - n_sick
# 检测结果
# 患病者检测阳性概率 = sensitivity
sick_positive = np.random.binomial(1, sensitivity, n_sick).sum()
# 健康者检测阳性概率 = 1 - specificity
healthy_positive = np.random.binomial(1, 1 - specificity, n_healthy).sum()
total_positive = sick_positive + healthy_positive
true_positive_rate = sick_positive / total_positive if total_positive > 0 else 0
theoretical = (sensitivity * prevalence) / \
(sensitivity * prevalence + (1 - specificity) * (1 - prevalence))
return {
'simulated_rate': true_positive_rate,
'theoretical_rate': theoretical,
'n_sick': n_sick,
'n_healthy': n_healthy,
'sick_positive': sick_positive,
'healthy_positive': healthy_positive,
'total_positive': total_positive
}
# 运行模拟
result = simulate_disease_test()
print(f"理论概率: {result['theoretical_rate']:.4f}")
print(f"模拟概率: {result['simulated_rate']:.4f}")
print(f"检测阳性总人数: {result['total_positive']}")
print(f"其中真患病者: {result['sick_positive']}")
print(f"其中假阳性: {result['healthy_positive']}")点击展开可浏览运行结果
理论概率: 0.5000 模拟概率: 0.5071 检测阳性总人数: 1960 其中真患病者: 994 其中假阳性: 966
经典概率问题集锦
Monty Hall 问题(三门问题)
有三扇门,其中一扇后面有汽车,另外两扇后面是山羊。参赛者选择一扇门后,主持人(知道汽车在哪)会打开另一扇有山羊的门,然后问参赛者是否换门。问:换门是否增加获胜概率?
答案:换门获胜概率为 2/3,不换为 1/3。核心在于主持人的行为不是随机的——他一定不会打开有汽车的门。
此处有展示代码展开 ▼
python
def simulate_monty_hall(n_trials: int = 100000) -> dict:
"""蒙特卡洛模拟三门问题"""
stay_wins = 0
switch_wins = 0
for _ in range(n_trials):
doors = [0, 0, 0]
car = np.random.randint(0, 3)
doors[car] = 1 # 1 表示汽车
# 参赛者初始选择
choice = np.random.randint(0, 3)
# 主持人打开一扇有山羊的门(不是参赛者的选择,也不是汽车)
available = [i for i in range(3) if i != choice and doors[i] == 0]
opened = np.random.choice(available)
# 换门选择
other = [i for i in range(3) if i != choice and i != opened][0]
if doors[choice] == 1:
stay_wins += 1
if doors[other] == 1:
switch_wins += 1
return {
'stay_win_rate': stay_wins / n_trials,
'switch_win_rate': switch_wins / n_trials
}点击展开可浏览运行结果
Monty Hall 问题模拟 (n=10,000) 坚持原选择胜率: 33.3% 换门胜率: 66.7% 结论: 换门的获胜概率是坚持的 2.0 倍
生日问题
一个房间至少需要多少人,才能使得至少两人同一天生日的概率超过 50%?
答案:只需要 23 人。直觉上这个数字小得惊人。
$$P(\text{至少两人同生日}) = 1 - \frac{365 \times 364 \times ... \times (365-n+1)}{365^n}$$
此处有展示代码展开 ▼
python
def birthday_probability(n_people: int, days: int = 365) -> float:
"""计算n人中至少两人同生日的概率"""
prob_all_different = 1.0
for i in range(n_people):
prob_all_different *= (days - i) / days
return 1 - prob_all_different
def find_birthday_threshold(target_prob: float = 0.5, days: int = 365) -> int:
"""找到达到目标概率所需的最小人数"""
prob = 0
n = 1
while prob < target_prob:
n += 1
prob = birthday_probability(n, days)
return n
print(f"23人生日概率: {birthday_probability(23):.3f}")
print(f"50%概率需要人数: {find_birthday_threshold(0.5)}")点击展开可浏览运行结果
📘 本段代码定义了 2 个函数/类:函数 `birthday_probability`(计算n人中至少两人同生日的概率)、函数 `find_birthday_threshold`(找到达到目标概率所需的最小人数)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
赌徒破产问题
一个赌徒初始有
当
$$P(\text{破产}|\text{初始资金}=i) = \frac{(q/p)^i - (q/p)^N}{1 - (q/p)^N}$$
当
$$P(\text{破产}|\text{初始资金}=i) = 1 - \frac{i}{N}$$
关键洞见:即使
统计题
假设检验
名词解释:假设检验
假设检验(Hypothesis Testing)是统计推断的核心方法。它通过样本数据来判断关于总体的某个假设是否成立。框架包括:原假设
p 值的正确定义:
p 值是"在原假设为真的条件下,观察到当前检验统计量或更极端值的概率"。它不是"原假设为真的概率",也不是"效应量的度量"。
常见的面试误区:
- p 值不是原假设为真的概率(那是贝叶斯后验概率)。
- p < 0.05 只是说"如果原假设为真,这样的结果不太可能出现",不能说"备择假设为真的概率是 95%"。
- p 值受样本量影响:样本量足够大时,任何微小的效应都能产生显著的 p 值。
置信区间
95% 置信区间的正确解释:
如果我们重复抽样无限多次,对每个样本计算 95% 置信区间,那么这些区间中有 95% 会包含真实的总体参数。
NOT:有 95% 的概率真值落在给定的区间内。(这个表述实际上是在做贝叶斯推断。)
均值的置信区间:
$$\bar{X} \pm t_{\alpha/2, n-1} \cdot \frac{s}{\sqrt{n}}$$
其中
线性代数核心
特征值与特征向量:
$$A\mathbf{v} = \lambda \mathbf{v}$$
在量化领域的应用:
- PCA(主成分分析):协方差矩阵的特征向量对应主成分方向,特征值对应方差解释量。
- 因子模型:对协方差矩阵进行特征分解,最大的几个特征值对应的因子可以解释大部分风险。
- 投资组合优化:最小方差组合与协方差矩阵的特征分解密切相关。
PCA 推导要点:
- 数据中心化:
- 计算协方差矩阵:
- 特征分解:
- 投影:
(选取前 个特征向量)
此处有展示代码展开 ▼
python
import numpy as np
from numpy.linalg import eig
def pca_from_scratch(X: np.ndarray, n_components: int = 2) -> dict:
"""
手动实现 PCA
Parameters
----------
X : np.ndarray
数据矩阵,shape (n_samples, n_features)
n_components : int
保留的主成分数量
"""
n_samples = X.shape[0]
# 1. 中心化
X_centered = X - X.mean(axis=0)
# 2. 协方差矩阵
cov_matrix = X_centered.T @ X_centered / (n_samples - 1)
# 3. 特征分解
eigenvalues, eigenvectors = eig(cov_matrix)
# 4. 按特征值降序排列
idx = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[idx].real
eigenvectors = eigenvectors[:, idx].real
# 5. 投影到前 k 个主成分
W = eigenvectors[:, :n_components]
X_pca = X_centered @ W
# 6. 方差解释比例
explained_variance_ratio = eigenvalues[:n_components] / eigenvalues.sum()
return {
'transformed': X_pca,
'components': W,
'eigenvalues': eigenvalues[:n_components],
'explained_variance_ratio': explained_variance_ratio
}
# 测试PCA
np.random.seed(42)
X = np.random.randn(100, 5)
X[:, 1] = X[:, 0] * 0.8 + np.random.randn(100) * 0.2 # 添加相关性
result = pca_from_scratch(X, n_components=2)
print(f"前2个主成分的方差解释比例: {result['explained_variance_ratio']}")
print(f"累计解释: {result['explained_variance_ratio'].sum():.3f}")点击展开可浏览运行结果
前2个主成分的方差解释比例: [0.32906059 0.24912032] 累计解释: 0.578
时间序列核心概念
平稳性(Stationarity):一个时间序列的均值和方差不随时间变化,且协方差只依赖于时间间隔。
单位根检验(ADF检验):检验时间序列是否存在单位根(即是否非平稳)。
协整(Cointegration):两个或多个非平稳时间序列的线性组合可能是平稳的。这是配对交易策略的统计学基础。
此处有展示代码展开 ▼
python
from scipy import stats
def test_stationarity(series: np.ndarray) -> dict:
"""手动实现简化的 DF 检验"""
n = len(series)
delta_y = np.diff(series)
y_lag = series[:-1]
# 回归: delta_y_t = gamma * y_{t-1} + e_t
# 如果 gamma = 0,则存在单位根(非平稳)
slope, intercept, r_value, p_value, std_err = stats.linregress(y_lag, delta_y)
# DF 检验的临界值(比标准 t 分布更负)
# 简化处理:使用 t 统计量
se = std_err
t_stat = slope / se
# 近似的 DF 分布临界值(n=100 时约为 -2.89 at 5%)
# 这里用绝对值粗略判断
return {
'test_statistic': t_stat,
'p_value': p_value,
'is_stationary': p_value < 0.05,
'note': 'p值可能不精确,建议使用 statmodels.adfuller'
}点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `test_stationarity`(手动实现简化的 DF 检验)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
常见面试题速查
线性回归的前提假设是什么?
- 线性关系(Linearity):因变量与自变量之间是线性关系
- 独立性(Independence):观测值之间相互独立
- 同方差性(Homoscedasticity):误差项的方差恒定
- 正态性(Normality):误差项服从正态分布
- 无多重共线性(No Multicollinearity):自变量之间不能高度相关
中心极限定理是什么? 当样本量足够大时,样本均值的分布近似服从正态分布,无论原始总体的分布是什么。这是为什么正态分布如此重要,也是很多统计检验的理论基础。
量化面试中的数学和统计考察重在"概率直觉"和"概念理解"而非机械计算。使用 Python 模拟验证你的推理是一个很好的习惯——既能确认答案正确性,也能在面试中展示你的计算思维。