二分法解决最值的最值化

1.两球之间的磁力,求的是m个球中相邻两球距离的最小值的最大化

func maxDistance(position []int, m int) int {
    /*
    二分法,图片可以参考:https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/c-er-fen-sou-suo-by-acvv_itdef-og5j/
    0.最小磁力指的是这 m 个球中相邻两球距离的最小值的结论。对于i<j<k 三个位置的球,最小磁力一定是j−i 和k−j 的较小值,而不是跨越了位置 j的 i 和 k 的差值 k−i。
    1.比如说我们假设最小距离是3 那么两球间的距离至少是3不会低于3,否则3就不可能是最小距离。
    如果在最小距离是3的情况可以放置,那么我们就尝试最小距离为4的放置方案,
    然后尝试最小间隔为5
    在可放置完成与不可放置完成之间的距离就是答案。
    这是一个区间划分的问题 可以使用二分解决
    2.问题的关机是如何判断当前最小距离是否合法。即m个球最小距离是minDistance下 长度为n的position数组能否放下m个球。
    从贪心的角度考虑,第一个小球放置的篮子一定是position 最小的篮子,即排序后的第一个篮子。那么为了满足上述条件,第二个小球放置的位置一定要大于等于position[0]+minDistance,接下来同理。因此我们从前往后扫position 数组,看在当前答案 minDistance 下我们最多能在篮子里放多少个小球,我们记这个数量为cnt,如果cnt 大于等于 m,那么说明当前答案下我们的贪心策略能放下 m 个小球且它们间距均大于等于 minDistance ,为合法的答案,否则不合法
    */
    sort.Ints(position)
    var left=1//两个球之间距离最小是1
    var right=position[len(position)-1]-position[0]//两个球之间距离最大
    var ans =-1
    for left<=right{
        var mid=left+(right-left)/2
        if check(position,mid,m){
            //如果该距离合理,则尝试更大的距离
            ans=mid
            left=mid+1
        }else{
            //如果该距离不合理,则尝试更小的距离
            right=mid-1//此处如果是left<right,则right=mid
        }
    }
    return ans
}
//m个球最小距离是minDistance下 长度为n的position数组能否放下m个球
func check(position[]int, minDistance,m int)bool{
    //第一个球肯定在起点
    var pos=position[0]
    var cnt=1
    for i:=1;i<len(position);i++{
        if position[i]-pos>=minDistance{
            //可以放下一个球
            pos=position[i]
            cnt++
        }
    }
    if m<=cnt{
        return true
    }
    return false
}

2.小张刷题计划,求的是是m天中做题时间最多的一天对应的耗时T的最小化

func minTime(time []int, m int) int {
    /*
    二分法,求最值的最值,类似https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    0.耗时最多指的是m天中做题时间做多的一天对应的耗时
    1.设T=10合法即m天中做题时间最长的一次设10,那么就应该尝试T=9的,直至不能在m天内完成所有的题目。在可完成与不可完成之间的时间就是最佳答案
    2.问题关键是如何确定当前最大值是否合法,即对time数组在最大值是maxTime下可以分成m天内完成所有题目。
    3.类似题目
    https://leetcode-cn.com/problems/minimum-limit-of-balls-in-a-bag/
    https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    https://leetcode-cn.com/problems/split-array-largest-sum/solution/fen-ge-shu-zu-de-zui-da-zhi-zui-xiao-hua-hvv7/
    */
    // 如果天数大于题目 可以0做完 
    if len(time)<=m{
        return 0
    }
    var left,right int
    left=time[0]
    //范围是最小值,所有时间和
    for i:=range time{
        if left>time[i]{
            left=time[i]
        }
        right+=time[i]
    }
    var ans=-1
    for left<=right{
        var mid=left+(right-left)/2
        if canSplit(time,mid,m){
            //如果mid天可以完成所有题目
            ans=mid
            right=mid-1
        }else{
            //如果mid天可以完成所有题目
            left=mid+1
        }
    }
    return ans
}

func canSplit(time []int,maxTime,m int)bool{
    var sum int// 用来记录当前回答问题耗费的时间
    var maxT int// 记录今天的所回答问题的最大值
    var flag=-1// 用来判断是否已经使用过一次求助
    for i:=0;i<len(time);{
        sum+=time[i]
        // 获取今天的最大值
        if time[i]>maxT{
            maxT=time[i]
        }
        // 如果今天耗费的时间大于给定的时间maxTime
        if sum>maxTime{
            // 是否能够使用一次求助 如果可以 就使用一次求助解决耗时最长的一个问题 并把最大值减掉
            // 这样能够回答问题最大化  贪心
            if flag==-1{
                sum-=maxT
                flag=-flag//求助使用完
            }else{
                // 如果已经使用过了
                m--
                //等下一天
                if m<=0{
                    // 如果m使用完 但是还没有遍历完成,则m天做不完所有题目
                    return false
                }
                sum=0
                maxT=0
                flag=-flag
                continue//等下一天处理当前的
            }
        }
        i++
    }
    return true
}

3. 分割数组的最大值,求的是分割数组的m个子数组和的最大值的最小化

func splitArray(nums []int, m int) int {
    /*
    二分法,求最值的最值,类似https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    0.最大值指的是m个子数组和中的最大值
    1.设m个子数组和中的最大值为10,如果在子数组和中最大值是10的情况下可以产生m个子数组,那么就尝试最大值为9的,直至
    不能产生m个非空子数组。在可产生与不可产生之间的距离就是答案。
    2。问题关键是如何确定当前最大值是否合法,即对num数组在最大值是maxSum下可以分成m和非空连续子数组。
        遍历数组,使用 sum 累加
        如果 sum 超过规定的数值, sum 清零然后重新从当前数字累加,子数组的个数 +1
        如果子数组先用完了,表示失败
        反之跑完了所有数组中的数字,表示成功
    3.类似题目:
    https://leetcode-cn.com/problems/minimum-limit-of-balls-in-a-bag/
    https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua/
    */
    var left,right int
    //子数组和最小值是数组最大值,最大值是所有数和
    for i:=range nums{
        right+=nums[i]
        if left<=nums[i]{
            left=nums[i]
        }
    }
    var ans=-1
    for left<=right{
        var mid=left+(right-left)/2
        if canSplit(nums,mid,m){
            //该数组和满足m切分
            ans=mid
            right=mid-1
        }else{
            //该数组和不满足切分
            left=mid+1
        }
    }
    return ans
}

func canSplit(nums[]int,maxSum,m int)bool{
    var sum int
    var cnt int
    for i:=range nums{
        sum+=nums[i]
        if sum<=maxSum{
            continue
        }
        sum=nums[i]
        cnt++
        if cnt>=m {
            //子数组先用完了,即不能将数组中所有数字都分割进子数组,即还没分割完,子数组数量就用完了。则表示这个最大值不够大
            return false
        }
    }
    return true
}

4.袋子里最小数目的球,多次操作中新袋子里球数最多的数目的最小化

func minimumSize(nums []int, maxOperations int) int {
    /*
    二分法,求最值的最值,类似https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    0.maxOperations次操作中单个袋子里的球数目的最大值的最小化
    1.对某个开销值,看它能不能在maxOperations内操作得到。理论上只要Operation足够大,完全可以达到最小开销1。最大开销值越大,需要的Operations越少。最终要得到结果在1~1000_000_000内,连续且有序,所以可以用二分法去找这个符合条件(operations <= maxOperations)的最小的开销
    2.问题的关键是如果判断一个开销值合法,即是不是在小于等于maxOperations内可以操作得到这样的结果
    因为每次都是重新分给两个新袋子。则对于nums里的每一个数,如果开销为maxCnt,那么就用这个数去除maxCnt,不能整除的话,就是除之后得到的结果如num=10,开销为3,10/3=3,意思是3次可以分成(3,3,3,1)如果是能整除如num=9,开销为3,9/3=3,其实2次就能分完(3,3,3)
    3.类似题目:
    https://leetcode-cn.com/problems/split-array-largest-sum/
    https://leetcode-cn.com/problems/magnetic-force-between-two-balls/solution/liang-qiu-zhi-jian-ci-li-zui-xiao-zhi-zu-g3h3/
    https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua/
    */
    var left,right int
    left=1//最小是操作后球数目是1
    right=nums[0]//最大是球数目的最大值
    for i:=range nums{
        if right<nums[i]{
            right=nums[i]
        }
    }
    var ans =-1
    for left<=right{
        var mid=left+(right-left)/2
        if canPut(nums,mid,maxOperations){
            //如果单个袋子最大球数目mid个情况下可以在最多maxOperations次完成
            ans=mid
            right=mid-1
        }else{
            left=mid+1
        }
    }
    return ans
}

func canPut(nums[]int,maxCnt,maxOperations int)bool{
    var cnt int//操作次数
    for i:=range nums{
        cnt+=(nums[i]/maxCnt)
        if nums[i]/maxCnt==0{
            cnt--
        }
        if cnt>maxOperations{
            return false
        }
    }
    return true
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值