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

探讨了将数组分成k段,求所有分段中和的最大值最小化的三种方法:暴力搜索、动态规划和二分查找。通过实例解析,详细介绍了每种方法的原理和Java代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

给定一个数组,将数组分成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;
	}

 

分段方法在Java中通常用于处理数据范围的划分,比如将一个区间划分为几个较小的子区间。这种操作可以用来实现一些特定的功能,如计算统计区间、分割数组等。以下是一个简单的例子,展示了如何创建一个静态方法,接收两个整数参数(minValue maxValue),并返回一个表示这些范围的列表: ```java import java.util.ArrayList; import java.util.List; public class SegmentRange { public static List<Segment> createSegments(int minValue, int maxValue) { if (minValue > maxValue) { throw new IllegalArgumentException("Minimum value must be less than or equal to maximum value."); } List<Segment> segments = new ArrayList<>(); while (minValue <= maxValue) { segments.add(new Segment(minValue, Math.min(minValue + 9, maxValue))); // 每次加10作为示例,你可以替换为你需要的步长 minValue += 10; // 示例中每次增加10,可以根据实际情况调整 } return segments; } static class Segment { private final int start; private final int end; public Segment(int start, int end) { this.start = start; this.end = end; } // 获取开始结束值的方法 public int getStart() { return start; } public int getEnd() { return end; } } public static void main(String[] args) { List<Segment> segments = createSegments(1, 50); for (Segment segment : segments) { System.out.println("Segment from " + segment.getStart() + " to " + segment.getEnd()); } } } ``` 在这个例子中,`createSegments` 方法会生成一系列`Segment`对象,每个对象代表一个从`minValue`到`maxValue`之间的一个间隔。注意这只是一个基础示例,实际应用中你需要根据需求定制步长。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值