cpp
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// sort(nums.begin(),nums.end()) 不能先排序再进行操作,题目要求的是连续子数组
//使用快慢指针
int left =0;
int n = nums.size();
int sum =0;
int len =INT32_MAX;//初始化长度为int类型的最大整数
for(int right = 0;right<n;right++){
sum +=nums[right];
while(sum>=target){
sum-=nums[left];
len = min(len,right-left+1);
left++;
}
}
return len == INT32_MAX?0:len;
}
};
是的,除了暴力法(O(n^2)
)外,还有一种 更高效的方法 ,即使用 滑动窗口(双指针法) ,时间复杂度可以优化到 O(n)
。
滑动窗口法(双指针法):
滑动窗口法的核心思想是维护一个动态窗口,窗口内的元素的和不断扩展和缩小,直到找到满足条件的子数组。具体来说,我们使用两个指针,分别表示窗口的左右边界,通过扩展右边界来增加窗口的元素,并根据当前窗口和的大小决定是否需要收缩左边界。
具体步骤:
- 使用两个指针
left
和right
表示窗口的左右边界。初始时,left = 0
,right = 0
。 - 不断增加右指针,将
nums[right]
加到当前窗口的和中。 - 每当当前窗口的和大于或等于
s
时,尝试缩小窗口(通过移动左指针),并更新最短子数组长度。 - 继续扩展右指针,直到遍历完整个数组。
优点:
- 时间复杂度:
O(n)
:每个元素最多被左右指针访问一次,因此总时间复杂度为O(n)
。 - 空间复杂度:
O(1)
:只用了常数级别的额外空间。
代码实现:
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n = nums.size();
int result = INT32_MAX; // 最小子数组长度
int sum = 0; // 当前窗口的和
int left = 0; // 左指针
for (int right = 0; right < n; ++right) {
sum += nums[right]; // 增加右指针的元素到窗口和中
// 当窗口和 >= s 时,尝试收缩窗口,更新最小长度
while (sum >= s) {
result = min(result, right - left + 1); // 更新最小子数组长度
sum -= nums[left]; // 缩小窗口,减少左边的元素
left++; // 左指针右移
}
}
// 如果 result 没有更新,说明没有找到符合条件的子数组,返回 0
return result == INT32_MAX ? 0 : result;
}
};
代码解析:
-
初始化:
result
用来存储满足条件的最短子数组长度,初始为INT32_MAX
。sum
用来存储当前滑动窗口的和,初始为0
。left
是左指针,表示窗口的左边界,初始为0
。
-
右指针遍历数组:
- 外层循环的
right
指针遍历数组的每个元素,通过sum += nums[right]
扩展当前窗口的和。
- 外层循环的
-
判断和是否满足条件:
- 每当
sum >= s
时,说明当前窗口的和已经满足题目的要求,可以尝试收缩窗口,更新最短子数组长度result
。 - 在
while (sum >= s)
循环中,左指针left
右移,减去nums[left]
,即收缩窗口,直到sum
小于s
为止。每次都更新result
为当前窗口的长度right - left + 1
和之前的最小值。
- 每当
-
返回结果:
- 如果
result
仍然是初始的INT32_MAX
,说明没有找到符合条件的子数组,返回0
。 - 否则,返回最短的符合条件的子数组的长度。
- 如果
示例:
假设 nums = [2, 3, 1, 2, 4, 3]
和 s = 7
,下面是滑动窗口的执行过程:
-
初始化:
left = 0
,sum = 0
,result = INT32_MAX
-
右指针遍历:
right = 0
:sum += 2
,sum = 2
(不满足条件)right = 1
:sum += 3
,sum = 5
(不满足条件)right = 2
:sum += 1
,sum = 6
(不满足条件)right = 3
:sum += 2
,sum = 8
(满足条件)- 进入
while (sum >= 7)
:result = min(INT32_MAX, 3 - 0 + 1) = 4
sum -= 2
,left++
,sum = 6
(不满足条件,退出while
)
- 进入
right = 4
:sum += 4
,sum = 10
(满足条件)- 进入
while (sum >= 7)
:result = min(4, 4 - 1 + 1) = 3
sum -= 3
,left++
,sum = 7
(满足条件)result = min(3, 5 - 2 + 1) = 2
sum -= 1
,left++
,sum = 6
(不满足条件,退出while
)
- 进入
right = 5
:sum += 3
,sum = 9
(满足条件)- 进入
while (sum >= 7)
:result = min(2, 5 - 3 + 1) = 2
sum -= 2
,left++
,sum = 7
(满足条件)result = min(2, 6 - 4 + 1) = 2
sum -= 4
,left++
,sum = 3
(不满足条件,退出while
)
- 进入
-
返回结果 :
result = 2
,返回 2。
时间复杂度:
- 时间复杂度:
O(n)
:每个元素最多被左右指针访问一次,因此总时间复杂度为O(n)
,其中n
是数组的长度。
空间复杂度:
- 空间复杂度:
O(1)
:只使用了常数级别的额外空间。
总结:
总结:
-
滑动窗口法(双指针法)比暴力法更高效,时间复杂度由
O(n^2)
降低到O(n)
,适用于此类连续子数组和问题,特别是在处理较大数组时能够显著提高性能。 -
这行代码的作用是判断是否找到了符合条件的子数组,如果没有找到符合条件的子数组,就返回
0
。具体来说:return result == INT32_MAX ? 0 : result;
解释:
-
result == INT32_MAX
:这里的
result
最开始被初始化为INT32_MAX
,即int
类型的最大值(通常为2147483647
)。INT32_MAX
是一个非常大的值,通常用作一个"哨兵值"来标记某个变量是否已经被更新过。在整个过程中,result
会被更新为最小符合条件的子数组长度。如果没有找到符合条件的子数组,result
仍然保持为这个初始值。 -
? 0 : result
:- 如果
result
没有被更新过,还是等于INT32_MAX
,说明没有找到符合条件的子数组,这时就返回0
。 - 如果
result
已经被更新过,说明找到了一个符合条件的子数组,那么就返回result
,即最短符合条件的子数组长度。
- 如果
-
这行代码的目的是在没有找到符合条件的子数组时返回
0
,否则返回最短的符合条件的子数组的长度。