算法 前缀和|滑动窗口|合并区间

前缀和

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。子数组是数组中元素的连续非空序列。O(nlogn)

这道题的关键在于如何以O(1)的复杂度发现和为k的子数组个数, 重点在于子数组一定连续, 连续的一段区间的和可以用前缀以O(1)求得, 问题在于如何知道这样的数的个数.

对于运算到第i处的前缀per[i], 前面可以和其相减得到k的per, 有或没有, 有多少都是问题, 那么就可以进行per值的存储, 要把其数量也一并记录, 那么用map就可以了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int subarraySum(vector<int>& nums, int k){
vector<int> pre(nums.size() + 10);
map<int, int> mp; // <前缀和, 出现的次数>
mp[0] = 1;
int ans = 0;
for(int i = 0; i < nums.size(); i ++ )
{
pre[i] = nums[i];
if(i != 0) pre[i] += pre[i - 1];
if(mp.count(pre[i] - k)) ans += mp[pre[i] - k];
mp[pre[i]]++;
}
return ans;
}
};

滑动窗口

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值

标准例题, 深入理解的关键在于这里的滑动窗口是一个固定值, 就要把控制窗口的变量和队列的操作分清楚.

  • 队列 : 本题中要维护的是一个单减队列, 就是为了保证队头一定是最大值, 为了实现这个目的, 只要新元素比队尾元素小就要弹出队尾元素, 直到遇到大于等于的.

  • 窗口 : 窗口的移动在遍历中实现, 可以想到只要i - k >= 0, 每次向后移动一格就一定会离开一个加入一个.

  • 问题的关键在于队列会提前弹出部分元素, 所以队列的实际大小和原窗口是匹配不上的, 也就是说, 即使i - k >= 0, 如果队头还没有到应该弹出的时间, 就不能弹出, 而到了应该弹出的时候就必须弹出.

  • 问题就转化成了 : 随着窗口的移动, 如何确定队头所在的元素是否离开了区域?

    其实只需要对比当前要离开的元素是否和队头元素值相等就行了, 因为如果是其他值, 就一定是还没轮到队头, 不然不可能会出现这个队头存在的情况.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
deque<int> dq;
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 将k范围的内容先入队, 求最大值
vector<int> ret;
int n = nums.size();
for(int i = 0; i < n; i ++ )
{
// 把小的都去掉, 因为不会影响最大值, 当前最大值就一定在队头
while(dq.size() && dq.back() < nums[i]) dq.pop_back();
dq.push_back(nums[i]);
if(i - k >= 0 && dq.front() == nums[i - k]) dq.pop_front();
if(i - k + 1 >= 0) ret.push_back(dq.front());
}
return ret;
}
};

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

例题, 核心在于区间需要排序, 我们需要在左端点从小到达排列的前提下去分析才能有效.

剩下的其实就是控制住当前区间的左右端点[nl, nr], 对新加入的区间[l, r]进行分析 :

  • 加入l > nr, 那么区间根本不相交, 这是一个新的区间, 于是将大权转移.
  • l <= nr, 那么区间相交, 接着就是看右端点还需不需要更新 :
    • r > nr, 新的右边界更大, 右边界主权转移.
    • r <= nr, 说明这个新区间完全被包含在原区间中, 不做调整.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> ret;
vector<vector<int>>& a = intervals;
sort(a.begin(), a.end());
int nl = -1, nr = -1;
for(int i = 0; i < a.size(); i ++ )
{
int l = a[i][0], r = a[i][1];
if(l > nr)
{
if(nr != -1)
{
vector<int> v = {nl, nr};
ret.push_back(v);
}
nl = l, nr = r;
}
else
{
if(nr < r) nr = r;
}
}
vector<int> v = {nl, nr};
bool flag = false;
for(auto e : ret)
{
if(e[0] == nl)
{
flag = true;
break;
}
}
if(!flag) ret.push_back(v);
return ret;
}
};

算法 前缀和|滑动窗口|合并区间
http://example.com/2025/02/25/[算法复健] 前缀和 滑动窗口 合并区间/
作者
天目中云
发布于
2025年2月25日
许可协议