抽象出 $x、f(x)、target$ 框架
此题思路同 1011. 在 D 天内送达包裹的能力 - 力扣(LeetCode) 以及 875. 爱吃香蕉的珂珂 - 力扣(LeetCode),同样根据 laluladong的总结 写出来的。一开始还给它排了序,提交时有些例子不过,才知道要求连续子数组,删除排序操作就可以了。
什么是自变量 $x$,对应函数 $f(x)$ 以及 $target$
题目要求m个子数组的各自和的最大值最小,其实也就说在满足分组数目的条件下,尽可能的让分组的最大和最小;所以完全可以把分组的最大和设置为变量x,所以在计算 $f(x)$ 时,将 $x$ 理解为每个分组和的限制条件即可。
对应函数 $f(x)$ 是什么
- $f(x)$ 就对应的是分组和限制条件 $x$ 下的分组数目,很显然,$x$ 越大分组数目越少,$x$ 越小,分组数目越多(注意:不同的 $x$ 可能对应相同的分组数)
二分怎么找?
对于返回值判断的问题取决于二分查找区间的设计,我喜欢左右都闭的情况,以下只针对该情况分析。
- target在哪:这道题,一定存在一个 $x$ 使得,$f(x)==target$,而且可能存在多个 $x$ 满足,所以要找的是左边界。
- 这一点与 1011. 在 D 天内送达包裹的能力 - 力扣(LeetCode) 以及 875. 爱吃香蕉的珂珂 - 力扣(LeetCode) 这两道题是不同的,吃香蕉和运货物中不一定存在 $x$ 满足 $f(x)==target$,因此对于返回条件 $left==right$,需要根据 $left$ 对应的函数值来做出判断。
- 虽然,该题与运送货物极为相似,但还是略有不同;该题一定可以将
nums
分成 $K$ 组,只不过不同的分法对应不同的限制条件,但是运送货物不一定能够保证在 $days$ 天完成,可能就是在 $days+1$ 或者 $days-1$ 天完成,所以为了满足条件,只能取 $days-1$ 对应的运输能力。 - 自变量的左右边界:最少分成1组,对应的是数组和;最多分成
nums.size()
组,对应的是其中的最大值。
具体实现
class Solution {
public:
int splitArray(vector<int>& nums, int k) {
// 先排序
// sort(nums.begin(),nums.end());
vector<int> ans=minMax(nums);
int left=ans[0],right=ans[1];
while(left<right)
{
// f(x)递减,因为要求在特定的组数目,groupSum最小,找左边界,而且一定存在left或者mid==groupNum
int mid=left+(right-left)/2;
if(f(nums,mid)==k)
{
right=mid;
}
else if(f(nums,mid)>k)
left=mid+1;
else if(f(nums,mid)<k)
right=mid-1;
}
return left;
}
// 构建f(x) 函数,自变量设置每一组的和,返回值是组数目
int f(vector<int>&nums,int groupSum)
{
// 返回值分组数组
int temp=0,group=0;
for(int i=0;i<nums.size();i++)
{
temp+=nums[i];
if(temp==groupSum)
{
temp=0;
group++;
}
else if(temp>groupSum)
{
temp=0;
group++;
i--;
}
}
// 如果索引完,结果temp还有值,则还需要一组
if(temp>0) // if(temp<groupSum)
group++;
return group;
}
// 求左右边界,理论最小边界就是最大的元素值,这样分组数目最多
// 理论最大边界就是所有元素之和,这样就只需要一组即可
vector<int> minMax(vector<int>&nums)
{
int sum=0,maxNum=0;
for(int i=0;i<nums.size();i++)
{
sum+=nums[i];
maxNum=maxNum>nums[i] ? maxNum : nums[i];
}
return {maxNum,sum};
}
};
$\cdots$ end $\cdots$