LeetCode209. 長度最小的子陣列
阿新 • • 發佈:2020-06-28
方法一
暴力列舉所有可能的子陣列,也就是列舉子陣列的所有開始下標和結束下標,計運算元陣列的和,如果子陣列的和小於等於s,就更新最小長度。
class Solution { public: int minSubArrayLen(int s, vector<int>& nums) { if(nums.size() == 0) { //陣列為空的情況需要特判 return 0; } int res = INT_MAX; //最終的返回結果,初始化為一個較大的數 for(int i = 0; i < nums.size(); ++i) { //列舉子陣列的起始下標 int sum = 0; //子陣列的和 for(int j = i; j < nums.size(); ++j) { //列舉子陣列的結束下標 sum += nums[j]; if(sum >= s) { res = min(res, j -i + 1); //如果子陣列的和小於等於s,就更新res break; //不要忘了加 break !!! } } } return res == INT_MAX ? 0 : res; } };
方法二
同樣是列舉子陣列的起始下標,我們考慮優化子陣列結束下標的搜尋空間,如果能用二分找到(滿足子陣列的和大於等於s)子陣列結束下標的位置,相比於方法一就會更快,
二分查詢要求陣列是單調的,由於題目保證每一個元素都是正的,所以我們考慮用一個sums陣列記錄字首和,也就是說,sums[i]表示nums[0] ~ nums[i - 1]的元素總和,
這樣,問題就轉化為對於每一個起始下標i,通過二分查詢尋找一個大於等於i的最小下標bound,使得sums[bound] - sums[i - 1] >= s,sums[bound] - sums[i - 1]就是子陣列的和。找到了bound之後,我們就更新子陣列的最小長度(此時子陣列的長度是bound - (i - 1))。
class Solution { public: int minSubArrayLen(int s, vector<int>& nums) { if(nums.size() == 0) { return 0; } int size = nums.size(); vector<int> sums(size + 1, 0); //這裡sums的大小為size + 1,是因為我們要讓sums[0]為0,方便下一句的for迴圈對suns做初始化 for(int i = 1; i <= size; ++i) { sums[i] = sums[i - 1] + nums[i - 1]; } int res = INT_MAX; for(int i = 1; i <= size; ++i) { int target = s + sums[i - 1]; //target 為 s + sums[i - 1],也就是說如果某個數bound的字首和sums >= target,那麼以i為起始下標,到bound的子陣列的和就大於等於sum auto bound = lower_bound(sums.begin(), sums.end(), target); if(bound != sums.end()) { //如果bound不越界,就更新子陣列的最小長度 res = min(res, static_cast<int>((bound - sums.begin()) - (i - 1))); } } return res == INT_MAX ? 0 : res; } };
方法三
考慮用雙指標,兩個指標start和end分別表示子陣列的開始和結束下標,變數sum表示以start開頭,以end結尾的子陣列的和。start, end, sum的初始值都為0.
先將nums[end]加入sum中,如果sum < s,就繼續增加end(當然同時也把nums[end]加入sum)直到sum >= s,此時不增加end,而是更新子陣列的最小長度(此時長度為end - start + 1),然後不斷增加start(並更新子陣列的最小長度)直到sum < s,然後再繼續增加end,直到end超過了陣列最大下標。
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
if(nums.size() == 0) {
return 0;
}
int start = 0, end = 0, sum = 0, res = INT_MAX;
while(end < nums.size()) {
sum += nums[end];
while(sum >= s) {
res = min(res, end - start + 1);
sum -= nums[start];
++start;
}
++end;
}
return res == INT_MAX ? 0 : res;
}
};
複雜度分析
- 暴力方法中列舉起始下標需要O(n),內層迴圈列舉結束下標又需要O(n),因此時間複雜度是O(n);由於無需額外的輔助陣列,因此空間複雜度是O(1);
- 字首和 + 二分查詢同樣需要O(n)來列舉起始下標,但這裡對於子陣列的結束下標使用了二分查詢進行優化,查詢的複雜度是O(logn),因此總的時間複雜度是O(nlogn);由於需要一個額外陣列sums存放字首和,因此空間複雜度是O(n);
- 雙指標方法中,左右指標start和end都只會向右移動,並且對於每個起始下標,無需從頭開始列舉結束下標,因此時間複雜度是O(n);雙指標方法也無需額外的輔助陣列,空間複雜度為O(1).