一道刷了两三遍的滑动窗口题:第一遍没了解思路,第二遍看过灵神题解并结合AI而尝试,第三遍独立写出该题并于2025年5月18日写下该篇博客进行记录,以勉励以后的计算机练习
感谢一切帮助我的人和物,感谢自己,感谢这个世界
题目链接209. 长度最小的子数组 - 力扣(LeetCode)
如上图所示:
我们需要找到一个最小的连续子数组来满足题目条件,该子数组中的元素总和应大于等于target,我的第一篇尝试,虽然我也不太记得自己是用什么思路想的了,不过按照现在的思路看之前写的题,只感到稚嫩和不熟练
//分块
/*
比较块,比较答案和最小值
窗口块,快慢指针窗口滑动
*/
int slow=0;
int ans=INT_MAX;
int sum=0;
for(int fast=0;fast<nums.size();fast++)
{
sum+=nums[fast];
while(sum>=target)//慢指针窗口
{
int len=fast-slow+1;
ans=min(ans,len);
sum-=nums[slow];
slow++;
}
}
if(slow==0) return 0;
return ans;
这篇应该是看题解过的,因为我没什么印象了(雾)
这篇AC代码和第三篇类似,我讲解第三篇时再好好谈谈
第二篇
int n=nums.size();
int ans=0;
int left=0;
int sum=0;
for(int i=0;i<n;i++) sum+=nums[i];//求出总和
for(int right=0;right<n;right++)//循环
{
sum-=nums[right];//总和相减
while(sum<target)
{
sum+=nums[left];//相加,直到总和大于等于target
left++;//左指针右移
}
ans=min(ans,right-left+1);
}
return ans;
这份代码中我采用了逆向思维进行查询,我们先将所有值相加得到sum,然后,很愚蠢的一件事情发生了,我居然没注意如果sum小于target怎么办,也就是说,如果sum小于target,那么直接返回0即可,但我疏忽了这一点。
好,假设有数据点sum>=target,进行以下操作
其次,我在right的遍历过程中,我是这样思考的,每轮遍历就将sum减nums[right],
如果相减之后的值仍然大于等于target,那么我就将最小进行记录,
如果相减之后的值小于target,那么我就将它放入循环用left进行相加,
这题的原意已经被我完全混淆,因为如果从right开始相减,那不就等同于左边界是right而右边界是nums.size()吗,这样求出来的结果怎么可能正确!
不用看了,题目理解错误,还想妄图逆向思维进行计算,不错才怪
第三
int n=nums.size();
int ans=1e9+5;
int left=0;
int sum=0;
int res=0;
for(int i=0;i<nums.size();i++) res+=nums[i];
if(res<target) return 0;
for(int right=0;right<nums.size();right++)
{
sum+=nums[right];
while(sum>=target)
{
ans=min(ans,right-left+1);
sum-=nums[left];
left++;
}
}
return ans;
第三篇AC代码和第一篇很像,看完这篇之后可以和第一篇对照一下
第三篇代码:
第一,这次我们注意到了如果所有数得到的和res小于target是一个不符合题意的解,那么这个就需要直接退出返回0
第二,我们这次的滑动窗口right从0开始,然后向右滑动,依次相加,如果得到的sum值大于等于target,那么我们就要进入循环将窗口缩小,直到sum小于0,并且,我们的循环中要注意计算答案,不能放在外循环中,那里得到的窗口都是sum<target的
以下是我在闲余时写的笔记
// //分块
// /*
// 比较块,比较答案和最小值
// 窗口块,快慢指针窗口滑动
// */
// int slow=0;
// int ans=INT_MAX;
// int sum=0;
// for(int fast=0;fast<nums.size();fast++)
// {
// sum+=nums[fast];
// while(sum>=target)//慢指针窗口
// {
// int len=fast-slow+1;
// ans=min(ans,len);
// sum-=nums[slow];
// slow++;
// }
// }
// if(slow==0) return 0;
// return ans;
第一份答题题解
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int ans = INT_MAX; // 初始化为最大值
int left = 0; // 左指针
int sum = 0; // 当前窗口的和
for (int right = 0; right < n; right++) { // 右指针
sum += nums[right]; // 将右指针的值加入当前窗口的和
// 当当前窗口的和大于等于目标值时,尝试缩小窗口
while (sum >= target) {
ans = min(ans, right - left + 1); // 更新最小长度
sum -= nums[left]; // 移除左指针的值
left++; // 左指针右移
}
}
// 如果没有找到满足条件的子数组,返回 0
return (ans == INT_MAX) ? 0 : ans;
}
};
//正统答案
思路确实存在逻辑上的问题,这也是导致代码无法正确运行的主要原因。在滑动窗口问题中,我们需要通过动态调整窗口的大小来找到满足条件的最小窗口,而不是通过扩大窗口来寻找满足条件的窗口。以下是对你思路的详细分析和解释:
### 问题分析
1. **窗口扩大的逻辑问题**:
- 你的代码中,当 `sum < target` 时,你试图通过增加 `left` 来调整窗口大小,这与滑动窗口的常规逻辑完全相反。
- 在滑动窗口问题中,当当前窗口的和小于目标值时,应该通过增加 `right` 指针来扩大窗口,而不是减少 `left`。
2. **窗口缩小的逻辑问题**:
- 当窗口的和大于等于目标值时,应该通过增加 `left` 指针来缩小窗口,以寻找更小的满足条件的窗口。
- 你的代码中没有正确实现这一点。
3. **变量 `sum` 的使用问题**:
- 你在代码中将 `sum` 初始化为整个数组的总和,这在逻辑上是错误的。`sum` 应该是当前窗口的和,而不是整个数组的总和。
### 修正思路
为了实现滑动窗口的正确逻辑,我们需要:
1. **初始化窗口**:从左到右逐步扩大窗口,直到窗口的和大于等于目标值。
2. **调整窗口**:当窗口的和大于等于目标值时,尝试通过增加 `left` 指针来缩小窗口,同时更新最小窗口长度。
3. **重复步骤**:继续扩大窗口(增加 `right` 指针),重复上述过程,直到遍历完整个数组。
### 修正后的代码
以下是修正后的代码,使用滑动窗口的正确逻辑:
```cpp
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int ans = INT_MAX; // 初始化为最大值
int left = 0; // 左指针
int sum = 0; // 当前窗口的和
for (int right = 0; right < n; right++) { // 右指针
sum += nums[right]; // 将右指针的值加入当前窗口的和
// 当当前窗口的和大于等于目标值时,尝试缩小窗口
while (sum >= target) {
ans = min(ans, right - left + 1); // 更新最小长度
sum -= nums[left]; // 移除左指针的值
left++; // 左指针右移
}
}
// 如果没有找到满足条件的子数组,返回 0
return (ans == INT_MAX) ? 0 : ans;
}
};
```
### 修正后的代码说明
1. **初始化 `ans` 为 `INT_MAX`**:
- 这样可以确保在第一次更新时能够正确记录最小长度。
2. **滑动窗口逻辑**:
- 使用两个指针 `left` 和 `right` 来表示窗口的左右边界。
- 在每次循环中,将 `right` 指针的值加入当前窗口的和。
- 如果当前窗口的和大于等于目标值 `target`,则尝试通过右移 `left` 指针来缩小窗口,并更新最小长度 `ans`。
3. **返回值**:
- 如果最终 `ans` 仍为 `INT_MAX`,说明没有找到满足条件的子数组,返回 `0`。
### 示例
假设输入:
```cpp
target = 7
nums = {2, 3, 1, 2, 4, 3}
```
运行修正后的代码,输出:
```
2
```
解释:最小长度的子数组是 `[4, 3]`,长度为 2。
### 总结
你的思路中确实存在逻辑问题,主要是在窗口的扩大和缩小操作上。滑动窗口的核心思想是:
1. **扩大窗口**:通过增加 `right` 指针来扩大窗口,直到窗口的和大于等于目标值。
2. **缩小窗口**:当窗口的和大于等于目标值时,通过增加 `left` 指针来缩小窗口,同时记录最小窗口长度。
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n=nums.size();
int ans=1e9+5;
int left=0;
int sum=0;
int res=0;
for(int i=0;i<nums.size();i++) res+=nums[i];
if(res<target) return 0;
for(int right=0;right<nums.size();right++)
{
sum+=nums[right];
while(sum>=target)
{
ans=min(ans,right-left+1);
sum-=nums[left];
left++;
}
}
return ans;
}
};
二刷题解
当然,我没有忘记后面的进阶思考,如果它这样提问,那么我就将在不久后尽量将其写出
2025/5/18