数组分段和最大值的最小值问题

题目:

给定一个数组,将数组分成k段,得到所有分段中和的最大值

求如何分段,使得所有分段中和的最大值 最小,求出这个最小值。

题目分析:

方法1:暴力搜索

使用递归暴力搜索进行解题:

         递归公示如下:

                                    M[n,k] = {\min_{j=1}^{n}}\left ( \max \left \{ M[j,k-1], \sum_{i=j}^{n}A[i]\right \}\right )

        其中M[n,k]中,n表示数组长度,k表示分段个数

        初始化条件:

                                   M[1,k] = A[0]

                                   M[n,1] = \sum_{i=1}^{n}A[i]

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;
}

方法二:动态规划(DP)

 

       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;
	}

 

你可能感兴趣的:(算法)