← 返回 Day 197 正文

Day 197 · 图解

Day 197 图解 — 面试准备

所有图都可用 Mermaid Live(https://mermaid.live)或支持 Mermaid 的 Markdown 渲染器打开。 最后一张是纯 ASCII,方便在终端里直接看。


1. 面试准备的闭环

flowchart LR A[题库 12 题] --> B[限时作答] B --> C{四维评分} C -->|概念覆盖不足| D[补知识缺口] C -->|技术深度不足| E[补因果与边界] C -->|结构不足| F[改表达模板] C -->|简洁度不足| G[删冗余] D --> H[重答] E --> H F --> H G --> H H --> C C -->|稳定 ≥60| I[进入追问演练] I --> J[项目深挖 / 系统设计]
flowchart LR
    A[题库 12 题] --> B[限时作答]
    B --> C{四维评分}
    C -->|概念覆盖不足| D[补知识缺口]
    C -->|技术深度不足| E[补因果与边界]
    C -->|结构不足| F[改表达模板]
    C -->|简洁度不足| G[删冗余]
    D --> H[重答]
    E --> H
    F --> H
    G --> H
    H --> C
    C -->|稳定 ≥60| I[进入追问演练]
    I --> J[项目深挖 / 系统设计]

要点:评分不是为了打分,而是为了决定下一轮改什么。 没有反馈的重复练习,只是把同一个错误练得更熟练。


2. GIL 下的 CPU 密集 vs IO 密集

sequenceDiagram participant T1 as 线程 1 participant T2 as 线程 2 participant G as GIL participant CPU as CPU 核 participant IO as 内核/IO Note over T1,CPU: CPU 密集型 T1->>G: 申请 GIL G-->>T1: 获得 T1->>CPU: 执行字节码 T2->>G: 阻塞等待 T1->>G: 释放(时间片到) G-->>T2: 获得 T2->>CPU: 执行字节码 Note over T1,CPU: 任一时刻只有一个线程在跑 → 无多核加速 Note over T1,IO: IO 密集型 T1->>IO: read() 阻塞 T1->>G: 主动释放 GIL G-->>T2: 获得 T2->>IO: read() 阻塞 T2->>G: 主动释放 Note over T1,IO: 等待期间 GIL 空闲 → 多线程有效
sequenceDiagram
    participant T1 as 线程 1
    participant T2 as 线程 2
    participant G as GIL
    participant CPU as CPU 核
    participant IO as 内核/IO

    Note over T1,CPU: CPU 密集型
    T1->>G: 申请 GIL
    G-->>T1: 获得
    T1->>CPU: 执行字节码
    T2->>G: 阻塞等待
    T1->>G: 释放(时间片到)
    G-->>T2: 获得
    T2->>CPU: 执行字节码
    Note over T1,CPU: 任一时刻只有一个线程在跑 → 无多核加速

    Note over T1,IO: IO 密集型
    T1->>IO: read() 阻塞
    T1->>G: 主动释放 GIL
    G-->>T2: 获得
    T2->>IO: read() 阻塞
    T2->>G: 主动释放
    Note over T1,IO: 等待期间 GIL 空闲 → 多线程有效

一句话:GIL 限制的是「同时执行 Python 字节码」,不是「同时等待 IO」。


3. 迭代器协议状态机

stateDiagram-v2 [*] --> Iterable: 实现 __iter__ Iterable --> Iterator: iter(obj) Iterator --> Yield: next(it) Yield --> Iterator: 还有元素 Yield --> Exhausted: StopIteration Exhausted --> [*] note right of Iterable 每次 __iter__ 返回新的迭代器 list / dict / str 都是可迭代对象 end note note right of Iterator 实现 __iter__ 返回 self 实现 __next__ 推进状态 end note note right of Exhausted 生成器耗尽后不可复用 再 next() 继续抛 StopIteration end note
stateDiagram-v2
    [*] --> Iterable: 实现 __iter__
    Iterable --> Iterator: iter(obj)
    Iterator --> Yield: next(it)
    Yield --> Iterator: 还有元素
    Yield --> Exhausted: StopIteration
    Exhausted --> [*]

    note right of Iterable
        每次 __iter__ 返回新的迭代器
        list / dict / str 都是可迭代对象
    end note

    note right of Iterator
        实现 __iter__ 返回 self
        实现 __next__ 推进状态
    end note

    note right of Exhausted
        生成器耗尽后不可复用
        再 next() 继续抛 StopIteration
    end note

4. 装饰器的调用链

flowchart TD A["@timed def add(a, b)"] --> B["add = timed(add)"] B --> C["timed 返回 wrapper"] C --> D["调用 add(1, 2)"] D --> E["wrapper(*args) 开始计时"] E --> F["调用原函数 fn(1, 2)"] F --> G["finally 记录耗时"] G --> H["返回结果"] C -.->|functools.wraps| I["复制 __name__ / __doc__ / __wrapped__"]
flowchart TD
    A["@timed def add(a, b)"] --> B["add = timed(add)"]
    B --> C["timed 返回 wrapper"]
    C --> D["调用 add(1, 2)"]
    D --> E["wrapper(*args) 开始计时"]
    E --> F["调用原函数 fn(1, 2)"]
    F --> G["finally 记录耗时"]
    G --> H["返回结果"]
    C -.->|functools.wraps| I["复制 __name__ / __doc__ / __wrapped__"]

没有 wraps 时,add.__name__ 会变成 "wrapper", 日志、文档生成、框架反射全部拿到错误信息。


5. STAR 的信息结构

flowchart LR S[背景 Situation<br/>约束是什么] --> T[目标 Task<br/>要达成什么] T --> A[行动 Action<br/>我具体做了什么] A --> R[结果 Result<br/>数字证明] R --> F[反思<br/>如果重做改什么] S -.->|缺失| X1[面试官不知道难度] T -.->|缺失| X2[不知道你的判断标准] A -.->|缺失| X3[不知道是不是你做的] R -.->|缺失| X4[不知道有没有效果]
flowchart LR
    S[背景 Situation<br/>约束是什么] --> T[目标 Task<br/>要达成什么]
    T --> A[行动 Action<br/>我具体做了什么]
    A --> R[结果 Result<br/>数字证明]
    R --> F[反思<br/>如果重做改什么]

    S -.->|缺失| X1[面试官不知道难度]
    T -.->|缺失| X2[不知道你的判断标准]
    A -.->|缺失| X3[不知道是不是你做的]
    R -.->|缺失| X4[不知道有没有效果]

STAR 的本质是降低对方重建你经历的成本。 顺序固定,是因为它对应「约束 → 目标 → 选择 → 验证」的决策链。


6. 复杂度预算图(n 与可接受复杂度)

每秒可执行的 Python 基本操作(量级,实测相关):
  简单循环/比较       ~1e7 次/秒
  函数调用            ~5e6 次/秒
  哈希查找            ~1e7 次/秒
  排序(C 实现)      ~1e6~1e7 元素/秒

题目给的 n          允许的复杂度            典型算法
---------------------------------------------------------------
n ≤ 10               O(n!) / O(2^n)          回溯、暴力枚举
n ≤ 20               O(2^n)                  状压 DP
n ≤ 100              O(n^3)                  区间 DP、Floyd
n ≤ 1,000            O(n^2)                  普通 DP、双重循环
n ≤ 100,000          O(n log n)              排序、堆、二分
n ≤ 1,000,000        O(n)                    双指针、滑动窗口、哈希
n ≤ 10^8             O(log n) / O(1)         数学、二分答案
---------------------------------------------------------------
经验:先把 n 写下来,再选模板。反过来(先想算法再看 n)十次有三次会选错。

7. 评分流水线(本课实现)

answer.txt
   │
   ├─ normalize:去空白 + 小写
   │
   ├─ 概念覆盖 ── must_have 逐项匹配(支持 a|b 同义词)── 权重 35%
   ├─ 技术深度 ── 因果/复杂度/边界/实测等信号计数 ────── 权重 25%
   ├─ 表达结构 ── 首句定义 + 分句数 + 边界(或 STAR 组)─ 权重 30%
   └─ 简洁度   ── 80–1200 字区间 ───────────────────── 权重 10%
   │
   ├─ 总分 = Σ(维度得分 × 权重)
   ├─ 及格线 60(校准集上选出)
   └─ 建议 = 缺失概念 + 最低维度对应的改法

已知局限:词袋匹配不懂语义;同义改写可能被低估;长段关键词堆砌可能蒙混过关。 所以它只能当自查清单,不能替代真人面试。


8. 一句话记忆卡(ASCII)

GIL      : 同一时刻一个线程跑字节码;IO 等待会释放 → IO 用线程,CPU 用进程
内存     : 引用计数为主,分代 GC 兜循环引用
迭代器   : __iter__ 返回迭代器,__next__ 推进,StopIteration 结束
生成器   : yield 暂停/恢复,惰性,只能遍历一次
装饰器   : 函数 → 函数;@ 是语法糖;wraps 保元数据
默认参数 : 定义时求值一次,可变默认值跨调用共享 → 用 None 哨兵
MRO      : C3 线性化;子类先于父类,声明靠前者优先
拷贝     : 浅拷贝共享内层,深拷贝递归复制
slots    : 省 __dict__,代价是不能动态加属性
异常     : finally 里的 return 会吞异常
哈希     : dict 平均 O(1),key 必须可哈希
排序     : Timsort 稳定,key 每元素只调一次
二分     : 先定区间语义,再写 mid ± 1
滑动窗口 : 连续子段 + 单调性 → O(n)
DP       : 状态 → 转移 → 边界 → 顺序 → 空间优化
STAR     : 背景 → 目标 → 行动 → 结果(+ 数字 + 反思)