代码随想录算法训练营Day2 关于前缀和、滑动窗口、以及简单模拟
关于滑动窗口
滑动窗口我大一的时候跟着灵神的题库刷过一部分,简单题可以使用大框架,比较难的时候需要一点trick。
以一道题引入核心框架。
Leetcode.1456 定长子串中元音的最大数目
题目链接:https://leetcode.cn/problemsmaximum-number-of-vowels-in-a-substring-of-given-length/description/
题目描述:给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
class Solution {
public:
int maxVowels(string s, int k) {
// 定义初值
int ans = 0, vowel = 0;
// 检测是否是元音字母
auto check = [&](char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
};
// 1.进入窗口
for (int i = 0; i < s.size(); i ++) {
// 2.更新答案
if (check(s[i])) {
vowel ++; // 检测当前窗口内的元音数量
}
if (i - k + 1 < 0) continue; // 如果窗口不够长,那就跳过这次循环
ans = max(ans, vowel); // 找到了符合题意的窗口,那我们就更新一下答案
// 3.离开窗口
if (check(s[i - k + 1])) vowel --; // 因为新加入的字母会在循环开始进行check,我们只需要检测退出的字母。
}
return ans;
}
};
基本的框架就是,进入窗口,更新答案,离开窗口。
这个过程当中可能会用到的trick:跳过非法状态,利用一些特殊的性质来更新答案......
Leetcode 209. 长度最小的子数组
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/description/
题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size(), ans = 1E9, sum = 0, left = 0;
for (int right = 0; right < n; right ++) {
// 1.进入窗口
sum += nums[right];
// 2.保持最小
while (sum - nums[left] >= target) {
sum -= nums[left ++];
}
// 3.更新答案
if (sum >= target) ans = min(ans, right - left + 1);
}
return ans <= n ? ans : 0;
}
};
这个题目的更新答案就放到了最后一步,因为在这个题目当中,每一个需要判断的滑动的窗口大小都应该是符合题意的最小的窗口,所以需要先更新窗口左端点,再更新答案。
关于模拟
模拟题虽说按照思路去做就好了,但是有时候一个题一个样,其实不太好总结。多学多做,慢慢地就会找到其中的一些规律吧(大概)。
Leetcode 59.螺旋矩阵 II
题目链接:https://leetcode.cn/problems/spiral-matrix-ii/description/
题目描述:给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<pair<int, int>> dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
auto a = vector(n, vector(n, 0));
int x = 0, y = 0, d = 0;
for (int i = 1; i <= n * n; i ++) {
if (a[x][y] == 0) a[x][y] = i;
auto [dx, dy] = dirs[d];
int nx = x + dx, ny = y + dy;
if (nx < 0 || nx >= n || ny < 0 || ny >= n || a[nx][ny] != 0) {
d = (d + 1) % 4;
dx = dirs[d].first, dy = dirs[d].second;
nx = x + dx, ny = y + dy;
}
x = nx, y = ny;
}
return a;
}
};
这个题目在AcWing上面做过了,我觉得那种做法更加清晰。
思路是:每次枚举需要填入的数,从\(1\)到\(n * n\),固定一个方向\(d\),每次只处理当前格子,并判断下一个格子。如果下一个格子越界了,或者已经填入了数据,那么就需要拐弯。最后不要忘记跳到下一个格子。
关于前缀和
这里讨论的前缀和,主要是指“前缀和的思想”,就是通过某种可逆的、具有线性性质的运算,都可以通过预处理一个前缀数组,然后来完成\(O(1)\)的查询操作。
kamacoder 44. 开发商购买土地
题目链接:https://kamacoder.com/problempage.php?pid=1044
题目描述:在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。
现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。
然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。 为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。
注意:区块不可再分。
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using u128 = unsigned __int128;
using i128 = __int128;
#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define Fd(i, a, b) for (int i = (a); i >= (b); i--)
#ifndef DEBUG
struct __X {
__X& operator<<(const auto& str) { return *this; }
void sp(const std::string& str = "") {}
} dout;
#define debug(x) ;
#endif
constexpr int mod = 998244353;
constexpr int MOD = 1E9 + 7;
constexpr int INF = 1E9;
constexpr int N = 2E5 + 10;
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> a(n, std::vector<int>(m, 0));
i64 sum = 0;
for (auto &row : a) for (auto &x : row) std::cin >> x, sum += x;
// 处理两个前缀和 O(1)查询划分之后的结果
std::vector<int> horizontal(n, 0), vertical(m, 0);
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++) {
horizontal[i] += a[i][j];
}
}
debug(horizontal);
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
vertical[i] += a[j][i];
}
}
debug(vertical);
// 枚举所有的切点 定义切点分给A
i64 ans = INT_MAX;
i64 now = 0;
for (int i = 0; i < n; i ++) {
now += horizontal[i];
ans = std::min(ans, std::abs(sum - now * 2));
}
now = 0;
for (int i = 0; i < n; i ++) {
now += vertical[i];
ans = std::min(ans, std::abs(sum - now * 2));
}
std::cout << ans << '\n';
}
/* By Tangzy
⠄⠄⠄⠄⢠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣯⢻⣿⣿⣿⣿⣆⠄⠄⠄
⠄⠄⣼⢀⣿⣿⣿⣿⣏⡏⠄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣧⢻⣿⣿⣿⣿⡆⠄⠄
⠄⠄⡟⣼⣿⣿⣿⣿⣿⠄⠄⠄⠈⠻⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⣿⣿⣿⠄⠄
⠄⢰⠃⣿⣿⠿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠙⠿⣿⣿⣿⣿⣿⠄⢿⣿⣿⣿⡄⠄
⠄⢸⢠⣿⣿⣧⡙⣿⣿⡆⠄⠄⠄⠄⠄⠄⠄⠈⠛⢿⣿⣿⡇⠸⣿⡿⣸⡇⠄
⠄⠈⡆⣿⣿⣿⣿⣦⡙⠳⠄⠄⠄⠄⠄⠄⢀⣠⣤⣀⣈⠙⠃⠄⠿⢇⣿⡇⠄
⠄⠄⡇⢿⣿⣿⣿⣿⡇⠄⠄⠄⠄⠄⣠⣶⣿⣿⣿⣿⣿⣿⣷⣆⡀⣼⣿⡇⠄
⠄⠄⢹⡘⣿⣿⣿⢿⣷⡀⠄⢀⣴⣾⣟⠉⠉⠉⠉⣽⣿⣿⣿⣿⠇⢹⣿⠃⠄
⠄⠄⠄⢷⡘⢿⣿⣎⢻⣷⠰⣿⣿⣿⣿⣦⣀⣀⣴⣿⣿⣿⠟⢫⡾⢸⡟⠄.
⠄⠄⠄⠄⠻⣦⡙⠿⣧⠙⢷⠙⠻⠿⢿⡿⠿⠿⠛⠋⠉⠄⠂⠘⠁⠞⠄⠄⠄
⠄⠄⠄⠄⠄⠈⠙⠑⣠⣤⣴⡖⠄⠿⣋⣉⣉⡁⠄⢾⣦⠄⠄⠄⠄⠄⠄⠄⠄
*/
这个题目的思想非常显然,就是先预处理出每一行的和,每一列的和,然后通过枚举所有可能的划分点,去更新答案。
关于数组总结
文章链接:https://programmercarl.com/数组总结篇.html#数组的经典题目
关于数组其实可以展开的题目类型就是那些,基本的就那么多。
想刷题的话可以去看一下灵神题单的前两个专题。
题单链接:https://leetcode.cn/discuss/post/3141566/ru-he-ke-xue-shua-ti-by-endlesscheng-q3yd/

浙公网安备 33010602011771号