2026-03-13 22:24:00
![]()
arr = []
arr = [0] * n
dp = [[0] * n for _ in range(m)] (严禁使用 [[0]*n]*m)arr = [x**2 for x in range(10) if x % 2 == 0]
+ 运算符: [1, 2] + [3, 4] -> [1, 2, 3, 4] (产生新列表,耗时)arr.extend([3, 4]) (等价于按顺序 append,$O(K)$)append(x) $O(1)$, pop() $O(1)$, arr[::-1] 翻转 $O(N)$, sort() 原地排序 $O(N \log N)$。s = set() (严禁使用 {},那是空字典)s = {1, 2, 3}
s1 | s2 或 s1.union(s2)
s1 & s2 或 s1.intersection(s2)
s1 - s2
add(x) $O(1)$, remove(x) $O(1)$ (不存在会报错), discard(x) $O(1)$ (不存在不报错)。d = {} 或 dict()
d = defaultdict(int) (访问不存在的键返回默认值)(from collections import defaultdict)d = {'a': 1, 'b': 2}
d3 = d1 | d2 (合并字典,同名键覆盖)d1.update(d2)
d.get(key, default) (安全读取), key in d $O(1)$。s[:4] # “leet” (前 4 个字符)s[-4:] # “code” (后 4 个字符)s[::-1] # “edocteel” ($O(N)$ 极速反转)s.find("etc") # 返回 2 (子串起始索引,找不到返回 -1,笔试首选)s.startswith("le")# Trues.endswith("de") # Trues.isalnum() # True (判断是否只包含字母和数字,双指针判断回文时必用)s.isdigit() # False (判断是否纯数字)s2 = " a b c "s2.strip() # “a b c” (默认袪除首尾所有空白符)s2.replace(" ", "") # “abc” (极其好用的全量替换)t = ()
t = (1,) (⚠️ 必须带逗号,否则 (1) 会被当作整数运算)t = (1, 2, 3)
visited.add((r, c));DP 记忆化 memo[(idx, weight)] = res。r, c = (0, 1) (多变量同时赋值)t[0], t[::-1] (与列表行为一致,但不能修改元素)
|
|
| 转换方向 | 语法 | 说明 |
|---|---|---|
| List -> Set | set(arr) |
瞬间去重,常用于降维打击 $O(N)$ 查找 |
| Set -> List | list(s) |
集合转回数组,常用于去重后排序 |
| String -> List | list("abc") |
变为 ['a', 'b', 'c'],因为字符串不可变,需转列表修改 |
| List -> String | "".join(arr) |
高效拼接,arr 内部必须全是字符串元素 |
| Dict -> List |
list(d.keys())list(d.values())
|
分别提取字典的键或值构成列表 |
| List <-> Tuple |
tuple(arr)list(t)
|
列表转元组以使其可哈希(存入 Set/Dict),或元组转列表以修改内容 |
处理国内笔试题(如牛客网)必备,避免 input() 导致超时或读取异常。
|
|
|
|
避坑:Python 中在 for 循环内修改 i 无效,会被下一轮自动重置。如需动态调整指针必须用 while。
|
|
|
|
|
|
|
|
float('inf'), float('-inf')。pow(a, b, mod) 快速幂取模算法(比自己手写快)。divmod(a, b) 同时返回商和余数。ord('a') -> 97, chr(97) -> ‘a’。s.split() (默认处理所有连续空白符并丢弃空串)。s.isalnum() (判断是否全为字母或数字,回文串常用)。s.find(sub) (找子串,找不到返回 -1,严禁使用 index() 因为会抛异常报错)。for i, val in enumerate(arr): (同时拿索引和值)。for x, y in zip(arr1, arr2): (双数组齐头并进)。arr.sort() # 默认升序,原地排序,适用于数字/字符串arr.sort(reverse=True) # 降序排序arr.sort(key=lambda x: x[0]) # 按元素的第一个字段排序(如区间、元组、二维数组)arr.sort(key=lambda x: (x[0], -x[1])) # 多条件排序:优先按第一元素升序,相同则按第二元素降序sorted(arr) # 返回新排序后的列表,不改变原数组sorted(arr, key=..., reverse=...) # 一切排序技巧均可用intervals.sort(key=lambda x: x[0]) # 区间按左端点排序,区间合并/覆盖问题高频a if 条件 else b (等价于 C/C++/Java 的 条件 ? a : b)res = x if x > 0 else -x # 取绝对值自带的 bisect 库是 $O(\log N)$,直接替代手写二分。
|
|
|
|
|
|
|
|
|
|
在你听到“红黑树”、“B+树”等高大上的名词时,需要首先明确工业界底层开发与 LeetCode 算法应试的绝对分界线:
std::set / std::map,在 Java 中使用 TreeMap,在 Python 中则依赖第三方库 sortedcontainers 或使用 bisect 模块结合列表模拟。以下是为你剥离冗余后,针对 LeetCode 场景的树与图 Cheat Sheet。
辩证本质: 树形结构中的“二分查找”。它将线性数组的查找效率从 $O(N)$ 降维至 $O(\log N)$,但代价是每次插入/删除都需要动态维护其严格的大小关系。
Python 模板:利用中序遍历验证 BST
|
|
辩证本质: 典型的“空间换时间”。通过将字符串拆解为字符并构建多叉树,将海量字符串的匹配时间复杂度,从 $O(N \times L)$ 极致压缩到 $O(L)$($L$ 为单个单词的长度),代价是需要极其庞大的节点内存。
Python 模板:Trie 的标准实现
|
|
辩证本质: 树其实是图的一种极度受限的特例(树是没有环的连通无向图)。图抛弃了“父子关系”的严格层级,允许数据之间存在任意的网状联系。
visited 集合来记录走过的节点,坚决不走回头路。辩证本质: 将一个存在依赖关系的网状图,拉平为一条线性的执行序列。如果图中存在环(如 A 依赖 B,B 依赖 A),则拓扑排序必然失败(发生死锁)。
Python 模板:利用 BFS (Kahn算法) 实现拓扑排序
|
|
机试时,先看题目给定的变量范围 $N$,直接反推该用什么算法:
已完成联网核对。前文提到的“有效的字母异位词”题号有误(LeetCode 24 实际为“两两交换链表中的节点”,242 才是“有效的字母异位词”),下表已修正。
这是一份纯粹用于检验 Python 数据结构熟练度的闭卷通关表:
| 题目号及名称 | 核心知识点 | 闭卷 AC 验收标准 |
|---|---|---|
| 125. 验证回文串 | 字符串过滤、切片翻转 | 熟练使用推导式 [c.lower() for c in s if c.isalnum()],用切片 s == s[::-1] 一行判断,不写双指针。 |
| 349. 两个数组的交集 | Set 的初始化与交集运算 | 严禁手写两层循环,必须一句话搞定:return list(set(nums1) & set(nums2))。 |
| 128. 最长连续序列 | Set 的 $O(1)$ 极速查找 | 将数组转为 set,彻底理解并利用 num in num_set 的 $O(1)$ 特性防超时。 |
| 242. 有效的字母异位词 | 词频统计 (Counter) |
严禁手写循环统计,直接引入 Counter,使用 return Counter(s) == Counter(t) 一行秒杀。 |
| 49. 字母异位词分组 |
defaultdict、Tuple 作为哈希键 |
熟练写出 d = defaultdict(list),深刻理解必须将排序后的字符串转为 tuple 才能作为字典的 key。 |
| 169. 多数元素 | 字典遍历 / API 获取极值 | 熟练手写字典遍历寻找最大值,或直接使用 Counter(nums).most_common(1)[0][0]。 |
| 20. 有效的括号 | List 作为栈的使用 | 熟练使用 append() 和 pop(),并用静态字典 {')': '(', ']': '[', '}': '{'} 优雅映射替代大量 if-else。 |
| 102. 二叉树的层序遍历 |
deque 实现 BFS |
倒背如流 q = deque([root]) 和 q.popleft(),严禁在此处使用 list.pop(0)。 |
| 200. 岛屿数量 | 多维坐标的 Tuple 打包与 Set 去重 | 在二维 BFS/DFS 中,极其熟练地将坐标打包成元组存入访问记录:visited.add((r, c))。 |
| 215. 数组中的第K个最大元素 |
heapq 核心 API |
熟练使用 heapq.heapify 以及维护大小为 $K$ 的小顶堆(heappush 和 heappop)。 |
| 347. 前 K 个高频元素 | 大顶堆技巧与复杂结构嵌套 | 结合 Counter 统计频率,并熟练写出“取负数塞入小顶堆模拟大顶堆”的操作:heappush(hp, (-freq, num))。 |
| 56. 合并区间 | 列表的高阶排序 (lambda) |
不看资料直接写出按左端点升序排列的定制规则:intervals.sort(key=lambda x: x[0])。 |
2026-03-11 14:23:00
![]()
在搭建个人博客的过程中,我一直有一个执念:希望网页底部的音乐播放器能够像网易云音乐那样,在页面切换时永不中断。
传统的静态博客(如 Hugo 生成的站点)每一次点击链接,浏览器都会重新加载整个页面 (Full Page Reload)。这意味着:
为了解决这个问题,我们需要引入 SPA (Single Page Application) 的概念,或者更轻量级的方案 —— Pjax (PushState + Ajax)。
Pjax 的工作原理非常直观:
<a> 标签的点击事件。Ajax 请求新页面的 HTML 内容。.main-container)。history.pushState 修改浏览器的 URL地址栏,使其看起来像正常跳转。通过这种方式,页脚 (Footer) 和 侧边栏 (Sidebar) 可以保持不变,驻留在其中的音乐播放器自然也就不会中断了。
首先在 <head> 中引入 Pjax 库(推荐使用 pjax 库而非老旧的 jquery-pjax):
|
|
这是最关键的一步。为了保证 Stack 主题的正常渲染,如果你直接替换整个 body,播放器还是会挂掉。我们需要精准打击。
我在 layouts/partials/head/custom.html 中进行了如下配置:
|
|
关键点:不仅要通过 CSS 选择器指定更新区域,还要自定义 switch 函数,确保 body 标签只更新属性而不重置内容。
实现 Pjax 只是第一步,真正的挑战在于副作用。
现象:跳转到 Timeline 页面,Mastodon 动态加载不出来。
原因:通过 innerHTML 插入的 HTML 片段中如果包含 <script> 标签,浏览器出于安全和规范考虑,通常不会执行它们。
解决方案:
我们将初始化代码封装为全局函数,并在 Pjax 完成事件 (pjax:complete) 中手动调用。
|
|
现象:虽然使用了 Pjax,但用户有时会习惯性按 F5 刷新,或者 Pjax 请求超时回退到普通跳转,这时候音乐还是会断,且进度归零。
解决方案:状态持久化 (State Persistence)。
利用 localStorage 在播放器每秒更新时记录状态:
|
|
在页面加载时(无论是 Pjax 还是普通加载),尝试恢复状态:
|
|
这里还有一个细节:audio 元素必须在元数据加载后才能 seek,所以需要监听 loadedmetadata 或 canplay 事件。
通过引入 Pjax 并配合精细的生命周期管理,我们成功在静态博客上实现了类似 SPA 的流畅体验:
折腾博客的乐趣往往不在于写文章本身,而在于通过解决这些具体的技术问题,窥探现代 Web 开发的冰山一角。
2026-03-10 10:24:00
![]()
ConfigManager 配置管理器,就必须是一个单例。你在简历上面试时可以说:“为了避免多次读取配置文件造成 I/O 浪费,我用 C++11 的 std::call_once(或局部静态变量)实现了一个线程安全的单例配置中心。”Subject(被观察者),把负责发送网络请求的模块、负责写本地日志的模块作为 Observer(观察者)。显存一旦超过阈值,主动“通知”这些模块报警,而不是让它们写个 while(true) 死循环去轮询。DeviceFactory,传入 “NVIDIA”,它就吐出一个调用 NVML API 的对象;传入 “CPU”,它就吐出一个读取 /proc/cpuinfo 的对象。
|
|
|
|
时间复杂度 (Time Complexity)nums[0],或者在字典/集合中查值 if key in my_dict:。while left < right: 配合 mid)。for 循环,从头到尾扫一遍。nums.sort(),或者用了归并/快速排序。for 循环。面试官通常会让你把它优化到 $O(n)$。空间复杂度 (Space Complexity)left, right),没有开辟随数据规模增大的新空间。Pythonic 循环抛弃 for(int i=0; i<n; i++),只记以下三种:
for num in nums:
for i in range(len(nums)):
for i, num in enumerate(nums):
核心标准库 (绝不重复造轮子)collections.Counter (计数器)count[i] = count[i] + 1 循环。
|
|
collections.deque (双端队列)list 做队列。
|
|
heapq (优先队列 / 堆)std::priority_queue。
|
|
|
|
|
|
ord(char) —— 字符转索引index = ord(char) - ord('a')
0-25,配合数组做计数。chr(code)。ord() 的逆运算,如 chr(97) 返回 'a',常用于将计算后的索引还原为字符。sorted(s) —— 排序法return sorted(s) == sorted(t)
list.sort(reverse=True)。sorted() 返回新列表,支持所有可迭代对象;list.sort() 是列表的原地排序(无返回值)。Counter —— 词频统计return Counter(s) == Counter(t)
Counter(s).most_common(k)。返回前 k 个高频元素,解决 Top K 问题的神器。[0] * n —— 数组快速初始化count = [0] * 26
dict 更快更省空间。[[0] * n for _ in range(m)]。构建二维数组(矩阵)的正确写法,必须用列表推导式以避免引用拷贝错误。
|
|
|
|
|
|
|
|
nums.index(val) —— 列表查找idx = nums.index(target - n)
for 循环里用它,时间复杂度直接退化为 $O(n^2)$。因为它底层是线性扫描。hash_map[key]。面试官想看的是用哈希表将查找降维到 $O(1)$。Two Pointers —— 双指针法l, r = 0, len(nums) - 1,配合 while l < r。nums.sort()。双指针的前置条件通常是数组有序。range(i + 1, n) —— 内层循环起点for j in range(i + 1, len(nums)):
i + 1 开始。(A, B) 又计算 (B, A)。enumerate(nums) —— 枚举遍历for i, n in enumerate(nums):
range(len(nums))(只拿索引)、for n in nums(只拿值)。不要在需要索引时手动维护一个 count 变量,太土。
|
|
|
|
tuple(mutable_obj) ——不仅是转换key = tuple(count)
frozenset。不可变集合,也可以做 Key。list(d.values()) —— 字典值导出return list(res.values())
d.values() 返回的是一个视图对象 (View),必须用 list() 强转才能符合题目要求的返回类型 List[List[str]]。''.join(sorted(s)) —— 字符串标准化key = "".join(sorted(s))
sorted(s) 返回的是列表 ['a', 'e', 't'](可变,不可做 Key),必须用 join 拼回字符串 "aet"。tuple(sorted(s))。如果不拼回字符串,转成元组也可以做 Key。
|
|
|
|
|
|
|
|
count.get(num, 0) —— 字典计数初始化count[num] = 1 + count.get(num, 0)
0,避免 KeyError。defaultdict(int)。效果等价,写法更短:count[num] += 1。arr.pop()[1] —— 取“当前最大频次元素”[cnt, num] 放进数组并升序排序,再 arr.pop() 拿末尾最大频次对。arr.pop() 返回 [cnt, num],[1] 取的是 num。k 个。freq = [[] for _ in range(len(nums) + 1)] —— 桶数组初始化n + 1 个桶,索引代表“出现次数”,桶里存“出现该次数的数字”。n + 1:元素最高频次可能是 n(全相同),所以要开到下标 n。[[...]] * n 引用拷贝坑。Bucket Sort —— Top K 的线性解法count 频次,再把数字丢进 freq[cnt],最后从高频桶倒序取数直到凑够 k。O(n),空间 O(n)。Heap (Min-Heap) —— Top K 的工程常用解法k 的最小堆,堆里存 (freq, num)。heappush,若堆超 k 就 heappop,最终堆内即 Top K。O(n log k),空间 O(n + k);当 k << n 时常比全排序更优。1. 语言与阵地
2. 路线与开销
3. 核心刷题 SOP (15分钟法则)
4. 兜底退路与国内大厂补丁 (Pivot 方案)
5. NeetCode 的正确打开方式
NeetCode 的 Visualize the algorithm step by step 功能非常便于理解算法流程,至少对我来说,可视化地过一遍算法流程,就很清晰了。
Throughout heaven and earth I alone am the dumb one, I tried to solve this with trie.
2026-03-06 13:50:00
![]()
经历了一学期的研究生生活,lihan 变得保守了很多,也不再执着于读博与当教授,外企的技术岗是当前的首选目标了。兼顾当前课业与课题组压力以及身心健康,制定了一个目标明确的学习计划,计划的核心是提升自己的技术能力,兼顾项目开发与实习经历以及英语能力,以健康的身体和积极的心态迎接秋招。
🎯 终极目标 (The North Star): 2027年秋招/暑期实习斩获一线外企(Microsoft, Amazon, NVIDIA等)核心 Infra/后端 开发岗 Offer,实现 965 工作制、高时薪,拥有绝对的生活主导权。
主线任务由算法与 C++/AI Infra 工程双轮驱动。前端开发仅作为前置 3 天的工具测试,不占主线资源。
阶段一:基建与探路期 (研一下,至 2026 年 8 月)
算法:Python 刷通 LeetCode 经典 150 题(按分类),训练全英文“边写边说”能力。
工程:启动「异构 GPU 算力监控系统」。用 C++ 配合 NVML 写出轻量级数据采集探针,跑通本地与远端 5090 服务器的底层硬件状态拉取。
🛑 异常接管 (期末突击):学期末提前划出 3 周 纯粹用于期末复习。此期间【高能输出】状态冻结,一切为了保住 GPA,考完立刻解冻。
阶段二:并发与重构攻坚期 (研二上,2026.09 - 2027.02)
算法:二刷错题本,定向突击目标外企高频题库。
工程:引入 Python FastAPI 或 Go 重构通信网关,解决跨网络并发与数据持久化。
🛑 异常接管:同样预留 3 周 应对研二上期末考试。
阶段三:狙击与收网期 (研二下,2027.03 - 2027.09)
动作:精修全英文简历,开启 BQ(行为面试)的英文 Mock Interview。海投外企暑期实习。
终局:依靠 3 个月的暑期实习斩获 Return Offer,或携极具深度的 C++ 底层项目降维打击秋招。
你的每一天只会处于以下五种状态之一,绝不允许出现“边学边玩”的模糊中间态。
针对每周只有四节课的现状,将上课时间直接视为【🔵 防御敷衍】模块。如果在课上,则挂机听讲,大脑后台构思代码或复习计网/OS理论。
| 时间块 | 状态机 | 核心执行逻辑与输入/输出 | 时长 |
|---|---|---|---|
| 08:30 - 09:30 | 系统冷开机 | 洗漱、早餐。 浏览开源社区或科技资讯,平缓启动大脑。 | 1.0h |
| 09:30 - 11:30 | 🟢 高能输出 | 算法与理论底座 (Python/CS理论) | |
| 精力峰值时段。刷 1-2 道 LeetCode,强制英文口述思路。 | 2.0h | ||
| 11:30 - 14:00 | 物理断电 | 午餐 + 深度午休。 雷打不动睡 40-60 分钟。 | 2.5h |
| 14:00 - 17:00 | 🟢 高能输出 | AI Infra 底层工程实战 (C++) | |
| 操作本地高配机器与远端服务器,死磕内存管理与网络并发。 | 3.0h | ||
| 17:00 - 19:00 | 硬件维护 | 体能训练与晚餐。 健身房或操场,切换轨道,缓解颈椎压力。 | 2.0h |
| 19:00 - 21:30 | 🔵 防御敷衍 | 导师横向与学校课业 | |
| 降维处理杂活与作业。到点准时停手。(若此时在上课,则此模块平移至上课时段) | 2.5h | ||
| 21:30 - 23:00 | 🟢 / 🟡 判定 | 复盘或触发合法狂欢 | |
| 若当日高能任务 100% 达成,立刻启动游戏;未达成则做最后冲刺与英文 Bug 复盘。 | 1.5h | ||
| 23:00 - 24:00 | 内存清理 | 降温与休眠。 洗澡、看闲书、刷 B 站。24:00 强制熄灯休眠。 | 1.0h |
以下记录就业相关信息,以备查阅:
利用 AI 同时熟悉现代前端开发框架,快速搭建一个 Todo Web APP,作为本计划的状态流转记录工具。
2026-02-06 01:00:00
![]()
| 技术 | 作用 |
|---|---|
| Windows | 操作系统与 GUI 编辑器环境 |
| WSL2 | Linux 核心环境,和服务器一致 |
| nvm | Node 版本管理器,保证项目 Node 版本一致 |
| Node.js | JS/TS 运行环境 |
| pnpm | 高效包管理器,安装依赖与管理脚手架 |
| Next.js + React + TS | 现代前端框架,支持组件化与页面路由 |
| Docker | 插件化工具容器化,保证隔离和可移植性 |
| 阶段 | 学习目标 | 依赖 |
|---|---|---|
| 技术工具 | [x] 开发环境搭建:Windows + WSL2 + VS Code Remote-WSL,Docker 容器化思维 | WSL2 安装与配置、VS Code Remote-WSL、Docker&&K8s学习笔记
|
| [ ] Node.js + TypeScript 入门:Node 运行机制、nvm 版本管理、npm / pnpm / yarn 使用、TypeScript 基础 | Nodejs+TypeScript_技术工具学习记录 |
|
| [ ] Next.js 现代前端开发:React + Next.js + TS、组件化开发、页面路由与 API 路由、插件化平台思维 | Next.js 官方文档、React 官方文档、UI 组件库(Ant Design / Tailwind UI)、Docker 容器化工具 | |
| 基础知识 | 了解前后端解耦与现代 WEBAPP 架构 | HTTP/HTTPS、RESTful API、GraphQL、微服务与插件化服务思维 |
| 掌握模块化与依赖管理概念 | CommonJS / ES Module、模块加载机制、包管理器原理 | |
| 熟悉前端组件化设计与状态管理 | React 组件设计模式、状态管理(Zustand / Redux / Context API) | |
| 产出学习 | 能搭建插件化 WEBAPP 平台,整合前端页面与 Docker 插件工具 | Node + Next.js + Docker API 调用、前端组件化开发、插件化工具集成 |
| 通过小项目练习技术链 | 小工具插件(音乐播放器、计算器、小游戏、AI Agent)容器化 + 前端调用 | |
| 建立完整开发与部署流程能力 | Windows → WSL2 → nvm → Node → pnpm → Next.js → Docker 插件化平台 |
|
|
核心思维:环境统一 → Node 版本稳定 → 高效依赖管理 → 组件化前端 → 容器化服务 → 插件化平台
2026-02-06 01:00:00
![]()
包管理器:推荐 pnpm,安装与验证:
VS Code 配置:
类似于 python 的 conda,nvm(Node Version Manager)是一个用于管理多个 Node.js 版本的工具。它允许你在同一台机器上安装和切换不同版本的 Node.js,非常适合开发者在不同项目中使用不同版本的 Node.js。
类似于 python 的 pip,pnpm 是一个高效的 JavaScript 包管理器,提供了更快的安装速度和更少的磁盘空间占用。它通过使用符号链接来共享依赖项,从而避免了重复安装相同的包。
Node.js 官网 提供了 Node.js 的安装命令,在选择了 系统环境、Node 版本管理器、包管理器和Node.js 版本后,可以直接复制命令在终端中执行。
|
|
值得注意的是,nvm 和 pnpm 的安装步骤可能会因操作系统和环境的不同而有所差异,详见[
nvm 和 pnpm 的安装注意事项](# 7.1-nvm-和-pnpm-的安装注意事项)。
|
|
|
|
|
|
Docker 容器化运行
|
|
实践经验
由于 Node.js 25.x 版本的最新更新,Corepack 在 Node.js 25.x 中已被移除,因此需要手动安装 Corepack 来使用 pnpm 包管理器。
在 Node.js 25.x 中安装 pnpm 的步骤比 Node.js 24.x 多了一步:
``bash
npm install -g corepack
|
|