本题是facebook 的面试题。
问题描述:
一、第一种方法:暴力解法
该方法在 Leetcode 中会超时!
// 时间复杂度: O(n^3)
// 空间复杂度: O(1)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
assert(s > 0);
int res = nums.size() + 1;
for(int l = 0 ; l < nums.size() ; l ++)
for(int r = l ; r < nums.size() ; r ++){
int sum = 0;
for(int i = l ; i <= r ; i ++)
sum += nums[i];
if(sum >= s)
res = min(res, r - l + 1);
}
if(res == nums.size() + 1)
return 0;
return res;
}
};
分析:
使用两个指针 l 和 r 双重遍历数组,
1)当 l = 0 时,使用 r 遍历数组:
2)当 l = 1 时,使用 r 遍历数组:
3)一次类推,知道指针 l 遍历完成:
由于每移动一次指针 r 都需要 for 遍历数组求和sum, 所以时间复杂度为 O(n^3)
第二种方法:活动窗口法
活动窗口法分析:
①设定两个指针 i 和 j , 将数组区间nums[ i ... j] 看成窗口,
②if(j + 1 < nums.size() && sum < s)
则 j ++ ;
这时 sum += nums[ j ];
③ 当 sum > s
则 sum -= nums[ i ];
④当 sum < s,则移动指针 j
实现代码:
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
assert(s > 0);
int l = 0 , r = -1; // nums[l...r]为我们的滑动窗口
int sum = 0;
int res = nums.size() + 1;
while(l < nums.size()){ // 窗口的左边界在数组范围内,则循环继续
if(r + 1 < nums.size() && sum < s){
++r;
sum += nums[r];
}
else {// r已经到头 或者 sum >= s
sum -= nums[l];
l++;
}
if(sum >= s)
res = min(res, r - l + 1);
}
if(res == nums.size() + 1)
return 0;
return res;
}
};
int main() {
int nums[] = {2, 3, 1, 2, 4, 3};
vector<int> vec(nums, nums + sizeof(nums)/sizeof(int));
int s = 7;
cout << Solution().minSubArrayLen(s, vec) << endl;
return 0;
}
第三种方法:活动窗口法的另一种实现方法
实现代码:
//时间复杂度:O(n)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n = nums.size(), start = 0, sum = 0, minlen = INT_MAX;
for (int i = 0; i < n; i++) {
sum += nums[i];
while (sum >= s) {
minlen = min(minlen, i - start + 1);
sum -= nums[start++];
}
}
return minlen == INT_MAX ? 0 : minlen;
}
};
你可能会感到奇怪,有一个for循环和一个while 循环,它怎么可能是O(n)。关键是,while循环在每个启动位置开始时最多执行一次。然后开始增加1,while循环移动到下一个元素。因此,在整个for循环从0到n - 1的过程中,循环在大多数O(n)次中运行。因此,for循环和while循环都有O(n)时间复杂度,总体运行时间为O(n)。
你可以看一下整个遍历过程:
1)当 i = 0 时,sum = 2;
2)当 i = 1 时,sum = 5;
3)当 i = 2 时,sum = 6;
4)当 i = 3 时,sum = 8; 记录此时的 sum > 7 时的数组个数:minlen = 4;
此时,sum > s=7; 将start右移一位:
4)此时,sum = 6;将 i 右移一位:
此时,sum = 10 > s; 记录此时的 sum > 7 时的最小数组个数:minlen = 4;
将start右移一位:
此时,sum = 7,仍然大于等于 s = 7;记录此时的 sum > 7 时的最小数组个数:minlen = 3;
继续将start右移一位:
5)此时,sum = 6,将指针 i 向右移一位:
此时,sum = 9 ,大于s = 7; 记录此时的 sum > 7 时的最小数组个数:minlen = 3;
然后将start右移一位:
此时,sum = 7 ,大于等于s = 7; 记录此时的 sum > 7 时的最小数组个数:minlen = 2;
然后将指针start右移一位:
此时,sum = 5,然后i ++.
整个数组遍历完成!
<本文完>