LeetCode 410. Split Array Largest Sum

Question

Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.

Note

If n is the length of array, assume the following constraints are satisfied:

1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)

Examples
Input:
nums = [7,2,5,10,8]
m = 2

Output:
18

Explanation:
There are four ways to split nums into two subarrays.
The best way is to split it into [7,2,5] and [10,8],
where the largest sum among the two subarrays is only 18.

Analysis

    这个题目咋一看会无从下手,其实有两个解决的办法,都非常巧妙。
    一种是二分法。一般来说,根据二分的对象不同,二分法有两种思路,一是对定义域进行二分,比如在有序数列中找指定的数;另一种思路是对值域进行二分,比如这个题目。
    首先确定二分的上下界,当m=n时,最终的结果就是数组中的最大值,因此这个最大值就是二分的下界。相比于随便取数组中的一个值,以这个最大值为下界还有一个好处,即保证每个子数组至少包含一个数。二分法的上界在m=1时取到,为整个数组的和。
    然后确定比较准则。定义另一个函数```check(...)```,判断将数组nums分为m份且每份的和不超过maxs是否可行。我们只需要遍历数组并做累加,当累加的和超过maxs时就归零,同时m--,如果遍历结束后m==1,说明刚好可以分为m份使得每份的和不超过maxs(最后一个子数组没有m--就结束循环了)。此时maxs可以作为新的上界。否则,maxs作为新的下界。具体方法见代码。
    另一个方法是动态规划。动态规划的关键是建立递推关系。而这个递推关系也是这个题目比较精髓的地方。一般来说,递推关系可以直接设为要求的值,比如:

  1. 根据nums划分,D[i,j]表示[num[i], nums[j]]之间的数分为m份时的解,或D[i]表示数组前i个数划分为m份时的解;
  2. 根据m划分,D[i]表示对整个数组划分为i份时解;

    但是仔细考察上面两种方法,均无法建立简单高效的递推关系,因此不可行。这个题目的正确解法是

  • 设D[i,j]表示前i个数划分为j份时的解;
  • 最终要求的是D[nums.size() - 1,m];
  • 递推关系为D[i,j] = min(D[i,j], max(D[k, j-1], sum(nums[p] | k < p <= i)));

    为了快速计算累积和,还需要额外一个数组保存预计算的累积和。具体实现细节见代码。

Solution 二分法

class Solution {
public:
    int check(vector<int>& nums, int m, long int maxs){
        long int count = 0;
        for(int i = 0; i < nums.size(); ++i){
            count += nums[i];
            if(count <= maxs)
                continue;
            count = nums[i];
            m--;
        }
        if(m >= 1) // maxs is equal or larger than ground truth;
            return -1;
        else // maxs is smaller than ground truth;
            return 1;
    }
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        long int left = nums[0];
        long int right = 0;
        for(int i = 0; i < n; ++i){
            right += nums[i];
            left = max(nums[i], (int)left);
        }
        while(left < right){
            printf("left: %d, right: %d\n",left,right);
            long int mid = (left + right) / 2;
            switch(check(nums, m, mid)){
                case 1:
                    left = mid + 1;
                    break;
                case -1:
                    right = mid;
                    break;
            }
        }
        return right;
    }
};

Solution 动态规划

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        vector<int> sum(n, nums[0]);
        vector<vector<int>>pool(n, vector<int>(m + 1, INT_MAX));
        for(int i = 1; i < n; ++i){
            sum[i] = sum[i - 1] + nums[i];
            pool[i][1] = sum[i];
        }
        pool[0][1] = nums[0];
        for(int i = 1; i < n; ++i){
            for(int j = 2; j <= min(m, i + 1); ++j){
                for(int k = j - 2; k < i; ++k){
                    pool[i][j] = min(pool[i][j], max(pool[k][j - 1], sum[i] - sum[k]));
                }
            }
        }
        for(int i = 0; i < n; ++i){
            for(int j = 0; j <= m; ++j){
                printf("%d ", pool[i][j]);
            }
            printf("\n");
        }
        return pool[n - 1][m];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值