N个不同重量的人同时过河,需要多少条船

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处开始往两边分开找合适的人来搭配,看图:
图1
不妨设我们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个,只能一人一条船,没得选

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值