7-5 愤怒的牛
农夫约翰建造了一座有n间牛舍的小屋,牛舍排在一条直线上,第i间牛舍在x i的位置,但是约翰的m头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。John 决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
输入格式:
第一行用空格分隔的两个整数n和m,n,m<=10^5 ;
第二行为n个用空格隔开的整数,表示位置x i。x i <MAXINT
输出格式:
输出仅一个整数,表示最大的最小距离值。
输入样例:
5 3
1 2 8 4 9
输出样例:
3
思路:排序加二分查找
首先对小屋的位置进行排序,然后计算出第一个小屋和最后一个小屋之间的距离(house[n-1]-house[0] )
而题目要求的“最大的最小距离值”答案一定在 [ 1 , house[n-1]-house[0] ] 这个闭区间内, 我们对此区间进行枚举寻找符合条件的最大值(条件在下文“确定判断条件”中说明),但是一般的一个个的枚举一定会超时,而二分查找用于在一个单调或者局部单调有序数组中查找一个符合某些条件的最优值,时间复杂度为O(logN),正是解决这类问题的算法。
二分法最重要的两个点:
-
while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right 迭代过程中
-
middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle
本题中我设置的 left 和 right 的关系为 left <= right,符合条件时left=middle+1 , 否则 right=middle-1,这样设置跳出循环时left = right+1,导致这种情况只有俩个可能:
- 条件判断成功,此时left=right,left=middle+1
- 条件判断失败,此时left=right,right=middle-1
此时的right一定是最后一次判断条件成功的值:
- judge(middle):条件判断函数
while(left<=right){
int middle = (left+right)/2;
if(judge(middle)){
left=middle+1;
}
else{
right=middle-1;
}
}
确定判断条件
在一般的经典二分题目,一个有序区间寻找是否存在一个数时,条件只需要判断middle和寻找数的大小关系再进行迭代,如此题:704. 二分查找,本题中我们判断的是当前二分枚举的值(x)是否符合条件,即以x为最小的间隔来安排牛进牛舍,统计最后一共能有多少头牛能进入宿舍,结果大于等于牛的总数即符合条件:
- house:房子的位置数组
- x:二分枚举的距离的“最小的最大值”
- last:上一个牛入住的牛舍位置下标,基于贪心,下标0的第一个牛舍必然安排牛入住,last=0
- sum:当前入住牛数目,sum初始为1
bool judge(int x){
int last=0,sum=1;
for(int i=1;i<n;i++){
if(house[i]-house[last]>=x){
last=i;
sum++;
}
}
return sum>=m? true:false;
}
AC代码:
#include<bits/stdc++.h>
using namespace std;
vector<int> house;
int n,m;
bool judge(int x){
int last=0,sum=1;
for(int i=1;i<n;i++){
if(house[i]-house[last]>=x){
last=i;
sum++;
}
}
return sum>=m? true:false;
}
int main(){
cin>>n>>m;
house = vector<int>(n);
for(int i=0;i<n;i++){
cin>>house[i];
}
sort(house.begin(),house.end());
int left = 1 , right = house[n-1]-house[0];
while(left<=right){
int middle = (left+right)/2;
if(judge(middle)){
left=middle+1;
}
else{
right=middle-1;
}
}
cout<<right;
return 0;
}