二分法补充——力扣209. 长度最小的数组
题目描述
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 1091 <= nums.length <= 1051 <= nums[i] <= 104
思路
题目显然使用暴力的方法会超时,比较容易联想到使用前缀合+二分查找答案,即通过前缀合预先处理数组来降低后续查找答案的时间复杂度,然后通过二分查找逼近最小的满足题目要求的答案,复杂度为,二分查找的初始化上限为,下限为,代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
vector<int> sums(n + 1);
for(int i = 1; i <= n; i ++){
sums[i] = sums[i - 1] + nums[i - 1];
}
int l = 1, r = n;
while(l < r){
int mid = (l + r + 1) >> 1;
bool flag = false;
for(int i = 1; i <= n - mid + 1; i ++){
int tmp = sums[i + mid - 1] - sums[i - 1];
if(tmp >= target){
flag = true;
break;
}
}
if(flag){
l = mid;
}else{
r = mid - 1;
}
}
if(l == n && sums[n] < target){
return 0;
}
return l;
}
};
补充
二分查找有两种最核心的变体:寻找左边界(最小值) 和 寻找右边界(最大值)
写法一:寻找“满足条件的最小值” (左边界)
while(l < r){
int mid = (l + r) >> 1; // 向下取整
if(check(mid)){ // check() 函数判断 mid 是否满足条件
r = mid; // 满足条件:mid 可能是答案,保留它,尝试更小的
}else{
l = mid + 1; // 不满足:mid 肯定不是答案,丢弃它,往右找
}
}
核心特征与场景
- 目标:寻找满足
check(mid)为true的 最小值。 - 策略:
- 如果
mid满足条件,它是潜在的“最小值”,所以不能丢弃,必须r = mid。 - 如果
mid不满足,说明太小了,必须l = mid + 1。
- 如果
- mid 取法:因为出现了
r = mid,必须配合 向下取整(l + r) >> 1。
写法二:寻找“满足条件的最大值” (右边界)
while(l < r){
int mid = (l + r + 1) >> 1; // 向上取整!
if(check(mid)){ // check() 函数判断 mid 是否满足条件
l = mid; // 满足条件:mid 可能是答案,保留它,尝试更大的
}else{
r = mid - 1; // 不满足:mid 肯定不是答案,丢弃它,往左找
}
}
核心特征与场景
- 目标:寻找满足
check(mid)为true的 最大值。 - 策略:
- 如果
mid满足条件,它是潜在的“最大值”,所以不能丢弃,必须l = mid。 - 如果
mid不满足,说明太大了(或者不符合要求),必须r = mid - 1。
- 如果
- mid 取法:因为出现了
l = mid,必须配合 向上取整(l + r + 1) >> 1。
因果关系与 mid 取整
为什么 mid 的写法会不同?这是由 死循环的边界条件 倒逼出来的:
二分查找的死循环总是发生在区间只剩 2 个数时(例如 l = 2, r = 3)。
- 针对写法一 (
r = mid):- 如果我们用向上取整
(2+3+1)/2 = 3,此时mid = r。 - 如果走进
r = mid分支,r还是 3,l还是 2。死循环! - 结论:只要你有
r = mid,mid必须向下取整(l + r) >> 1,保证mid偏向l,这样r才会变小。
- 如果我们用向上取整
- 针对写法二 (
l = mid):- 如果我们用向下取整
(2+3)/2 = 2,此时mid = l。 - 如果走进
l = mid分支,l还是 2,r还是 3。死循环! - 结论:只要你有
l = mid,mid必须向上取整(l + r + 1) >> 1,保证mid偏向r,这样l才会变大。
- 如果我们用向下取整