长度最小的子数组
1.题目:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
2.思路:如果没有规定是连续子数组,则可以先把数组按从大到小排序,再遍历数组求前 i 个元素的和,使其和sum ≥ s,因为子数组中元素值越大,子数组的长度就越小。但是题目要求连续子数组,也就是不可以打乱原数组的排序。既然要求子数组长度最小,那么长度可以从1开始依次递增,直到增长到数组长度。但是最后超出了时间限制。
自己写的代码,超时:
int minSubArrayLen(int target, int* nums, int numsSize) {
int length = 1;
int sum = 0;
int i;
while (sum < target){
for (i = 0; i <= numsSize-length; ++i) {
sum = 0;
for (int j = 0; j < length; ++j) {
sum += nums[i+j];
}
// 如果不加这个 if 会先继续往后循环
if (sum >= target){
break;
}
}
if (length > numsSize){ // if 判断要在 length++ 之前
return 0;
}
length++;
}
return length-1;
}
3.题解:通过滑动窗口的思路解决。在暴力解法中使用两个for循环,一个for循环遍历起始位置,另一个for循环遍历终止位置。如果要在一个for循环内完成任务,此循环的索引应是终止位置,而不是起始位置。
滑动窗口:不断调节子序列的起始位置 start 和终止位置 end。
Q1:什么时候调节窗口的起始位置?
在for的一次循环中,窗口终点固定为 end,如果窗口元素之和 sum >= target,此时以 start 为起点(且以end为终点)的最小子数组长度已被找到,则移动 start 到下一个起点,找到下一个最小数组长度。
Q2:为什么 sum >= target 放在 while循环中而不是 if ?
书接上文,当移动 start 到下一个起点时(此时终点固定为end),虽然窗口尺寸变小了,但仍有可能 sum >= target ,因此需要不断移动 start 缩小窗口尺寸,更新最小数组长度。
Q3:什么时候调节窗口的终止位置?
当 sum < target 时,窗口起点固定为start,需要扩充窗口尺寸,即让 end 继续往前滑动,使窗口内元素的和 sum >= target,此时会不断地跳过 while 循环,执行 for 循环中的 end++。
int minSubArrayLen(int target, int* nums, int numsSize) {
int start = 0; // 窗口的起始位置,起始为 0,一定要初始化
int end; // 窗口的终止位置
int sum = 0; // 窗口内元素的和
int subLen = 0; // 窗口大小,也是子数组长度
int minLen = numsSize+1; // 子数组最小长度,设置比数组长度大的初始值,用于之后判断是否找到符合条件的子数组
// 当 sum < target 时,需要扩充窗口尺寸,即让 end 继续往前滑动,使窗口内元素的和 sum >= target
for (end = 0; end < numsSize; ++end) {
sum += nums[end];
// 为什么这里是 while,而不是if?
// 此时窗口终点固定为 end,如果 sum >= target,则不断移动 start 缩小窗口尺寸,找到最小数组长度
while (sum >= target){
subLen = end-start+1;
if (subLen < minLen){
minLen = subLen; // 更新子数组最小长度
}
// 当以 start 为起点的最小子数组长度已被找到,则移动 start 到下一个起点,找到下一个最小数组长度
sum -= nums[start]; // 下一个窗口的元素之和要去掉上一个起点的值
start++; // 移动起点
}
}
// 如果遍历完没有找到满足 sum >= target 的子数组,则 minLen 仍是初始值没有改变
if (minLen == numsSize+1){
return 0;
} else{
return minLen;
}
}
自己画的一些过程,等周末有空了再做个总结完善一下: