题目描述:
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意:
数组长度 n 满足以下条件:
- 1 ≤ n ≤ 1000
- 1 ≤ m ≤ min(50, n)
示例:
输入: nums = [7,2,5,10,8] m = 2 输出: 18 解释: 一共有四种方法将nums分割为2个子数组。 其中最好的方式是将其分为[7,2,5] 和 [10,8], 因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
错误思路:
最开始直接想到的方法是分组: 数1|数2|数3|数4 数5 数6 数7........... 分割线为分组情况, 然后记录下分割线的位置,从最右边的分割线开始,依次将分割线向右边移动,每次判断子数组和的最大值,如果减小就移动下一条分割线,于是很快写出了这样的代码。
JavaScript:
/**
* @param {number[]} nums
* @param {number} m
* @return {number}
*/
var splitArray = function(nums, m) {
var splitArr=[];
var valueArr=[];
var len=nums.length;
var max=0;
for(var i=0;i<m;i++)
{
splitArr.push(i);
if(i!=m-1)
valueArr.push(nums[i]);
}
var temp=0;
for(var i=m-1;i<len;i++)
{
temp+=nums[i];
}
splitArr.push(len-1);
valueArr.push(temp);
max=Math.max.apply(null,valueArr);
for(var i=m-1;i>0;i--)
{
var st=splitArr[i];
var ed=splitArr[i+1];
for(var j=st;j<ed;j++)
{
var tempA=valueArr.concat();
tempA[i]-=nums[j];
tempA[i-1]+=nums[j];
var tempMax=Math.max.apply(null,tempA);
if(tempMax<max)
{
max = tempMax;
splitArr[i]=j+1;
valueArr[i]-=nums[j];
valueArr[i-1]+=nums[j];
}
else
{
break;
}
}
}
return max;
};
但是运行的时候不能通过全部的测试用例,分析了一下逻辑,发现陷入了局部最优解。
正确思路:
采用动态规划。用一个二维数组记录情况,记录分组里面子数组各自和的最大值最小的值。dp[i][j]表示将输入数组的前j个数分为i 组。其中有dp[0][0]=0。dp[i-1][k]表示将k个数分为i-1组,j-k个数单独分为一组。那么刚好合起来就是i组。 转变为找所有分组里面子数组和的最大值。在这些最大值里面找到最小的值。
JavaScript:
/**
* @param {number[]} nums
* @param {number} m
* @return {number}
*/
var splitArray = function(nums, m) {
//动态规划
var len=nums.length;
var dp=[];
var maxArr=[0];
nums.forEach(function(item,index){
maxArr[index+1]=maxArr[index]+item;
});
for(var i=0;i<=m;i++)
{
dp[i]=[];
for(var j=0;j<=len;j++)
{
dp[i][j]=maxArr[maxArr.length-1];
}
}
dp[0][0]=0;
for(var i=1;i<=m;i++)
{
for(var j=1;j<=len;j++)
{
for(var k=i-1;k<j;k++)
{
var value=Math.max(dp[i-1][k],maxArr[j]-maxArr[k]);
dp[i][j]=Math.min(dp[i][j],value);
}
}
}
return dp[m][len];
};