以前对二分查找的认识不够,更多的是知道折半排序,这几个题就很好的总结了二分查找。
他们的共同特点都是在一个范围内查找答案,可以根据当前查找结果判断去左区间还是右区间,二分的效率还是很高的。1e9的数据也就三十几次就解决了。
LC1011:在 D 天内送达包裹的能力
求最低运载能力的最大运载量,包裹是按顺序放上的,我们发现最大运载量的最小值是包裹的最大值,最大值不超过包裹的总量,就在这之间寻找答案,我们选定每个区间的mid作为最大运载量,看在这个限制下需要多少天day运完,如果day超过D,那说明我们的最大运载量太低了,就选择右区间继续,如果小于等于D,说明我们的运载量可以,那我们看看能不能在笑一些,就选择左区间,直到left>right.
其中对于每一个最大运载量计算需要多少天day,因为是顺序的,超过了就放到下一个就可以了。
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int high = accumulate(weights.begin(), weights.end(), 0);
int low = 0;
for(int i : weights) low = max(low, i);
while(low < high)
{
int mid = (low + high) / 2;
//判断
int cnt = 0;
for(int i = 0; i < weights.size();)
{
int s = 0;
while(i < weights.size() && s + weights[i] <= mid)
{
s += weights[i];
i++;
}
cnt++;
}
if(cnt <= D)
high = mid;
else low = mid + 1;
}
return low;
}
};
LC875:爱吃香蕉的珂珂
一样的,这次求最小速度下的每次吃的最多数量
这次的边界是粗略的,不过没关系,差不了太多。也是一样的,选择一个最大吃的数量判断此情况下的最小速度,根据结果选择左区间还是右区间
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int low = 1;
int high = 1e9;
while(low < high)
{
int mid = (low + high) / 2;
int cnt = 0;
for(int i : piles)
cnt += i / mid + (i % mid == 0 ? 0 : 1);
if(cnt <= h) high = mid; //mid == 2 cnt < h but mid == 1 cnt > h
else low = mid + 1;
}
return low;
}
};
LC1723:完成所有工作的最短时间
这个是昨天的每日一题,都是差不多的,求分配方案中尽可能最小的最大工作时间,也是在一个范围内选择答案,但是这道题不同于上面两个的是他不是把东西顺序分配的,也就是说可以随便分配,所以加上dfs搜索+剪枝
剪枝
- 边界剪枝:可以将最小值放到工作的最大值,最大值是所有工作之和
- 优先分配工作量大的任务,一开始分配小任务量可能导致后面大的进不来,所以对工作量进行排序
- 如果当前第i个工人 + 第j个工作正好就等于我们设定的最大工作时间而且后面工作的dfs失败,那就不用再处理后面的情况了,比如说当前第i个工人的工作时间是8,又加入第j个工作,时间是5正好达到最大工作时间13,后面处理j之后的工作发现dfs失败,本来应该将这个5放到j之后再看看能不能成功,但其实不用。你放到后面,那就意味着第j个工人再分配肯定是小于等于5的(总共13),既然j承担8 + 5都不能使后面dfs成功,那么8 + 小于等于5的肯定也不能使后面dfs成功。
- 还是如果后面dfs失败而且当前第j个工人分配时间为0,那么你再把第j个工作调整到后面的工人也是没有用的,因为第j个工人和j后面之后的工人都是0,没有什么区别
class Solution {
public:
bool dfs(int ma, int j, vector<int>& jobs, vector<int>& workload)
{
if(j == jobs.size())
return true;
for(int i = 0; i < workload.size(); i++)
{
if(workload[i] + jobs[j] <= ma)
{
workload[i] += jobs[j];
if(dfs(ma, j + 1, jobs, workload))
return true;
workload[i] -= jobs[j];
//剪枝
if(workload[i] == 0 || workload[i] + jobs[j] == ma)
break;
}
}
//全都加不进去了而且还有工作没分配完
return false;
}
int minimumTimeRequired(vector<int>& jobs, int k) {
int right = accumulate(jobs.begin(), jobs.end(), 0);
int left = 0, ans = right;
sort(jobs.begin(), jobs.end(), greater<int>());
left = jobs[0];
while(left < right)
{
int mid = (left + right) / 2;
vector<int> workload(k, 0);
int tmp = 0;
if(dfs(mid, 0, jobs, workload))
{
for(int i : workload)
tmp = max(tmp, i);
ans = min(tmp, ans);
right = mid;
}
else
left = mid + 1;
}
return ans;
}
};
LC1482:制作 m 束花所需的最少天数
祝我最爱的妈妈母亲节快乐 !
一开始想到了二分,但是觉得头铁先写个暴力,果然超时了,还是继续写二分
class Solution {
public:
int minDays(vector<int>& bloomDay, int m, int k) {
int n = bloomDay.size(), left = 1, right = 1e9;
if(m * k > n) return -1;
while(left < right)
{
int mid = (left + right) / 2;
int c = m, cnt = 0;
for(int i = 0; i < n; i++)
{
if(bloomDay[i] <= mid)
{
cnt++;
if(cnt >= k)
{
c--;
cnt = 0;
}
}
else cnt = 0;
}
if(c > 0)
left = mid + 1;
else right = mid;
}
return left;
}
};