最近刷到挺多巧用二分的题目,经常在hard题中出现。
先把我最近刷到的先记录一下,后期这个博客会不断地更新~
这类型的题下面会总结什么情况可以考虑二分法。
另外二分法看起来简单,细节是魔鬼,用的时候考虑清楚是需要找出target就好,还是要找左边界或者右边界。
总结
这种题目一般拿到手不大容易往二分上去想。在遇到跟一个未知数k相关的题目,而且关于k的函数能得到一个单调不减/增的序列,我们可以往二分上多考虑一下。
题解
1539. 第 k 个缺失的正整数
这题直接遍历寻找肯定是可以的,但是这样的时间复杂度为
O
(
k
+
n
)
O(k+n)
O(k+n),最坏情况n个数组没有缺失,还需要再遍历k个。
能不能用二分呢?能不能写出关于k(缺失数)的函数?
这里考虑一下与arr平行的数组lost,对
i
i
i位置来讲lost[i] = arr[i]-i-1
lost是一个有重复数的单调非减序列
二分的任务就是要找到lost[i]=k的左边界(但是lost 中不一定包含k),或者说找到第一个大于等于k的左边界。
现在有了
lost[left-1]=n, lost[left]=m
n<k, m>=k
而我们要求的数应该是从arr[left-1]再数k-n个
即arr[left-1]+k-lost[left-1]
= arr[left-1] + k - (arr[left-1] - (left-1) - 1)
= k+left
class Solution {
public:
int findKthPositive(vector<int>& arr, int k) {
if(arr[0] > k) return k;
int left = 0, right = arr.size()-1;
while(left <= right){
int mid = left + (right - left) / 2;
int lost = arr[mid] - mid - 1;
if(lost == k)
right = mid - 1;
else if(lost < k)
left = mid + 1;
else
right = mid - 1;
}
return k + left;
}
};
793. 阶乘函数后 K 个零
先简单分析一下本题,
x
x
x阶乘后面多少0,其实只跟
[
1
,
x
]
[1,x]
[1,x]中所有数拥有的5的因子的个数有关,因为只有
2
∗
5
2*5
2∗5会构成10,而2的因子的个数肯定是超过5的,所以这里只需要考虑5。
这里就有了
f
(
x
)
f(x)
f(x)的计算方法
f(x) = (x/5) + f(x/5)
f(0) = 0
f(1) = 0
f(2) = 0
f(3) = 0
f(4) = 0
f(5) = 1
f(6) = 1
...
f(10) = 2
...
f(24) = 4
f(25) = 6
f
(
x
)
f(x)
f(x)的规律,每隔5会跳一下,但是具体跳多少需要计算一下。从
f
(
24
)
=
4
f(24)=4
f(24)=4跳到
f
(
5
)
=
6
f(5)=6
f(5)=6,从
f
(
74
)
=
16
f(74)=16
f(74)=16跳到
f
(
75
)
=
18
f(75)=18
f(75)=18,题目中的意思是计算
f
(
x
)
=
K
f(x)=K
f(x)=K的个数,可以发现只有0或者5两种情况。而
f
(
x
)
f(x)
f(x)是一个单调不减序列,那我们的问题就变成了能否在这个单调不减序列中找到
K
K
K。
二分法!
class Solution {
public:
int preimageSizeFZF(int K) {
long long left = K, right = 5LL*K;
while(left <= right){
long long mid = left + (right - left)/2;
int cnt = factorZero(mid);
if(cnt == K)
return 5;
else if(cnt < K)
left = mid + 1;
else
right = mid - 1;
}
return 0;
}
int factorZero(long long x){
int cnt = 0;
while(x){
x /= 5;
cnt += x;
}
return cnt;
}
};
287. 寻找重复数
这题的常规思路,一是交换排序,时间复杂度为
O
(
n
)
O(n)
O(n),但是会改变原数组(pass),其次哈希,时间复杂度
O
(
n
)
O(n)
O(n),但是空间复杂度
O
(
n
)
O(n)
O(n)(pass),另外还有暴力,空间复杂度
O
(
1
)
O(1)
O(1),但是时间复杂度
O
(
n
2
)
O(n^2)
O(n2)(pass)。
一开始不容易想到二分,貌似找不到那个单调数组
重新读一下题,n+1长度的数组,所有元素均为1-n的数,只有1个元素重复。
那我们可以考虑这个单调数组设为,对位置i而言,小于等于i的个数cnt。假设我们要找的目标值为target,那么当i<target的时候有cnt[i] <= i,而当i>=target,就有cnt[i] > i;单调数组是不是就出现了!!
这个时候考虑二分法找到第一个cnt[i] >i的位置即可
(1)先说明一下为什么这是单调不减的序列
对于位置i+1,数组中只有两种情况,
一种是有i+1,一种没有i+1,
也就是说cnt[i+1]至少是大于等于cnt[i]的
(2)其次为什么target位置有cnt[target] > target
总共有n+1个数字,且只有一个重复的
cnt[n]处肯定是n+1
而从target+1-n,加上的数字只能比n-(target+1)+1要小
cnt[target]处肯定是大于target,且是第一个满足这个等式
若是target之前有位置pos满足cnt[pos]>pos
那只能说明target之前就有重复元素,矛盾
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size()-1;
int left = 1, right = n;
while(left < right){
int mid = left + (right - left) / 2;
int cnt = 0;
for(int i = 0; i <= n; ++ i)
if(nums[i] <= mid)
++ cnt;
if(cnt <= mid)
left = mid+1;
else
right = mid;
}
return left;
}
};
1011. 在 D 天内送达包裹的能力
假定一个数组CanFinsh[x],表示在运载能力为x的情况下,能否在D天内将包裹送达完成。
不难想到CanFinsh数组应该是前面一串为False,后面一串为True,而我们要求的其实就是第一个为True对应的下标~显然是二分法
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int left = 0, right = 1;
for(int weight : weights){
left = max(left, weight);
right += weight;
}
while(left < right){
int mid = left + (right - left) / 2;
if(canfinish(weights, D, mid))
right = mid;
else
left = mid+1;
}
return left;
}
bool canfinish(vector<int>weights, int D, int carry){
int time = 1;
int pos = 0, cur = 0;
while(pos < weights.size()){
if(cur + weights[pos] > carry){
++ time;
cur = 0;
}
cur += weights[pos++];
}
return time <= D;
}
};