和为K的子数组
我的暴力双指针解法超时,并且此方法仅适用于全非负数组!
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
int count = 0; // 记录符合条件的子数组数量
// 外层循环:固定子数组的起始位置l
for (int l = 0; l < n; l++) {
int current_sum = 0; // 记录从l开始的子数组和
// 内层循环:扩展子数组的结束位置r(从l到n-1)
for (int r = l; r < n; r++) {
current_sum += nums[r]; // 累加当前元素到子数组和
// 若当前子数组和等于k,计数+1
if (current_sum == k) {
count++;
}
}
}
return count; // 返回总计数
}
};
枚举:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int count = 0;
for (int start = 0; start < nums.size(); ++start) {
int sum = 0;
for (int end = start; end >= 0; --end) {
sum += nums[end];
if (sum == k) {
count++;
}
}
}
return count;
}
};
前缀和+哈希表的解法:
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
// 前缀和 -> 该前缀和出现的次数
unordered_map<int, int> prefixSumCount;
// 初始化:前缀和为 0 的情况至少出现 1 次(处理子数组从索引 0 开始的情况)
prefixSumCount[0] = 1;
// 当前累计的前缀和
int currentSum = 0;
// 记录和为 k 的子数组个数
int result = 0;
for (int num : nums) {
// 累加当前数字,更新前缀和
currentSum += num;
// 核心逻辑:
// 如果存在前缀和为 (currentSum - k),说明这两个前缀和之间的子数组和为 k
// 例如:currentSum = 前缀和 A,若存在前缀和 B = A - k,则子数组 [B+1 ... A] 的和为 k
if (prefixSumCount.find(currentSum - k) != prefixSumCount.end()) {
result += prefixSumCount[currentSum - k];
}
// 更新当前前缀和的出现次数
prefixSumCount[currentSum]++;
}
return result;
}
};
滑动窗口最大值
#include <vector> // 引入vector容器,用于存储结果
#include <deque> // 引入deque双端队列,用于实现单调队列
using namespace std;
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res; // 存储每个滑动窗口的最大值
deque<int> dq; // 单调队列,存储元素的索引(核心数据结构)
// 队列特性:索引对应的nums元素值保持【单调递减】
// 遍历数组中的每个元素(i是当前元素的索引)
for (int i = 0; i < nums.size(); ++i) {
// 步骤1:移除队列中【超出当前窗口范围】的元素
// 当前窗口的有效索引范围是 [i - k + 1, i]
// 如果队首元素的索引 <= i - k,说明它在窗口左侧外,需要删除
if (!dq.empty() && dq.front() <= i - k) {
dq.pop_front(); // 从队首删除
}
// 步骤2:维护队列的【单调递减特性】
// 如果队尾元素对应的数值 <= 当前元素值,说明它不可能是未来窗口的最大值
// 持续从队尾删除这些元素,直到队尾元素 > 当前元素或队列为空
while (!dq.empty() && nums[dq.back()] <= nums[i]) {
dq.pop_back(); // 从队尾删除
}
// 步骤3:将当前元素的索引加入队列
// 此时队列仍保持单调递减,当前元素是队列中最小的(或唯一的)
dq.push_back(i);
// 步骤4:当窗口完全形成后(i >= k-1),记录当前窗口的最大值
// 由于队列单调递减,队首元素就是当前窗口的最大值对应的索引
if (i >= k - 1) {
res.push_back(nums[dq.front()]); // 加入结果
}
}
return res; // 返回所有窗口的最大值
}
};
最小覆盖字串
滑动窗口加哈希表
#include <string> // 引入string头文件,处理字符串
#include <unordered_map> // 引入哈希表头文件,用于统计字符出现次数
using namespace std; // 使用std命名空间,简化代码书写
class Solution {
public:
// 函数定义:寻找s中包含t所有字符的最小子串
string minWindow(string s, string t) {
// 两个哈希表:
// need:存储t中每个字符需要出现的次数
// window:存储当前窗口中每个字符出现的次数
unordered_map<char, int> need, window;
// 初始化need:统计t中所有字符的需求数量
for (char c : t) {
need[c]++; // 例如t="ABC",则need['A']=1, need['B']=1, need['C']=1
}
// 滑动窗口的左右指针(左闭右开区间 [left, right))
int left = 0, right = 0;
// valid:记录窗口中满足"需求数量"的字符种类数
// 例如t需要A:1、B:1,窗口中A=1且B=1时,valid=2
int valid = 0;
// start:最小覆盖子串的起始索引
// len:最小覆盖子串的长度(初始设为极大值)
int start = 0, len = INT_MAX;
// 右指针遍历s,扩张窗口
while (right < s.size()) {
// c:即将加入窗口的字符(右指针指向的字符)
char c = s[right];
// 右指针右移,扩大窗口范围
right++;
// 如果当前字符是t中需要的字符(存在于need中)
if (need.count(c)) {
// 将该字符加入window,计数+1
window[c]++;
// 若window中该字符的数量恰好等于need中的需求数量
// 说明该字符的需求已满足,valid+1
if (window[c] == need[c]) {
valid++;
}
}
// 关键:当窗口中所有字符的需求都满足(valid等于need的大小)
// 开始尝试收缩左边界,寻找更小的有效窗口
while (valid == need.size()) {
// 若当前窗口长度小于已记录的最小长度,更新结果
if (right - left < len) {
start = left; // 更新起始索引
len = right - left; // 更新长度(右开区间,无需+1)
}
// d:即将移除窗口的字符(左指针指向的字符)
char d = s[left];
// 左指针右移,缩小窗口范围
left++;
// 如果移除的字符是t中需要的字符
if (need.count(d)) {
// 若window中该字符的数量恰好等于need中的需求数量
// 移除后将不满足需求,valid-1
if (window[d] == need[d]) {
valid--;
}
// 将该字符从window中移除,计数-1
window[d]--;
}
}
}
// 若len仍为INT_MAX,说明没有找到有效子串,返回空
// 否则返回从start开始,长度为len的子串
return len == INT_MAX ? "" : s.substr(start, len);
}
};