算法--数组--长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:
- 1 <= target <= 10^9
- 1 <= nums.length <= 10^5
- 1 <= nums[i] <= 10^5
1 解答
先写再看噢~
解答
这个讲得很详细👉代码随想录
1.1 思路
没太看懂题目,先列出来找一下规律,1个,2个之和,3个之和,...
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function (target, nums) {
// 先列出来找规律,1个,2个之和,3个之和,...
// 1个
for (let i = 0; i <= nums.length; i++) {
if (nums[i] >= target) {
return 1;
}
}
// 2个
for (let i = 0; i + 1 <= nums.length; i++) {
if (nums[i] + nums[i + 1] >= target) {
return 2;
}
}
// 3个
for (let i = 0; i + 2 <= nums.length; i++) {
if (nums[i] + nums[i + 1] + nums[i + 2] >= target) {
return 3;
}
}
// ...
// k个
for (let i = 0; i + k <= nums.length; i++) {
// const sum = nums[i] + nums[i + 1] + ... + nums[i + k];
// 求和
let sum = 0;
for (let j = i; j <= i + k; j++) {
sum += nums[j];
}
if (sum >= target) {
return k;
}
}
// ...
return 0;
};
整合一下这个规律
// 三层 for 循环:长度为 k 的窗口(循环一次)滑过去(循环一次),窗口内求和(循环一次)
var minSubArrayLen = function (nums, target) {
for (let k = 0; k <= nums.length; k++) {
for (let i = 0; i + k <= nums.length; i++) {
let sum = 0;
for (let j = i; j <= i + k; j++) {
sum += nums[j];
}
if (sum >= target) {
return k + 1;
}
}
}
return 0;
};
1.2 优化
var minSubArrayLen = function (target, nums) {
let result = Infinity; // 最终的结果
let sum = 0; // 子序列的数值之和
let subLength = 0; // 子序列的长度
for (let i = 0; i < nums.length; i++) {
// 设置子序列起点为i
sum = 0; // 总共运行 n(i=0,1,2,...,n-1) 次
for (let j = i; j < nums.length; j++) {
// 设置子序列终止位置为j
sum += nums[j]; // 总共运行 n-1(i=0), n-2(i=1), n-3(i=2), ... ,1(i=n+1) 次
if (sum >= target) {
// 一旦发现子序列和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength; // 取小
break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == Infinity ? 0 : result;
};
两层 for 循环,滑动窗口起点指向第i个(循环一次),终点j指向后一个,j不断往后移,边移边加(循环一次,长度小的留下),
o(n^2) = n + n-1 + n-2 + n-3 + ... + 1 = (1+n) * n /2 = n^2
1.3 优化--滑动窗口
一层 for 循环,滑动窗口起点指向第i个,终点指向第j个, j不断往后移,边移边加,若超了,i往后移,边移边减, o(2*n)=o(n)
var minSubArrayLen = function (target, nums) {
let result = Infinity; // 最终的结果
let sum = 0; // 滑动窗口数值之和
let i = 0; // 滑动窗口起始位置
let subLength = 0; // 滑动窗口的长度
for (let j = 0; j < nums.length; j++) {
sum += nums[j]; // 总共运行 n(i=0,1,2,...,n-1) 次
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= target) {
// 总共运行 1(j=0), 2(j=1), 3(j=2), ... ,n(j=n-1) 次 (x错
// 总共运行 n 次 (√对
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength; // 取小
sum -= nums[i]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
i++;
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == Infinity ? 0 : result;
};
不是两层循环就是n^2,i,j 最多移动n次
因为 target 为正整数,所以 满足 sum >= target 的窗口至少有1个数
因为每次的i不是从头开始的,而是从上一次继续往下走,满足 sum >= target 的窗口至少有1个数,所以 i++ 最多执行 n 次