LeetCode 410. Split Array Largest Sum

410. Split Array Largest Sum

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)

题目内容:
给出一个数组nums,和一个整数m,将数组nums切割成m份,得到m个子数组,使得这m个子数组中元素之和的最大值最小。
例如给定数组[1, 2, 3, 4, 5], m = 2
那么当将数组分成[1, 2, 3]和[4, 5]的时候,两个子数组的元素之和最大值为4+5=9,这已经是所有分割可能性中元素值之和最大值最小的可能,也就是说,对于其他的分割方式,得到的每个子数组中,元素值之和最大的不会还有比9小的。

解题思路:
将数组进行分割,可以通过往数组里面插入挡板来完成,例如要将数组分成m份,那么就可以将m-1个挡板放入数组当中,就可以将数组分成m份,那么究竟哪种分割方法才是符合题目要求的,通过枚举我们可以知道。但是题目给出数组最大长度可以达到1000,最多分成50份,那么也就是放入49个挡板,而1000个元素当中有999个位置可以放挡板,那么枚举的数量就有999 * 998 * …… * 950,显然这是非常低效的。
但是我们可以利用二分搜索的方法来找到这个最小的最大值。假如数组的最大值为max,所有元素之和为sum,这两个值可以通过对数组的一次遍历得到。那么我们可以知道将数组分成m份之后,子数组的元素值之和都会在[max, sum]之中。因为

  • 假如一种极端情况是每个元素被分割成一个子数组,那么max所在的子数组只有max一个元素,所有的子数组中,元素之和最大的就是max所在的子数组了;
  • 而另一种极端情况是不作任何分割,那么只有一个子数组,这个子数组与原数组相同,所以元素之和为sum。

那么根据二分搜索的思想,确定一个范围[left, right]之后,每次我们都检查这个范围中间的那个值middle,判断是否可以将数组分成m份,且每一份的元素值之和都不超过middle。left的初始值就是max,而right的初始值就是sum。至于如何检查,后面我会再详细说明。

  • 如果检查的结果说明是可以将数组分成m份,且每一份的元素值之和都不超过middle,那么说明此时的middle可能太大或者刚刚好,此时可以把当前分割出来的子数组元素之和的最大值currentMax当作新范围的右边界,新的范围就是[left,
    currentMax]重新计算middle再检查;
  • 否则说明此时的middle太小了,应该从[middle + 1, right]这个范围重新计算middle进行检查。

下面再说一下给出一个maxSum,怎么检查数组能否被分成m份,并且每一份的元素值之和不超过maxSum。
要实现这个过程,可以从左往右遍历数组,把数组的值一个一个地加起来,如果加起来的和已经超过了maxSum,那么说明前面加起来的元素应该被分成一组,那么刚刚加进来的元素就应该作为下一个分组的第一个元素,同时m的数量应该减1(可以把m理解为挡板数量,这里需要放置一个挡板,把前面的元素分割成一个组了)。如果发现挡板数量m不够了,说明不能满足要求,返回一个-1;如果遍历完数组,挡板还没用完,说明可以满足要求,返回这次检查的最大值。

因为题目中要计算多个整数之和,而且LeetCode的测试用例中存在值较大的整数,求和之后会超过int的表示范围,所以程序的整数我用了long long的类型。

代码:

//
//  main.cpp
//  410. Split Array Largest Sum
//
//  Created by mingjc on 2017/3/18.
//  Copyright © 2017年 mingjc. All rights reserved.
//

#include 
#include 
using namespace std;

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        if (nums.size() == 0) {
            return 0;
        }
        long long result = 0;
        long long max = -1, sum = 0;
        long long left, right;

        vector<int>::iterator it = nums.begin();
        while (it != nums.end()) {
            if (max < *it) {
                max = *it;
            }
            sum += *it;
            it++;
        }
        if (m == 1) {
            return sum;
        }
        result = sum;
        left = max;
        right = sum;
        while (left < right) {
            long long middle = (left + right) / 2;
            long long checkResult = check(nums, middle, m - 1);
            if (checkResult == -1) {
                left = middle + 1;
            }
            else {
                right = checkResult;
                if (checkResult < result) {
                    result = checkResult;
                }
            }

        }

        return result;
    }
    // 检查能否将数组nums分成(m + 1)份,且每一份的元素值之和不超过maxSum
    // 如果不可以,返回-1,否则返回元素值之和最大的子数组的元素值之和
    long long check(vector<int>& nums, long long maxSum, int m) {
        long long currentMax = 0; // 存储最大的subArray元素和
        long long tempSum = 0; // 存储当前subArray的元素和
        for (vector<int>::iterator it = nums.begin(); it != nums.end(); it++) {
            tempSum += *it;
            // 当前subArray的元素和已经超过了maxSum,把当前的元素从当前subArray移走,将当前元素当作下一个分组的第一位
            if (tempSum > maxSum) {
                tempSum -= *it;
                if (tempSum > currentMax) {
                    currentMax = tempSum;
                }
                it--;
                m--;
                tempSum = 0;
                // 隔板数目已经不够了,不能再分
                if (m < 0) {
                    return -1;
                }
            }
            if (tempSum > currentMax) {
                currentMax = tempSum;
            }
        }
        return currentMax;
    }
};

int main(int argc, const char * argv[]) {
    Solution sln;
    int array[] = {1, 2147483647};
    int m = 2;
    int arrayCount = sizeof(array) / sizeof(int);
    vector<int> nums(array, array + arrayCount);
    cout << sln.splitArray(nums, m) << endl;
    return 0;
}

你可能感兴趣的:(leetcode,LeetCode)