题目:
题目讲解:
给你输入一个数组 nums
和数字 m
,你要把 nums
分割成 m
个子数组。
肯定有不止一种分割方法,每种分割方法都会把 nums
分成 m
个子数组,这 m
个子数组中肯定有一个和最大的子数组对吧。
我们想要找一个分割方法,该方法分割出的最大子数组和是所有方法中最大子数组和最小的。
请你的算法返回这个分割方法对应的最大子数组和。
思路:
现在题目是固定了 m
的值,让我们确定一个最大子数组和;所谓反向思考就是说,我们可以反过来,限制一个最大子数组和 max
,来反推最大子数组和为 max
时,至少可以将 nums
分割成几个子数组。
比如说我们可以写这样一个 split
函数:
// 在每个子数组和不超过 max 的条件下, // 计算 nums 至少可以分割成几个子数组 int split(int[] nums, int max);
比如说 nums = [7,2,5,10]
,若限制 max = 10
,则 split
函数返回 3,即 nums
数组最少能分割成三个子数组,分别是 [7,2],[5],[10]
。
那如果我们找到一个最小 max
值,满足 split(nums, max)
和 m
相等,那么这个 max
值不就是符合题意的「最小的最大子数组和」吗?
现在就简单了,我们只要对 max
进行穷举就行,那么最大子数组和 max
的取值范围是什么呢?
显然,子数组至少包含一个元素,至多包含整个数组,所以「最大」子数组和的取值范围就是闭区间 [max(nums), sum(nums)]
,也就是最大元素值到整个数组和之间。
class Solution { public: int splitArray(vector<int>& nums, int m) { //取值范围[max(nums), sum(nums)] int lo=0,hi=0; for(int i=0;i<nums.size();++i){ lo=max(lo,nums[i]); hi+=nums[i]; } while(lo<=hi){ int mid=lo+(hi-lo)/2; int count=split(nums,mid); if(count<m){ //需要增大count,则需减小max,缩小右边界 hi=mid-1; } else if(count>m){ //减小count,需要增大max,缩小左边界 lo=mid+1; } else{ //求解左侧边界问题,所以相等时缩小右边界 hi=mid-1; } } return lo; } // 在每个子数组和不超过 max 的条件下, // 计算 nums 至少可以分割成几个子数组 int split(vector<int>& nums, int max){ //最小为1堆 int count=1; int sum=0; for(int i=0;i<nums.size();++i){ //当总和大于max,新开一堆 if(sum+nums[i]>max){ count++; sum=nums[i]; }else{ sum+=nums[i]; } } return count; } };