力扣Hot100速记
最近打算把 Hot100 给它肝了, 不过只是每日3题罢了...
有些题目二刷了, 之前会的, 现在还是得思考, 但是这种东西, 你思考你就输了...
故本博客会记录题目, 并且把题目的解法总结为短短的几句话或者图示, 以方便我复习 =-=
长期更新 2025-05-10 15:05:03 から; 最近更新: 2025-10-11 22:40:46
1. 哈希
- 
前缀和 (维护右枚举左), 秒了 
- 
给每一个字符串排序后哈希加入分组, 然后再return; , 是字符串长度 进阶的是使用 array<int, 26>来记录数字然后哈希;更厉害的是使用质数表然后乘法, 这样可以直接拿数字哈希 
- 
在未排序的数组中, 找到最长连续序列, 使用哈希集合, 枚举 与 来不断 next; 维护最大长度, 剪枝就是先把全部数先放集合, 从而去重; 然后再判断 是否在集合, 如果在, 说明之前已经枚举过它了, 不会有更长的, 可以直接 continue了
2. 双指针
- 
一个指针指向数字的后一个位置, 和一个不断变量的数字, 如果非0, 就和该指针交换位置, 并且指针++; class Solution {
 public:
 void moveZeroes(vector<int>& nums) {
 int mae = 0;
 for (int& x : nums) {
 if (x) {
 swap(x, nums[mae]);
 ++mae;
 }
 }
 }
 };
- 
左右指针, 维护最高的柱子, 同时遍历过程中, 不断计算并维护当前的水的最大值; 移动左右的低的柱子 
- 
有很慢的 的哈希的维护右枚举左的写法. 正解应该是排序后双指针 class Solution {
 using ll = long long;
 public:
 vector<vector<int>> threeSum(vector<int>& nums) {
 // 和为 0 的, 不重复的 (v1, v2, v3)
 int n = nums.size();
 vector<vector<int>> res;
 ranges::sort(nums); // 排序后, 定一, 然后就是 [有序版-两数之和]
 for (int i = 0; i < n - 2; ++i) {
 int v = nums[i];
 if (i && v == nums[i - 1]) // 去重
 continue;
 if (v + nums[i + 1] + nums[i + 2] > 0) // 剪枝: 当前 + 最小 > 0 => 不可能会 == 0
 break;
 if (v + nums[n - 1] + nums[n - 2] < 0) // 剪枝: 当前 + 最大 < 0 => 目前不会 == 0
 continue;
 int l = i + 1, r = n - 1, tg = -v;
 while (l < r) {
 int x = nums[l] + nums[r];
 if (x > tg) {
 --r;
 } else if (x < tg) {
 ++l;
 } else { // ==
 int lv = nums[l], rv = nums[r];
 res.push_back({v, lv, rv});
 while (l < r && lv == nums[l]) // 去重
 ++l;
 while (l < r && rv == nums[r]) // 去重
 --r;
 }
 }
 }
 return res;
 }
 };
- 
前缀和 + 后缀和 罢了 (其中一个方法)  
3. 滑动窗口
- 
哈希表计数 + 滑窗 即可 
- 
哈希表计数字母 + 滑窗 
4. 子串
- 
前缀和上的两数之和 (可维护右枚举左, 记得写等式推: , 其中 为前缀和的值) 
- 
单调双端队列: {维护单调性 => 元素离开窗口 => 记录答案} for in nums 即可~ 
- 
哈希计数字符个数 + 触发阈值的滑窗 (如果满足字符了 => 才开始减) class Solution {
 public:
 string minWindow(string s, string t) {
 auto hash = [](char c) -> int {
 return (c & ' ') ? c - 'a' : c - 'A' + 26;
 };
 array<int, 26 + 26> cntS{}, cntT{};
 for (char c : t)
 ++cntT[hash(c)];
 int n = s.size(), isOk = [&]{
 int res = 0;
 for (int v : cntT)
 res += v > 0;
 return res;
 }();
 int resBegin = 1e9, resLen = 1e9;
 for (int i = 0, l = 0; i < n; ++i) {
 int idx = hash(s[i]);
 if (++cntS[idx] == cntT[idx])
 --isOk;
 while (!isOk) {
 int jdx = hash(s[l]);
 if (cntS[jdx]-- == cntT[jdx]) {
 ++isOk;
 if (resBegin == (int)1e9 || i - l + 1 < resLen)
 resBegin = l, resLen = i - l + 1;
 }
 ++l;
 }
 }
 return resBegin == (int)1e9
 ? ""
 : s.substr(resBegin, resLen);
 }
 };
5. 普通数组
- 
贪心/dp: 如果小于0, 就不选了, 然后维护这个过程中的最大值 
- 
排序后, 从头遍历, 看看是否答案的右端点是否 大于 当前的左端点, 如果是则 更新当前答案的右端点的最大值, 如果否, 则答案新增一个区间 
- 
如果不想额外空间, 那么就上结论吧: 答案 = 反转整个数组 + 反转 [0, k) + 反转 [k, n) @todo: 有空把推导深入看看, 我用的是交换法, 有点难调, 发现答案是 gcd(k, n) 轮交换... 不是同一个算法吧~ 
- 
返回的数组做后缀积, 然后顺序再一次前缀积即可 
- 
换座位: 假设 [1, n] 都在自己座位上, 如果 nums[i] 不在 i + 1 上, 说明位置不对, 给她换了: class Solution {
 public:
 int firstMissingPositive(vector<int>& nums) {
 int n = nums.size();
 for (int i = 0; i < n; ++i) {
 // 在 [1, n] 内
 // nums[i] - 1 是因为索引从 0 开始
 // nums[nums[i] - 1] == nums[i]
 // 的前提是 nums[i] = i + 1
 while (nums[i] >= 1
 && nums[i] <= n
 && nums[nums[i] - 1] != nums[i]
 ) {
 // 交换: 把这里的换过去, 那里的也换过来
 swap(nums[i], nums[nums[i] - 1]);
 }
 }
 for (int i = 0; i < n; ++i) // 最终判断第一次 nums[i] != i + 1 的
 if (nums[i] != i + 1) // 就是缺少 i + 1 号同学
 return i + 1;
 return n + 1; // 否则就是都有; 缺 n + 1
 }
 };
6. 矩阵
- 
把第一行和第一列作为标志位, 再另外使用两个变量标记第一行、列本身是否有0即可 
- 
定义方向变量, 绕边内入, 当越界或者走过了, 就可以换方向了. 
- 
数学本质: 两次翻转等于一次旋转 - 竖着看: 第 列的东西去了 第 行
- 横着看: 第 行的东西去了 第 列
 故, 矩阵按照主对角线翻转, 把每一行翻转, 
- 
斜着看, 就是一颗二叉搜索树 
7. 链表
- 
结论: 让先到末尾的继续从另一个的开始走, 这样 n + m + l 等距离. 并且都为交点处. 
- 
边走边反转, tmp -> mae -> (node)=>tmp <- mae -> (node)
- 
时间 + 空间解法: 查找中间节点 (快慢指针) + 反转链表 后, 双指针 
- 
题意仅 找环: 快慢指针, 如果相等就有环. 找到 nullptr 显然无环. 
- 
暴力: 上 unordered_set<Node*>正解: Floyd 判圈算法, 记忆: 双指针相遇后, 慢指针和头指针再走 a 步相遇点为入环点. 推导: 记 , , 那么当慢指针走了 步快慢指针 相遇时候, 快指针走了 步. 假设 快指针比慢指针多走了 圈, 那么此时它们走过 , 即 距离. 而慢指针在 环内 总共走了: , 即 到达相遇点. 说明, 从相遇点, 再走 步, 就是入环口了, 即 , 即 . 为什么呢? 就类似于从入环口跑 圈, 最后还是回到入环口. 
- 
没什么好说的, 就双指针然后比对看着选 
- 
模拟, 记录一下进位即可. 
- 
使用 虚拟头节点 (避免情况讨论), 然后双指针从虚拟头节点开始, 快指针先走 n 步. 然后一起走, 等到头了, 慢指针就是要删除的. 
- 
使用虚拟头节点, 然后 while (node && node->next && node->next->next)并且依次把上上个节点上一个节点当前节点列好, 你画图就清晰了!



