每日一题,防止痴呆 = =
一、题目大意
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
二、题目思路以及AC代码
思路:
这道题我们的最普适的思路就是利用前缀和暴力枚举嘛,这样的时间复杂度是O(N2),然后我们进一步自然的是考虑对这种暴力的方式进行优化,因为前缀和数组是单调自增的,那自然可以考虑进行二分,具体的二分方法如下:
我们首先对数组的每个数进行枚举,这其实就是在枚举所求连续子序列的起始点,然后我们的终止点就可以通过对后面的子数组进行二分找到,然后总的时间复杂度就被背降为O(NlogN)了。
然后我看到进阶就懵了,为啥进阶才是到O(NlogN)?直接想到的是O(N),我一开始还以为这里打错了… 然后看了题解才想到双指针的O(N)的解法,下回看到涉及区间的题目还是先考虑一下双指针吧 = =。
这里双指针的方法就是我们设定前后两个指针分别表示子区间的两个端点,如果当前区间内的数和小于s,我们则将后面的指针向后移,否则将前面的指针向后移,直到后面的指针到达数组末尾,这样的时间复杂度就是O(N),只是没搞明白为啥进阶的题目时间复杂度反而更高 = =
AC代码
前缀和 + 二分:
#define MAX_INT 2147483647
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n_size = nums.size();
if (!n_size) return 0;
int pre[n_size + 1];
pre[0] = 0;
for (int i=1;i<=n_size;i++) {
pre[i] = nums[i-1] + pre[i-1];
// cout << pre[i] << " ";
}
int min_len = MAX_INT;
for (int i=0;i<=n_size;i++) {
int target = s + pre[i];
if (target > pre[n_size]) continue;
int l = i + 1, r = n_size;
while (l < r) {
int mid = (l + r) >> 1;
if (pre[mid] >= target) r = mid;
else l = mid + 1;
}
min_len = min(min_len, l - i);
}
if (min_len == MAX_INT) return 0;
return min_len;
}
};
双指针解法:
#define MAX_INT 2147483647
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n_size = nums.size();
if (!n_size) return 0;
int p = 0, q = 0;
int sum = nums[0];
int min_len = MAX_INT;
while (p < n_size && q < n_size) {
if (sum >= s) {
min_len = min(min_len, q - p + 1);
sum -= nums[p];
p++;
}
else {
q++;
if (q < n_size) sum += nums[q];
}
}
if (min_len == MAX_INT) return 0;
return min_len;
}
};
如果有问题,欢迎大家指正!!!