每日一題,防止癡呆 = =
一、題目大意
給定一個含有 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;
}
};
如果有問題,歡迎大家指正!!!