文章目录
前言
- 左闭右开是编程中表示一段区间的最规范的方式
- 二分查找的正确打开方式为
cpp
int lo = 0, hi = n, mi;
while (lo < hi) {
mi = (lo + hi) / 2;
if (k < sum[mi])
hi = mi;
else
lo = mi + 1;
}
return lo - 1;
- O(nlogn)的算法不一定比O(n)的算法慢
题目
方法一:滑窗 O(n)
滑窗处理"最长子列"问题,就是左端在迫不得已的时候才收缩;
滑窗处理"最短子列"问题,就是左端积极收缩,只要有能使窗口变小的可能,就积极尝试。
本题就是后者:滑窗右端每向右挪一个元素,尝试收缩左端。
cpp
class Solution {
int sum = 0; // 滑窗内的数值总和
int min(int a, int b) { return a < b ? a : b; }
public:
int minSubArrayLen(int target, vector<int>& nums) {
int pl = 0, pr = 0, ans = 200000;
// [pl, pr)左闭右开区间
while (pr < nums.size()) {
sum += nums[pr];
pr++;
if (sum < target)
continue;
while (sum - nums[pl] >= target) {
sum -= nums[pl];
pl++;
}
ans = min(ans, pr - pl);
}
return ans == 200000 ? 0 : ans;
}
};
方法二:二分答案 O(nlogn)
预处理好前缀和之后,对于每一个右边界,二分找到对应的左边界。
cpp
class Solution {
int sum[100010]; // 前缀和
int min(int a, int b) { return a < b ? a : b; }
int find(int n, int k) // n个元素,返回值小于等于k的最后一个元素的下标
{
int lo = 0, hi = n, mi; // lo hi左闭右开
while (lo < hi) {
mi = (lo + hi) / 2;
if (k < sum[mi])
hi = mi;
else
lo = mi + 1;
}
return lo - 1;
}
public:
int minSubArrayLen(int target, vector<int>& nums) {
int pl = 0, pr = 0, ans = 200000;
sum[0] = 0;
for (int i = 0; i < nums.size(); i++)
sum[i + 1] = sum[i] + nums[i];
for (int i = 1; i < nums.size() + 1; i++) {
int pos = find(nums.size() + 1, sum[i] - target);
if (pos < 0)
continue;
ans = min(ans, i - pos);
}
return ans == 200000 ? 0 : ans;
}
};