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 <iostream>
#include <vector>
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;
}