Skip to content

Latest commit

 

History

History
225 lines (169 loc) · 19.8 KB

README.md

File metadata and controls

225 lines (169 loc) · 19.8 KB

AlgorithmNote

本项目记录算法学习笔记。总结每道题的核心思路、算法实现与正确性证明,提炼可复用的算法设计思想。

一、LeetCode 热题 100

题目按 LeetCode 热题 100 官方学习计划 的专题顺序排列。已完成笔记:16/100;— 表示尚无解题笔记。

序号 题目(原题链接) 解题笔记
1 1. 两数之和 —
2 49. 字母异位词分组 —
3 128. 最长连续序列 查看笔记
4 283. 移动零 查看笔记
5 11. 盛最多水的容器 —
6 15. 三数之和 —
7 42. 接雨水 查看笔记
8 3. 无重复字符的最长子串 查看笔记
9 438. 找到字符串中所有字母异位词 查看笔记
10 560. 和为 K 的子数组 查看笔记
11 239. 滑动窗口最大值 查看笔记
12 76. 最小覆盖子串 查看笔记
13 53. 最大子数组和 —
14 56. 合并区间 查看笔记
15 189. 轮转数组 —
16 238. 除了自身以外数组的乘积 —
17 41. 缺失的第一个正数 查看笔记
18 73. 矩阵置零 —
19 54. 螺旋矩阵 —
20 48. 旋转图像 —
21 240. 搜索二维矩阵 II —
22 160. 相交链表 —
23 206. 反转链表 —
24 234. 回文链表 —
25 141. 环形链表 —
26 142. 环形链表 II 查看笔记
27 21. 合并两个有序链表 —
28 2. 两数相加 —
29 19. 删除链表的倒数第 N 个结点 查看笔记
30 24. 两两交换链表中的节点 —
31 25. K 个一组翻转链表 —
32 138. 随机链表的复制 —
33 148. 排序链表 —
34 23. 合并 K 个升序链表 —
35 146. LRU 缓存 —
36 94. 二叉树的中序遍历 —
37 104. 二叉树的最大深度 —
38 226. 翻转二叉树 —
39 101. 对称二叉树 —
40 543. 二叉树的直径 —
41 102. 二叉树的层序遍历 —
42 108. 将有序数组转换为二叉搜索树 —
43 98. 验证二叉搜索树 —
44 230. 二叉搜索树中第 K 小的元素 —
45 199. 二叉树的右视图 —
46 114. 二叉树展开为链表 —
47 105. 从前序与中序遍历序列构造二叉树 —
48 437. 路径总和 III —
49 236. 二叉树的最近公共祖先 —
50 124. 二叉树中的最大路径和 —
51 200. 岛屿数量 查看笔记
52 994. 腐烂的橘子 查看笔记
53 207. 课程表 查看笔记
54 208. 实现 Trie (前缀树) —
55 46. 全排列 —
56 78. 子集 —
57 17. 电话号码的字母组合 —
58 39. 组合总和 —
59 22. 括号生成 —
60 79. 单词搜索 —
61 131. 分割回文串 —
62 51. N 皇后 —
63 35. 搜索插入位置 —
64 74. 搜索二维矩阵 —
65 34. 在排序数组中查找元素的第一个和最后一个位置 —
66 33. 搜索旋转排序数组 —
67 153. 寻找旋转排序数组中的最小值 —
68 4. 寻找两个正序数组的中位数 查看笔记
69 20. 有效的括号 —
70 155. 最小栈 —
71 394. 字符串解码 —
72 739. 每日温度 —
73 84. 柱状图中最大的矩形 —
74 215. 数组中的第K个最大元素 —
75 347. 前 K 个高频元素 —
76 295. 数据流的中位数 —
77 121. 买卖股票的最佳时机 —
78 55. 跳跃游戏 —
79 45. 跳跃游戏 II —
80 763. 划分字母区间 —
81 70. 爬楼梯 —
82 118. 杨辉三角 —
83 198. 打家劫舍 —
84 279. 完全平方数 —
85 322. 零钱兑换 —
86 139. 单词拆分 —
87 300. 最长递增子序列 —
88 152. 乘积最大子数组 —
89 416. 分割等和子集 —
90 32. 最长有效括号 —
91 62. 不同路径 —
92 64. 最小路径和 —
93 5. 最长回文子串 —
94 1143. 最长公共子序列 —
95 72. 编辑距离 —
96 136. 只出现一次的数字 —
97 169. 多数元素 —
98 75. 颜色分类 —
99 31. 下一个排列 —
100 287. 寻找重复数 —

二、算法笔记

核心思想:二分查找划分边界。

划分思路本身比较简单,难点在于统一处理边界条件并保证算法正确性。

  1. 奇偶统一:统一使用 (m+n+1)//2 确定划分位置,仅在返回中位数时区分奇偶。
  2. 数组边界:明确变量表示划分位置,而非数组下标;使用正负无穷大作为哨兵处理空区间。
  3. 循环不变量:利用条件的单调性进行二分查找,通过循环不变量证明不会遗漏目标位置,且最终结果不会被覆盖。

可复用经验: 明确变量含义,统一处理特殊情况,利用循环不变量证明算法正确性。

核心思想: 使用 set 加速成员查询,并仅从连续序列的最小元素开始向后检查,避免重复遍历。

  1. 避免不必要的约束:只需要保证整个算法最终得到正确结果,不必为每个中间步骤添加不必要的约束。
  2. 先求解,再优化:在有性能要求的问题上,先找到简单思路,再分析性能瓶颈并逐步优化。

核心思想: 使用双指针将非零元素按原顺序写入数组前部,再将剩余位置填零。

核心思想: 利用双指针维护左右两侧的最大高度,每次处理最大高度较小的一侧,逐步确定各位置的接水量。

  1. 从确定性出发:先解决能够确定结果的边界位置,再逐步扩展到未解决区域。
  2. 利用问题性质推进:根据接水量由左右最大高度的较小值决定这一性质,选择可以安全处理的一侧。

核心思想: 将问题分解为以不同位置为起点的子问题,再利用滑动窗口复用相邻子问题的计算结果,避免重复搜索。

  1. 解空间分解:按照某一维度划分候选解,分别求出局部最优解,再合并得到全局最优解。
  2. 增量计算:利用相邻子问题之间的关联,复用已有计算结果,避免重复计算。

核心思想: 通过解空间分解,将问题转化为统计每个起点或终点对应的合法子数组数量。按终点枚举更适合从左向右遍历:利用前缀和与哈希表维护历史信息,即可直接统计当前终点对应的合法起点,无需重复扫描后续元素,将时间复杂度从 $O(n^2)$ 优化至平均 $O(n)$。按起点枚举也可以通过反向遍历实现相同复杂度。

核心思想: 通过解空间分解,将问题转化为判断每个固定长度的子串是否为字母异位词。利用滑动窗口复用相邻子问题的字符频次,并通过 diff 记录频次不匹配的字符种类数,采用新贡献减去旧贡献的方式增量更新,将每次窗口判断优化为 $O(1)$。

核心思想: 通过解空间分解,将问题规约为求解每个固定长度窗口的最大值。利用支配关系进行候选剪枝:当新元素在数值和有效期上均不劣于较早元素时,可以安全淘汰较早元素。使用单调队列增量维护最大值候选,将时间复杂度从 $O(nk)$ 优化至 $O(n)$。

  1. 支配关系与候选剪枝:根据数值优势和位置优势,证明被支配的元素不可能再成为必须保留的最大值候选。
  2. 增量维护:通过队首删除过期元素、队尾淘汰被支配元素,实现窗口最大值的高效更新。
  3. 循环不变量:不变量只需在每轮完整迭代结束时成立即可。

核心思想: 通过解空间分解,将问题规约为寻找每个起点对应的最短合法子串。利用覆盖条件的单调性,通过滑动窗口使左右指针单向移动,避免枚举所有子串;使用字符需求表 need 和缺失计数 missing 增量维护覆盖状态,将时间复杂度从朴素枚举的 $O(n^2)$ 级别优化至 $O(|s|+|t|)$。

核心思想: 排序 + 贪心。朴素解法中,当前合并区间的最终结果依赖于所有剩余区间,需要反复检查。通过按左端点排序建立单调性,使得当前区间一旦与下一个区间不重叠,就必然与所有后续区间不重叠,从而实现局部解与剩余区间的解耦。局部解可以安全确定,无需回溯,将反复扫描转化为一次线性扫描。一般性的算法思路是:通过引入某种结构(排序、单调性、边界约束等),将原本相互依赖的子问题转化为可以独立确定的局部问题。

核心思想:原地标记。 将问题规约为判断 [1,n] 中各整数是否存在。通过建立整数 x 与数组下标 x-1 的映射关系,利用数组元素的正负号记录存在性,并通过绝对值保留原始数值信息。将原本需要额外哈希集合存储的信息编码到输入数组中,实现时间复杂度 $O(n)$、额外空间复杂度 $O(1)$。

核心思想:快慢指针 + Floyd 算法。 利用快慢指针的相对运动检测环,再定位环入口。首先将问题分解为检测环、计算环长、定位入口三个子问题:利用环的周期性,计算环长后让两个指针相距一圈并同步移动,即可找到入口。进一步分析首次相遇点蕴含的距离关系,发现可以直接从头节点和相遇点同步移动找到入口,省去显式计算环长的步骤。

可复用经验: 先通过问题分解和结构性质构造直观、易于证明的解法,再分析中间状态蕴含的信息,利用数学关系消除不必要的计算。

核心思想:固定距离双指针。 当目标位置与链表末尾存在固定距离关系时,可以通过双指针保持固定间距,利用一个指针的终止条件确定另一个指针的位置,避免显式计算链表长度。

核心思想:DFS 连通分量计数。 每次从未访问陆地启动 DFS,标记同一个岛屿中的所有陆地,DFS 的启动次数即岛屿数量。采用入栈即标记避免重复访问,并通过原地标记省去额外的访问数组。

核心思想:多源 BFS + 分层遍历。 从所有初始腐烂橘子同时出发,每一层对应一分钟;新橘子入队时立即标记避免重复访问,全部腐烂后提前终止,得到最短传播时间。

核心思想:DFS 有向环检测 + 搜索结果复用。 将课程依赖关系转化为有向图,通过 DFS 判断是否存在环。朴素路径枚举存在重复搜索,通过三状态标记区分未访问、正在搜索和已完成的节点,实现环检测与结果复用,将时间复杂度优化至 $O(V+E)$。

三、通用算法设计思想

核心思想:从初始状态和目标状态出发,通过循环不变量设计状态迁移,再推导具体实现。

以回文链表的反转操作为例,将算法设计分为四个步骤:

  1. 状态定义:明确初始状态与目标状态,提取不同情况的共同结构,统一边界条件。
  2. 不变量设计:根据合法中间状态推导状态变量、初始化和终止条件。
  3. 状态迁移与实现:先设计抽象状态迁移,再根据数据依赖推导具体操作顺序,必要时引入临时变量。
  4. 正确性证明:证明初始化、不变量保持、算法终止及目标达成。

核心思想:示例驱动的父子问题关系分析。 用六列表格描述父问题输入、子问题分解方式、子问题输入、子问题结果、分支结果与父问题结果;通过输入关系推导递归调用,通过输出关系推导结果构造与汇总,并利用边界示例明确终止条件。

四、算法基础知识

核心内容: 介绍常见渐近复杂度记号的严格数学定义,以及 Python 常用数据结构操作的时间复杂度。

  1. 复杂度记号:理解大 O、大 Ω、大 Θ、小 o、小 ω 等渐近记号及其数学含义。
  2. 数据结构复杂度:掌握 Python list、dict、set 等常用数据结构的典型操作复杂度及适用条件。