Leetcode 410. 分割数组的最大值(困难) 二分查找高阶应用

labuladong讲解

410. 分割数组的最大值(困难)

题目:

 

 题目讲解:

给你输入一个数组 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;
    }
};

 

posted @ 2022-02-28 11:31  鸭子船长  阅读(53)  评论(0编辑  收藏  举报