题目:
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:
Given m satisfies the following constraint: 1 ≤ m ≤ length(nums) ≤ 14,000.
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.简单意思就是:给你一个含有n个元素的数组,让你把这数组划分成m段,那么每个段的所有元素就有一个sum,那么一次划分就会有一个最大的和(largest_sum),现在就是让你
求这个largest_sum的最小值!!!
方法一:
刚开始拿到这个题目,看到它的提示是动态规划,就一直想,就是想不出来后来在网上找到了大神们都用二分查找算法,但是这些博客没有对算法的思路有个详细的介绍,本文
就二分查找方法给出自己的理解!!
代码(模仿大神们的思想写的):
class Solution {
public:
bool is_reach(vector& arr, int m, long target_sum){
long cur_sum = arr[0];
int sections = 1;
for (int i = 1; i < arr.size(); ++i){
cur_sum += arr[i];
if (cur_sum > target_sum){
++sections;
cur_sum = arr[i];
}
}
if (sections > m)return false;//如果段计数器值大于m,假设题目要求m=3,而我们求得的段的大小为4,
return true;
}
int splitArray(vector& arr, int m) {
int n = arr.size();
long low = arr[0], high = arr[0];
for (int i = 1; i < n; ++i){
low = max(low, long(arr[i]));//数组的单个最大元素
high += arr[i];//数组的所有元素
}
int res = 0;
while (low < high){
long mid = (low + high) / 2;
if (is_reach(arr, m, mid)){
high = mid;
res = mid;
}
else
low = mid + 1;
}
return low;
}
};
解析(leetcode上大神给的解释):
1.我们可以这样对待数组,我们将数组分成每段小于或等于target_num的小组,那么每个小组的和肯定小于或等于target_sum,如果此时我们得到的分段数目小于或等于m,
那么确定最大子段和小于或等于target_sum,否则,最大子段和的最小值一定大于target_sum;
2. 在设计的时候一定要注意: 如果写成high=mid和low=mid的模式,则有可能陷入死循环的可能,因为我们最后low,mid和high的指针的转态如下图所示:
如果is_reach传回的值是true,那么执行low=mid,那么会陷入死循环!!!!
方法二:
利用动态规划思想:
版本1:
int splitArray1(vectornums, int m){
int L = nums.size();
int *S = new int[L + 1];
S[0] = 0;
for (int i = 0; i
版本2:
class Solution {
public:
int splitArray(vector& nums, int m) {
int size = nums.size();
vectorrow(size,0);
vector>re;
for (int i = 0; i <= m; i++)re.push_back(row);//二维数组
//将数组分成1段的情况
int sum = 0;
for (int j = 0; j < size; j++){
sum += nums[j];
re[1][j] = sum;
}
//填表
int temp, now;
for (int i = 2; i <= m;i++){//i表示要分成的段数
for (int j = i - 1; j < size;j++){
temp = INT_MAX;
for (int k = i - 2; k <= j - 1; k++){
//re[i][j]表示将数组索引从0到j的元素分成i段
now = max(re[i-1][k],re[1][j]-re[1][k]);
if (now<=temp)temp = now;
}
re[i][j] = temp;
}
}
return re[m][size - 1];
}
};
结果:
Status:
这个说明思想没有问题的,但是时间复杂度较高,运行超时,还要进一步优化!!!
算法思想:
re[s][j]:表示数组元素下标从0到j分成s段的结果;
re[s+1][j]:表示数组元素下标从0到j分成s+1段的结果,那么re[s+1][j]=min{max(d[s,k],a[k+1]+a[k+2]+....+a[j])},其中,s-2<=k<=j-1(见代码).