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
}