题目描述
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数。
子数组是数组中元素的连续非空序列。
示例
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
解法
1.暴力算法
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k == 0) return {};
if(k == 1) return nums;
vector <int> ans;
for(int i = 0;i <= nums.size() - k;i ++){
int max_val = nums[i];
for(int j = i;j < i + k;j ++){
max_val = max(max_val,nums[j]);
}
ans.push_back(max_val);
}
return ans;
}
};
暴力算法的时间复杂度为0(nk),大部分案例都通过不了。
2.暴力算法(优化)
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k == 0) return {};
if(k == 1) return nums;
vector<int> ans;
int max_val = nums[0], max_idx = 0;
// 初始化第一个窗口的最大值
for(int i = 1; i < k; i++){
if(nums[i] >= max_val){
max_val = nums[i];
max_idx = i;
}
}
ans.push_back(max_val);
// 处理后续窗口
for(int i = 1; i <= nums.size() - k; i++){
int right = i + k - 1; // 新窗口的右边界
// 如果最大值还在窗口内
if(max_idx >= i) {
if(nums[right] > max_val) { // 新元素更大
max_val = nums[right];
max_idx = right;
}
ans.push_back(max_val);
}
// 最大值已移出窗口
else {
// 重新计算当前窗口的最大值
max_val = nums[i];
max_idx = i;
for(int j = i + 1; j <= right; j++){
if(nums[j] >= max_val){
max_val = nums[j];
max_idx = j;
}
}
ans.push_back(max_val);
}
}
return ans;
}
};
通过判断窗口滑动后最大值是否还在窗口中,并且和新传入的窗口右边界元素进行比较,减少了大量判断,对纯暴力算法进行了优化,但是最坏的时间复杂度依然为0(nk),仍然有两组案例通过不了。
3.双端队列
思路:
仔细阅读该题目不难发现,题目和双端队列的定义很相似,于是考虑引入双端队列,可以简化编程,降低时间复杂度。
cpp
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty() || k == 0) return {};
vector<int> ans;
deque<int> dq; // 存储索引
for(int i = 0; i < nums.size(); i++) {
// 移除超出窗口范围的元素
if(!dq.empty() && dq.front() == i - k) {
dq.pop_front();
}
// 移除队列中比当前元素小的元素(关键)
while(!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
// 添加当前元素索引
dq.push_back(i);
// 当窗口形成时,添加最大值
if(i >= k - 1) {
ans.push_back(nums[dq.front()]);
}
}
return ans;
}
};
时间复杂度:O(n),空间复杂度:O(k)。
核心优化思想:单调队列,这种方法的巧妙之处在于它将找最大值的过程分摊到了每个元素的处理中,而不是为每个窗口单独计算,从而实现了线性时间复杂度。