给定一个数组,将数组分成k段,得到所有分段中和的最大值
求如何分段,使得所有分段中和的最大值 最小,求出这个最小值。
使用递归暴力搜索进行解题:
递归公示如下:
其中M[n,k]中,n表示数组长度,k表示分段个数
初始化条件:
Java实现代码如下:
public static int getSum(int[] arr, int start, int end) {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += arr[i];
}
return sum;
}
/*
参数:int[]arr需要分段的数组,n数组长度,k需要分的子数组数
返回: int minnum: 将长度为n的数组分为k段,每个子数组和的最大值的最小值(最小化最大值)
*/
public static int getMaxSum(int[] arr, int n,int k){
if(n == 1) {
return arr[0];
}
if(k == 1) {
return getSum(arr, 0, n - 1);
}
int minnum = Integer.MAX_VALUE;
for (int j = 1; j <= n; j++) {
minnum = Math.min(minnum, Math.max(getMaxSum(arr, j, k-1), getSum(arr,j,n-1)));
}
return minnum;
}
java代码实现如下:
public static int dpGetMaxSum(int[]arr, int n, int k){
int[][] M = new int[n+5][n+5];
int[] cm = new int[n+5];
for (int i = 1; i <= k; i++) {
M[1][i] = arr[0];
}
for (int i = 1; i <= n; i++) {
cm[i] = cm[i-1] + arr[i-1];
M[i][1] = cm[i];
}
for (int i = 2; i <= k; i++) { //分段数量递增到k,此时分段最大和最小值非递增
for (int j = 2; j <= n; j++) { //前i-1个分段长度从2递增到n,即最后分段由n-2减至0
int minnum = Integer.MAX_VALUE; //最大化最小值
for (int l = 1; l <= j; l++) { //对i个分段,不断尝试找到最大和最小值
minnum = Math.min(minnum, Math.max(M[l][i-1], cm[j]-cm[l]));
}
M[j][i] = minnum;
}
}
return M[n][k];
}
本问题可以转化为:给定k个桶,求桶的容量至少为多少,才可以将数组中的所有数顺序地装入桶中。
首先我们可以知道,桶的容量最少不会小于数组中的最大值,即桶容量的最小值(小于的话,这个数没法装进任何桶中),假设只需要一个桶,那么其容量应该是数组所有元素的和,即桶容量的最大值;其次,桶数量越多,需要的桶的容量就可以越少,即随着桶容量的增加,需要的桶的数量非递增的(二分查找就是利用这点);我们要求的就是在给定的桶数量m的时候,找最小的桶容量就可以把所有的数依次装入k个桶中。在二分查找的过程中,对于当前的桶容量,我们可以计算出需要的最少桶数requiredPainters,如果需要的桶数量大于给定的桶数量k,说明桶容量太小了,只需在后面找对应的最小容量使需要的桶数恰好等于k;如果计算需要的桶数量小于等于k,说明桶容量可能大了(也可能正好是要找的最小桶容量),不管怎样,该桶容量之后的桶容量肯定不用考虑了(肯定大于最小桶容量),这样再次缩小查找的范围,继续循环直到终止,终止时,当前的桶容量既是最小的桶容量。
对于数组 1 2 3 4 5 6 7,假设k=3,最小桶容量为7(要5个桶),最大桶容量为28(一个桶)
单桶容量 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
桶数量 |
5 |
5 |
4 |
4 |
3 |
3 |
3 |
3 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
2 |
第一行表示桶容量,第二行表示需要的桶数
即要求桶数量恰为k的最小桶容量;
因为桶数量增加时,桶容量肯定减小(可以想象把装的最多的桶拆成两个桶,那么装的第二多的桶就变成了之后的桶容量),所以找对应k的最小容量;
也是因为如此,上面两种方法(递归,DP)中,再求k个桶的最小容量时,也求了桶个数小于k时的最小桶容量,因为k个桶的最小容量肯定小于k-i时的最小容量,所以最后结果不会有影响。
java代码实现如下:
public static int binarySearchGetMaxSum(int[]arr, int n, int k) {
int maxnum = 0;
int minnum = 0;
for (int i = 0; i < arr.length; i++) {
maxnum += arr[i];
if(minnum < arr[i]) {
minnum = arr[i];
}
}
while(minnum != maxnum -1) {
int mid = (maxnum + minnum)/2;
if(canPaint(arr,n,mid) <= k){ //此处应有等号,确保找到的桶容量为最小值而
//不是桶数量相同时,桶容量的最大值
maxnum = mid;
}else{
minnum = mid;
}
}
return maxnum;
}
public static int canPaint(int[] arr,int n, int mid){
int needk = 1;
int x = mid;
for (int i = 0; i < arr.length; i++) {
if(x - arr[i] >= 0){
x -= arr[i];
}else{
x = mid - arr[i]; //将该数放到新的桶里装
needk++;
}
}
return needk;
}