目录
unordered_set
滑动窗口算法
1.滑动窗口的概念
2.滑动窗口算法举例
3.实例
4.总结
unordered_set
C++ 11 为 STL 标准库增添了 4 种无序(哈希)容器 ,unordered_set 容器就是其一
unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,
唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。
实现 unordered_set 容器的模板类定义在<unordered_set>头文件,并位于 std 命名空间中。
这意味着,如果程序中需要使用该类型容器,则首先应该包含如下代码:
#include <unordered_set>
using namespace std;
一、滑动窗口的概念
顾名思义,就像一个滑动的窗口,套在一个序列中,左右的滑动,窗口内就是一个内容集。
二、滑动窗口算法举例
LeetCode_209长度最小的子数组(https ://leetcode-cn.com/problems/minimum-size-subarray-sum/)
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。
如果不存在符合条件的连续子数组,返回 0。
示例 :
输入: s = 7, nums = [2, 3, 1, 2, 4, 3]
输出 : 2
解释 : 子数组[4, 3] 是该条件下的长度最小的连续子数组。
思路:
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在本题中实现滑动窗口,主要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。
#include<iostream>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
int num[maxn];
int main()
{
ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> num[i];
}
int result = INT32_MAX; // 初始化子序列的最小长度为无穷大
int sum = 0; //滑动窗口数值之和
int i = 0; //滑动窗口起始位置
int len = 0; //滑动窗口的长度
for (int j = 0; j < n; j++) { //滑动窗口终止位置
sum += num[j];
while (sum >= m) {
len = j - i + 1;
result = result < len ? result : len;
sum -= num[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
cout<<(result == INT32_MAX ? 0 : result)<<'\n';
return 0;
}
三.实例:
1.2022 第十三届蓝桥杯省赛 C / C++ B组
试题F: 统计子矩阵
时间限制 : 1.0s 内存限制 : 256.0MB 本题总分 : 15分
【问题描述】
给定一个N×M的矩阵A,请你统计有多少个子矩阵(最小1×1,最大NxM)满足子矩阵中所有数的和不超过给定的整数K ?
【输入格式】
第一行包含三个整数N, M和K.
之后N行每行包含M个整数,代表矩阵A.
【输出格式】
一个整数代表答案。
【样例输入】
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12
【样例输出】
19
【样例说明】
满足条件的子矩阵一共有19,包含 :
大小为1×1的有10个。
大小为1×2的有3个。
大小为1×3的有2个。
大小为1×4的有1个。
大小为2×1的有3个。
【评测用例规模与约定】
对于30 % 的数据, N, M ≤ 20.
对于70 % 的数据,N, M ≤ 100.
对于100 % 的数据,1 ≤ N, M ≤ 500; 0 ≤ Aij ≤1000; 1 ≤ K ≤ 250000000.
思路:使用前缀和+滑动窗口(适用于解决矩阵)
使用纵向前缀和+二重循环枚举上下边界+滑动窗口左右边界实现
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 5e2 + 10;
int n, m,k;
ll a[maxn][maxn];
ll ans;
int main()
{
ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
//维护****纵向前缀和****
a[i][j] += a[i - 1][j];
}
}
for (int i = 1; i <= n; i++) { //遍历上边界
for (int j = i; j <= n; j++) { //遍历下边界
int l = 1, r = 1;
int sum = 0;
for (r = 1; r <= m; r++) {
sum += a[j][r] - a[i - 1][r]; // 加上右端点处的和
while (sum > k) {
sum -= a[j][l] - a[i - 1][l]; //减去移出去的左端点处的和
l++;
}
ans += r - l + 1; // 方法数就是找到的区间大小累加
}
}
}
cout << ans << '\n';
return 0;
}
2.LeetCode_3:无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出 : 3
解释 : 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2 :
输入 : "bbbbb"
输出 : 1
解释 : 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3 :
输入 : "pwwkew"
输出 : 3
解释 : 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路:利用滑动窗口+数据结构unordered_set的特性erase(特定位置)insert+count
#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;
unordered_set<char>occ;
string s;
int main()
{
ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin >> s;
int n = s.size();
int r = -1, ans = 0; // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
for (int i = 0; i < n; i++) {
if (i != 0)
occ.erase(s[i - 1]); //左指针向右移动一格,移除一个字符
while (r < n - 1 && !occ.count(s[r + 1])) {
occ.insert(s[r + 1]);
r++;
}
ans = max(ans, r - i + 1);
}
cout << ans << '\n';
return 0;
}
3.LeetCode_1004:最大连续1的个数 III
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], K = 2
输出:6
解释:
[1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:A = [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1], K = 3
输出:10
解释:
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
1 <= A.length <= 20000
0 <= K <= A.length
A[i] 为 0 或 1
#include<iostream>
using namespace std;
const int maxn = 2e4 + 10;
int n, k;
int nums[maxn];
int main()
{
ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin >> n >> k;
for (int i = 0; i <n; i++) {
cin >> nums[i];
}
int Count = 0;
int l = 0;
int ans = 0;
for (int i = 0; i < nums[i]; i++) { //枚举右边界
if (!nums[i])
Count++;
while (Count>k) {
if (!nums[l++])
Count--;
}
ans = max(i - l + 1, ans);
}
cout << ans << '\n';
return 0;
}
时间复杂度:O(N),因为每个元素只遍历了一次。
空间复杂度:O(1),因为使用了常数个空间。
四:总结
滑动数组不断的调节子序列的起始位置和终止位置
其中while的作用是1.通过调节左或右边界使上限条件不再满足,能够继续运行直到遍历完成
2.通过调节左或右边界使之满足上限条件,用另外功能使上限条件不再满足,能够继续运行直到遍历完成
其中for如果调节左边界,则while调节右边界,反之亦然