一、环境说明
- 本文是 LeetCode 209题 : 长度最小的子数组,使用c语言实现
- 一题双解
- 测试环境:Visual Studio 2019
二、代码展示
2.1 前缀和+二分查找
//前缀和+二分查找
int binarySearch(int left,int right,int target,int nums[]){
if(nums[right]<target){//说明最大前缀和小于target
return -1;
}
int mid = 0;
while(left<=right){
mid=left+((right-left)>>1);
if(nums[mid]>=target){//大于目标值
right = mid -1;
}else{//小于等于目标值
left = mid +1;
}
}
return left;
}
int minSubArrayLen(int target, int* nums, int numsSize) {//求长度最小的子数组
int* sums = (int*)calloc(numsSize + 1, sizeof(int));//前缀和数组
for (int i = 1; i <= numsSize; i++) {//第0个前缀和为0,第1个前缀和为0+nums[0],以此类推,
//第n个前缀和为sums[n-1] + nums[n-1],意为前n-2个数+第n-1个数。
sums[i] = sums[i - 1] + nums[i - 1];
}
int ans = INT_MAX;//
for (int i = 1; i <= numsSize; i++) {
int search_target = target + sums[i - 1];//搜索的位置,是前缀和大于对应的位置
int len = binarySearch(1, numsSize, search_target, sums);//第一个小于target+sums[i]的位置,
//就是从i开始,前缀和>=target的一个长度
if (len != -1) {
ans = ans < len - i + 1 ? ans : len - i + 1;//答案是最小长度
}
}
return ans == INT_MAX ? 0 : ans;//ans==INT_MAX,说明ans没有进入循环,整个数组前缀和都小于target
}
2.2 滑动窗口
//滑动窗口
int minSubArrayLen(int target, int* nums, int numsSize){//求长度最小的子数组
int left = 0,right = 0;
int sum =0,ans = INT_MAX;
while(right<numsSize){//遍历nums
sum+=nums[right];//sum每次加上当前nums[right]数字
while(sum>=target){//left右移
ans = ans<right -left +1?ans:right - left +1;
sum-=nums[left++];//sum减去nums[left]对应数字,left右移
}
right++;//right右移
}
return ans==INT_MAX?0:ans;
}
三、思路分析
- 这是作者第一次接触前缀和。虽然官方答案最优解给的是滑动窗口,但是这题的前缀和+二分查找也很简单。适合新手入坑前缀和。
- 滑动窗口很简单,不讲了。
- 讲讲前缀和。
- 这种计算区间内数字之和的题目,区间内全是正数。用前缀和,生成递增数组,然后就可以二分查找啦。
- 生成前缀和数组,有个技巧,初始化第0个前缀和sums[0]=0。遍历从i=1开始,第1个前缀和sums[1]=0+nums[0],第n个前缀和sums[n]=sums[n-1]+nums[n-1],第n个前缀和=前n-2个数+第n-1个数。
- 二分查找是个手感问题,比如我这里循环体内,设置nums[mid]>=target,它的搜索结果left,就是第一个大于target的数字位置。想要提升能力的读者,可以做二分专项~
四、代码分析
- 看注释 ^ _ ^
五、AC
六、复杂度分析
滑动窗口复杂度
- 时间复杂度:O(n) ,n是nums数组的大小,遍历nums的时间复杂度为O(n)。
- 空间复杂度:O(1),除若干变量使用的常量空间,没有使用额外的线性空间。
前缀和+二分查找复杂度
- 时间复杂度:O(nlogn) ,n是nums数组的大小,遍历nums并且二分查找target的时间复杂度是O(nlogn)。
- 空间复杂度:O(n),额外申请了sums数组,n是sums数组的大小,用于保存每个位置的前缀和,空间复杂度O(n)