LeetCode410. 分割数组的最大值
给定一个非负整数数组和一个整数 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,在所有情况中最小。
解析:
-
解法一:动态规划
动态规划的思路很简单,但算法复杂度较高
当一个数组nums,已知其前j个数,分k-1组的子数组最大和为dp[j][k-1]
前i个数分为k组的子数组最大和dp[i][k]=Math.max( , ),
具体代码如下:
class Solution {
public int splitArray(int[] nums, int m) {
if(m==0){
return 0;
}
int[][] dp = new int[nums.length][nums.length];
for(int i=0;i<nums.length;i++){
dp[i][i]=nums[i];
for(int j=i+1;j<nums.length;j++){
dp[i][j]=dp[i][j-1]+nums[j];
}
}
if(m==1){
return dp[0][nums.length-1];
}
int[][] dpM = new int[nums.length][m+1];
for(int i=0;i<nums.length;i++){
dpM[i][1]=dp[0][i];
}
for(int k=2;k<=m;k++){
for(int i=k-1;i<nums.length;i++){
int res = Integer.MAX_VALUE;
for( int j=k-2;j<=i-1;j++){
res=Math.min(res,Math.max(dpM[j][k-1],dp[j+1][i]));
}
dpM[i][k]=res;
}
}
return dpM[nums.length-1][m];
}
}
解法二: 二分法
分析:
- 首先,对于这个问题,数组nums如果按照一个数分一组,那么最大和即为max(nums),如果所有数在同一组,那么最大和为sum(nums),而这两个值也就是所有可能解的上下界;
- 接着,选取一种中间值,按照这个中间值分组,使得每组和小于mid,且放入的元素最多,记录总分组数;
- 最后如果分组数大于给定的分组数,那么说明每组装少了,需要增大下界;如果分组数小于等于给定的分组数,那么说明每组还能装更多的数,也就是减小上界。
具体逻辑见代码注释:
public int splitArray(int[] nums, int m) {
//二分法是一种基于假设最大值并搜索的方法找到的
//max和min是最后结果所有可能值的上下限,如果可以一个数一组,那么结果是min,
//如果所有数一组,结果为max
long min=0,max=0;
//初始化min,max
for(int i:nums){
max+=i;
if(i>min){
min=i;
}
}
long res = max;
//开始搜索
while(min<max){
//这一轮循环认为每一组最大和为(min+max)/2
long mid = (min+max)>>1;
//sum存储每一组的和
long sum=0;
int group=1;
for(int i: nums){
//分组和超过了mid,则重新分一组
if(sum+i>mid){
sum=i;
group++;
}else{
sum+=i;
}
}
//如果分组多了,那说明我们每组和小了,提高我们分组和范围的下界,
//则可以让更多的数进入同一组
if(group>m){
min=mid+1;
}else{
//如果分组少了,那说明我们每组和大了,降低我们分组和范围的上界,
//则可以让更少的数进入同一组
res=Math.min(res,mid);
max=mid-1;
}
}
return (int)res;
}