书本分发——经典的数组分段求和最小问题,动态规划+二分查找法

说明

给你N本书。每本书的页数都是π。你必须把书分配给M个学生。有许多方法或排列可以做到这一点。在每个排列中,M个学生中的一个将被分配最大页数。在所有这些排列中,任务是找出分配给学生的最大页数是所有其他排列中最小页数的特定排列,并打印这个最小值。每本书只分配给一个学生。每个学生至少要分配一本书

输入
第一行包含表示测试用例数的’T’。接着是对T的描述测试用例:每个用例从一个正整数N开始,表示书。书第二行包含N个空格分隔的正整数,表示每行的页数预定。然后呢第三行包含另一个整数M,表示学生人数N训练:1<=T<=70,1<=N<=50,1<=A[i]<=250,1<=M<=50,注意:如果无法进行有效赋值,则返回-1,并且分配应按连续顺序进行(请参阅解释以获得更好的理解)

输出
对于每个测试用例,输出一行,包含每个学生必须为相应的测试用例阅读的最小页数。

样本输入1
1
4
12 34 67 90
2

样本输出1
113

题目分析:原问题相当于,给定一个连续的数组,要求把数组分为k份,求出使得k份中最大的值最小的情况对应的值,如数组{12,34,67,90}分为2份,只有分为{12,34,67}和{90}时,可以得到最小值113。

题解一:蛮力法求解
n n-1
M[n, k] = min { max { M[j, k-1], ∑ Ai } }
j=1 i=j

public class AllocateBook {
	// 暴力求解,指数级别
	static int sum(int A[], int from, int to) {
		int total = 0;
		for (int i = from; i <= to; i++)
			total += A[i];
		return total;
	}

	static int partition(int A[], int n, int k) {
		if (k == 1)
			return sum(A, 0, n - 1);
		if (n == 1)
			return A[0];

		int best = Integer.MAX_VALUE;

		for (int j = 1; j <= n; j++) {
			int p1 = partition(A, j, k - 1);
			int sum = sum(A, j, n - 1);
			best = Math.min(best, Math.max(p1, sum));
		}
		return best;

	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int testcase = sc.nextInt();
		for (int i = 0; i < testcase; i++) {
			int n = sc.nextInt();
			int[] arr = new int[n];
			for (int j = 0; j < n; j++) {
				arr[j] = sc.nextInt();
			}
			int k = sc.nextInt();
			System.out.println(partition(arr, n, k));
		}
		sc.close();
	}
}

分析:每次partition都需要重复地计算从下标from到下标to的数值之和sum,并且有多次重复计算,因此时间复杂度是指数级别的。

题解二:动态规划
用一个二维数组空间M[n][k]保存中间结果,n代表原数组的长度,k代表分成的份数,最后返回M[n][k]即可。

	static int findMax(int A[], int n, int k) {
		int M[][] = new int[n + 1][k + 1];
		int cum[] = new int[n + 1];
		for (int i = 1; i <= n; i++)
			cum[i] = cum[i - 1] + A[i - 1];

		for (int i = 1; i <= n; i++)
			M[i][1] = cum[i];
		for (int i = 1; i <= k; i++)
			M[1][i] = A[0];

		for (int i = 2; i <= k; i++) {
			for (int j = 2; j <= n; j++) {
				int best = Integer.MAX_VALUE;
				for (int p = 1; p <= j; p++) {
					best = Math.min(best, Math.max(M[p][i - 1], cum[j] - cum[p]));
				}
				M[j][i] = best;
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= k; j++)
				System.out.print(M[i][j] + " ");
			System.out.println();
		}
		return M[n][k];
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int testcase = sc.nextInt();
		for (int i = 0; i < testcase; i++) {
			int n = sc.nextInt();
			int[] arr = new int[n];
			for (int j = 0; j < n; j++) {
				arr[j] = sc.nextInt();
			}
			int k = sc.nextInt();
			System.out.println(findMax(arr, n, k));
		}
		sc.close();
	}

时间复杂度:O(kn²)
空间复杂度O(k
n)

题解三:二分查找
这是个牛人写的程序,我只是个搬运工。
算法思路:
low=max(arr)//数组中最大的值
high=sum(arr)//数组元素之和
mid=(low+high)/2 //求中间值
然后计算有多少组可以大于mid的个数number
比较number与k
如果number<=k 则high=mid 说明数组还可以再分
否则low=mid+1 说明数组左边不能分了,转向右边

//二分查找
	int getMax(int A[], int n) {
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			if (A[i] > max)
				max = A[i];
		}
		return max;
	}

	int getSum(int A[], int n) {
		int total = 0;
		for (int i = 0; i < n; i++)
			total += A[i];
		return total;
	}

	int getRequiredPainters(int A[], int n, int maxLengthPerPainter) {
		int total = 0, numPainters = 1;
		for (int i = 0; i < n; i++) {
			total += A[i];
			if (total > maxLengthPerPainter) {
				total = A[i];
				numPainters++;
			}
		}
		return numPainters;
	}

	int BinarySearch(int A[], int n, int k) {
		int lo = getMax(A, n);
		int hi = getSum(A, n);

		while (lo < hi) {
			int mid = lo + (hi - lo) / 2;
			int requiredPainters = getRequiredPainters(A, n, mid);
			System.out.println("lo="+lo+",hi="+hi+",mid="+mid+",requ="+requiredPainters);
			if (requiredPainters <= k)
				hi = mid;
			else
				lo = mid + 1;
		}
		return lo;
	}

时间复杂度:O(n*log(∑ ai ))
参考文章:
https://blog.csdn.net/qq_18121279/article/details/82622073

你可能感兴趣的:(算法,算法,动态规划,java)