力扣Hot100速记
最近打算把 Hot100 给它肝了, 不过只是每日3题罢了...
有些题目二刷了, 之前会的, 现在还是得思考, 但是这种东西, 你思考你就输了...
故本博客会记录题目, 并且把题目的解法总结为短短的几句话或者图示, 以方便我复习 =-=
长期更新
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即可