跳到正文
Petrichor's Blog
返回

力扣hot100刷题记录

更新于:
阅读 ... 次

目录

目录

哈希

关于哈希,以下是记忆模板:

unordered_map<Key类型, Value类型> mp;

// 存
mp[key] = value;

// 统计
mp[key]++;

// 查
if (mp.find(key) != mp.end()) {
    // 存在
}

// 遍历
for (auto& pair : mp) {
    pair.first;   // key
    pair.second;  // value
}

还有一个unordered_set:一个“只存唯一值”的无序集合,而且查找非常快。用法:

#include <unordered_set>
using namespace std;

unordered_set<int> st;

#插入
st.insert(5);
#判断是否存在
if (st.count(5)){}
#删除
st.erase(5);
#集合大小
st.size();

1.两数之和

链接

数组中找两个数,使它们满足某个和

优先使用哈希表:

Tip

遍历当前数 x.

算 need = target - x

先查 need 是否出现过

没出现就记录 x -> 下标

unordered_map<int, int> mp;

for (...) {
    int need = target - nums[i];

    if (mp.find(need) != mp.end())
        return {mp[need], i};

    mp[nums[i]] = i;
}

2.字母异位词分组

链接

把具有相同特征的元素分组

这题的“特征”就是:排序后的字符串

Tip

字母异位词 → 排序后一定相同。

用排序后的字符串作为 unordered_map 的 key。

mp[key].push_back(原字符串)。

最后把 map 中每个 vector 放入答案。

unordered_map<特征, vector<原元素>> mp;

for (原元素) {
    特征 = 计算特征;
    mp[特征].push_back(原元素);
}

3.最长连续序列

链接

找连续数字,但原数组本身是乱序的。

核心就一句:找不到前驱,才开始往后找。

要求 O(n) → unordered_set。
num - 1 不存在 → num 才是连续序列起点。
从起点不断检查 num + 1。
每段只扫描一次,所以整体 O(n)。
unordered_set<int> st(nums.begin(), nums.end());

for (int num : st) {

    // 只有前一个数字不存在,才是起点
    if (st.find(num - 1) == st.end()) {

        int current = num;
        int length = 1;

        while (st.find(current + 1) != st.end()) {
            current++;
            length++;
        }
    }
}

双指针

1.移动零

Tip

看到「原地移动 + 保持相对顺序」→ 想双指针。

slow 指向下一个非零元素的位置,遍历时遇到非零就 nums[slow++] = nums[i]。

最后 [slow, n) 全部补 0。

时间 O(n),空间 O(1)。

int slow = 0;

for (int i = 0; i < nums.size(); i++) {
    if (满足保留条件) {
        nums[slow] = nums[i];
        slow++;
    }
}

2.盛最多水的容器

两端选两个位置 + 求最大面积 + 宽度和高度互相制约

优先想到双指针:

int left = 0, right = n - 1;

while (left < right) {
    更新答案;

    if (左边更矮)
        left++;
    else
        right--;
}

3.三数之和

Tip

三数之和 = 排序 + 固定一个数 + 双指针。

和小了 left++,和大了 right—。

这题核心难点不是双指针,而是 三处去重:i 去重、left 去重、right 去重。

时间复杂度 O(n²)。

sort(nums.begin(), nums.end());

for (int i = 0; i < n - 2; i++) {
    // i 去重

    int left = i + 1;
    int right = n - 1;

    while (left < right) {
        int sum = nums[i] + nums[left] + nums[right];

        if (sum < target)
            left++;
        else if (sum > target)
            right--;
        else {
            记录答案;
            左右去重;
        }
    }
}

4.接雨水

Tip

接雨水 = 看每个位置左右两边的最高挡板。

单点水量:min(左最高, 右最高) - 当前高度。

双指针从两端走,谁矮处理谁,同时维护 leftMax / rightMax。

时间 O(n),空间 O(1)。

while (left < right) {

    if (height[left] < height[right]) {
        leftMax = max(leftMax, height[left]);
        ans += leftMax - height[left];
        left++;
    }
    else {
        rightMax = max(rightMax, height[right]);
        ans += rightMax - height[right];
        right--;
    }
}

滑动窗口

1.无重复字符的最长子串

最长/最短连续子串 + 满足某个条件,优先考虑 滑动窗口。

Tip

无重复最长子串:滑动窗口 + set。

right 扩张,遇到重复就 while 移动 left。

当前长度是 right - left + 1。

时间 O(n),空间 O(n)。

unordered_set<char> window;

int left = 0;

for (int right = 0; right < s.size(); right++) {

    while (当前窗口不合法) {
        删除 s[left];
        left++;
    }

    加入 s[right];

    更新答案;
}

2.找到字符串中所有字母异位词

在字符串中找所有长度固定、字符频率满足条件的子串,优先想到:固定长度滑动窗口 + 频率数组

Tip

异位词 = 字符次数完全相同。

用固定长度滑动窗口,长度保持为 p.size()。

右边进一个字符,窗口太长就左边删一个。

两个 26 字符频率数组相同,就记录 left。

时间 O(n),空间 O(1)。

for (int right = 0; right < s.size(); right++) {

    加入 s[right];

    if (窗口太长) {
        删除 s[left];
        left++;
    }

    if (窗口满足条件) {
        记录 left;
    }
}

子串

1.和为 K 的子数组

连续子数组 + 子数组和等于某个值 → 前缀和 + 哈希表。

unordered_map<int, int> mp;
mp[0] = 1;

int sum = 0, ans = 0;

for (...) {
    sum += ...;
    ans += mp[sum - k];
    mp[sum]++;
}

2.滑动窗口最大值

连续长度 k 的窗口,每移动一步求最大/最小值 → 单调队列 + 存下标

Note

先讲一下deque双端队列。普通 queue 只能:

  • 从后面加入 push
  • 从前面删除 pop

但 deque 更灵活,前面和后面都可以插入、删除。

常见操作:

deque<int> q;

q.push_back(2);   // [2]        从后面加入2
q.push_back(3);   // [2, 3]     从后面加入3
q.push_front(1);  // [1, 2, 3]  从前面加入1

cout << q.front(); // 1         查看第一个元素
cout << q.back();  // 3         查看最后一个元素

q.empty() == true   // q 是空的
q.empty() == false  // q 里面有元素
Tip

滑动窗口最大值 → 单调递减队列。

队列存下标:队头过期就删,队尾比新元素小就删。

新元素入队后,窗口形成时 nums[q.front()] 就是最大值。

时间 O(n),空间 O(k)。

while (!q.empty() && nums[q.back()] <= nums[i]){
    q.pop_back();
}

3.最小覆盖子串

过

普通数组

1.最大子数组和

Tip

最大连续子数组和 → Kadane。

curSum 表示“以当前位置结尾”的最大和。

每次二选一:接前面,或者从当前重新开始。

核心:curSum = max(nums[i], curSum + nums[i])。

curSum = max(nums[i], curSum + nums[i]);
maxSum = max(maxSum, curSum);

2.合并区间

一堆区间,要合并重叠区间

Tip

合并区间 → 先按左端点排序。

当前左端点 > 上一个右端点:不重叠,直接加入。

否则重叠:lastEnd = max(lastEnd, curEnd)。

整体 O(n log n)。

sort(intervals.begin(), intervals.end());

for (...) {
    if (当前左端点 > 上一个右端点)
        加入新区间;
    else
        更新右端点为 max(...);
}

3.轮转数组

Tip

数组右轮转 k 位 → 三次反转。

先 k %= n,再整体反转、前 k 个反转、剩余部分反转。

本质是把 [A|B] 变成 [B|A]。

时间 O(n),空间 O(1)。

k %= n;

reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());

4.除了自身以外数组的乘积

Tip

除自身乘积 → 前缀乘积 × 后缀乘积。

第一遍从左到右,把“左边乘积”存进 answer。

第二遍从右到左,再乘“右边乘积”。

关键:先使用 left/right,再乘 nums[i],避免把自己算进去。

int left = 1;
for (int i = 0; i < n; ++i) {
    answer[i] = left;
    left *= nums[i];
}

int right = 1;
for (int i = n - 1; i >= 0; --i) {
    answer[i] *= right;
    right *= nums[i];
}

5.缺失的第一个正数

先略过

矩阵

1.矩阵置零

Tip

矩阵置零本质是先记录哪些行、哪些列要清零。

row[i] 记录第 i 行要不要清零。

col[j] 记录第 j 列要不要清零。

进行第一次遍历,然后标记后再进行第二次遍历置0。

vector<bool> row(m,false);
vector<bool> col(n,false);

for(int i=0;i<m;i++){
    for(int j=0;j<n;j++){
        if(matrix[i][j]==0){
            row[i]=true;
            col[j]=true;
        }
    }
}
for(int i=0;i<m;i++){
    for(int j=0;j<n;j++){
        if(row[i]||col[j]){
            matrix[i][j]=0;
        }
    }
}

2.螺旋矩阵

Tip

看到“顺时针遍历矩阵” → 四边界模拟。

顺序固定:上 → 右 → 下 → 左。

每走完一边就收缩边界;下、左遍历前额外检查边界,防止重复。

while (top <= bottom && left <= right) {
    上边 → top++;
    右边 → right--;
    if (...) 下边 → bottom--;
    if (...) 左边 → left++;
}

3.旋转图像

Tip

旋转矩阵 + 要求原地 → 想“转置 + 翻转”。

顺时针 90°:先主对角线转置,再每行左右翻转。

转置只枚举 j = i + 1 开始的右上部分。

时间 O(n²),空间 O(1)。

// 转置
for (int i = 0; i < n; i++)
    for (int j = i + 1; j < n; j++)
        swap(matrix[i][j], matrix[j][i]);

// 每行反转
for (int i=0;i<n;i++)
    reverse(row.begin(), row.end());

分享这篇文章:

下一篇
3X-UI Xray新版REALITY导致Mihomo Timeout的解决记录