N个不同重量的人同时过河,需要多少条船
提示:这是一个排序+双指针的问题
非常有趣的一道题,需要你把边界抠清楚,联系coding的能力
题目
理解题目
给你一个arr,arri全部大于0,代表体重,N个人,一条船的载重为limit
规则:
一条船最多俩人,而且你不能超过这个船最大载重
请问,同时让N个人过河,你至少需要多少条船?才能载所有人过河
一、审题
举个例子:
船最大载重为10,你有俩人,他们能过河吗?
1)俩人重量为5,11,有一个人重11>10,他们不能过河,return -1;
2)俩人重量为5,5,显然总重量为10<=10,他们能过河,用几条船呢?1条足够,return 1;
3)俩人重量为5,6,总的重量11>10,他们不能放同一条船,因此需要2条独立装,才能过河,return 2;
好,现在有N个人,你怎么分配呢?最终需要几条船才能一起过河?
二、解题
显然,你得找一个经济的做法,那就是贪心策略,也就是,尽量让俩人的重量加起来接近limit
解决大流程:
先将arr排序升序,**o(N*log(N))**复杂度
如果N-1那个人,超过了limit,大家都过不去,返回-1;
任何一个超过limit/2的人只能做一条船,加起来<=limit的给一条船,2人公用一条船经济;
所以本题,从limit/2处开始往两边分开找合适的人来搭配,看图:
不妨设我们arr是上图那些,已然排序好,然后leftAll代表0–L这么多个总的小于等于limit/2的总数
1)在搭配过程中,左右能加起来<=limit的搭配的放leftused条船,因为俩合规的就只需要一条;
2)那配完左边的自然就还有leftAll-use=leftUnused这么人还没有没上传,既然是小的数,再咋招也得2人一条,故还需要用 (leftAll-use+1)/2=(leftUnused+1)/2条船,之所以加1是因为你1个人也要1条船,1+1之后除2才能等于1。
3)而自然右边的总人数是N-leftAll个,而右边已经有used个被配好了,剩下没用配的人数为rightNoused = N-leftAll- used,他们怎么运?显然1人一条,因为超过了limit/2的重量,你不能放俩,只能放一个一条,于是就需要rightNoused这么多条船
最后的需要的船的总数是:leftused+ (leftAll-use+1)/2 + rightNoused
代码:
public static int minBoat(int[] arr, int limit){
if (arr == null || arr.length == 0) return 0;
int N = arr.length;
for (int i = 0; i < N; i++) {
if (arr[i] > limit) return -1;//无效
}
Arrays.sort(arr);//升序
int mid = - 1;//取小于等于limit/2的最右边那个数
for (int i = N -1; i >= 0; i--) {//从右往左o(n)索引就行
if (arr[i] <= (limit >> 1)){
mid = i;
break;//找到就break
}
}
if (mid == -1) return N;//说明一个都没有找到,全部都大于limit/2,所以每个人都需要一条船
//下面就准备开始匹配,排序,双指针法
int L = mid;
int leftNoUsed = 0;//左边没有用过的,匹配好左边右边1条船这种
int R = mid + 1;
while (L >= 0){
//保证L不越界--一上来就开始判断有没有能匹配的左右一条船,记录
int used = 0;//目前匹配好的****************这个参数很重要,配合下面的if else来用
while (R < N && arr[L] + arr[R] <= limit){
//能配上,R++,直到不能配了
R++;
//这里L为啥不需要--,因为更小的和这个R一定能组合,所以L跳动只需要一会统一执行
used++;//本质就是计数count,记录我们匹配好几条船了
}
//一旦能不能配了,需要看看刚刚是不是一个都没有匹配过
if (used == 0) {//如果压根没有配右边,整个都不合规,那就是右边耗尽了,只能让L--
L--;
leftNoUsed++;//左边没有匹配的加加--每次used是配合用的,没法匹配则左侧没有配好的数++
}else L = Math.max(-1, L - used);//如果count==used配过了,得让L跳过去,笔记上有的,还不能越界
}
//这样的话,左边没有匹配好的出来了,leftNoused
int leftAll = mid + 1;
int leftUsed = leftAll - leftNoUsed;//左边已经匹配好的
int rightAll = N - mid - 1;//右边总的个数
int rightNoUsed = rightAll - leftUsed;//左边匹配好的就是右边匹配好的
return leftUsed + ((leftNoUsed + 1) >> 1) + rightNoUsed;//公式
}
测试
public static void test(){
int[] arr = {1,4,4,4,4,5,5,6,6,7,7,7,8,8,9};
//3+2+5=10条
System.out.println(minBoat(arr, 10));
}
public static void main(String[] args) {
test();
}
总结
提示:重要知识点:
最后的需要的船的总数是:leftused+ (leftAll-use+1)/2 + rightNoused
1)左边已经配好的就是右边已经配好的,共用了leftused条船
2) (leftAll-use+1)/2左边没有配好的,2人一条,不管怎么组合都是
3)右边的没有配好的rightNoused个,只能一人一条船,没得选