这里提供两种解法。
第一种方法,暴力法。初始化子数组的最小长度为无穷大,枚举数组nums中的每个下标作为子数组的开始下标,对于每个开始下标i,需要找到大于或等于i的最小下标j,使得从nums[i]到nums[j]的元素和大于或等于s,并更新子数组的最小长度(此时子数组的长度是j−i+1)。该方法的时间复杂度是O(N²)。
int minSubArrayLen(int s, int* nums, int numsSize){
int sum,len,minlen = INT_MAX;
if(numsSize == 0)
return 0;
for(int i=0; i<numsSize; ++i){
sum = 0;
for(int j=i; j<numsSize; ++j){
sum += nums[j];
if(sum >= s){
len = j-i+1;
if(len < minlen) minlen = len;
break;
}
}
}
return minlen==INT_MAX?0:minlen;
}
第二种方法,二分查找。为了使用二分查找,需要额外创建一个数组sums用于存储数组nums的前缀和,其中sums[i]表示从nums[0]到nums[i−1]的元素和。得到前缀和之后,对于每个开始下标i,可通过二分查找得到大于或等于i的最小下标bound,使得 sums[bound]−sums[i−1]≥s,并更新子数组的最小长度(此时子数组的长度是 bound−(i−1))。 因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。
int lower_bound(int *a, int l, int r, int q) {
if (a[r] < q) return -1;
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] >= q) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
int minSubArrayLen(int s, int *nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
int ans = INT_MAX;
int *sums = (int *)malloc(sizeof(int) * (numsSize + 1));
// 为了方便计算,令 size = n + 1
// sums[0] = 0 意味着前 0 个元素的前缀和为 0
// sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
// 以此类推
for (int i = 1; i <= numsSize; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 1; i <= numsSize; i++) {
int target = s + sums[i - 1];
int bound = lower_bound(sums, 1, numsSize, target);
if (bound != -1) {
ans = fmin(ans, bound - (i - 1));
}
}
return ans == INT_MAX ? 0 : ans;
}
二分查找的时间复杂度是O(NlogN),由于使用了新的数组,所以内存消耗要大一些。