代码随想录算法训练营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/

posted @ 2025-08-22 15:46  Tangzy0121  阅读(784)  评论(0)    收藏  举报