|
|
package class41;
|
|
|
|
|
|
// leetcode原题
|
|
|
// 测试链接:https://leetcode.com/problems/split-array-largest-sum/
|
|
|
public class Code04_SplitArrayLargestSum {
|
|
|
|
|
|
// 求原数组arr[L...R]的累加和
|
|
|
public static int sum(int[] sum, int L, int R) {
|
|
|
return sum[R + 1] - sum[L];
|
|
|
}
|
|
|
|
|
|
// 不优化枚举的动态规划方法,O(N^2 * K)
|
|
|
public static int splitArray1(int[] nums, int K) {
|
|
|
int N = nums.length;
|
|
|
int[] sum = new int[N + 1];
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
sum[i + 1] = sum[i] + nums[i];
|
|
|
}
|
|
|
int[][] dp = new int[N][K + 1];
|
|
|
for (int j = 1; j <= K; j++) {
|
|
|
dp[0][j] = nums[0];
|
|
|
}
|
|
|
for (int i = 1; i < N; i++) {
|
|
|
dp[i][1] = sum(sum, 0, i);
|
|
|
}
|
|
|
// 每一行从上往下
|
|
|
// 每一列从左往右
|
|
|
// 根本不去凑优化位置对儿!
|
|
|
for (int i = 1; i < N; i++) {
|
|
|
for (int j = 2; j <= K; j++) {
|
|
|
int ans = Integer.MAX_VALUE;
|
|
|
// 枚举是完全不优化的!
|
|
|
for (int leftEnd = 0; leftEnd <= i; leftEnd++) {
|
|
|
int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1];
|
|
|
int rightCost = leftEnd == i ? 0 : sum(sum, leftEnd + 1, i);
|
|
|
int cur = Math.max(leftCost, rightCost);
|
|
|
if (cur < ans) {
|
|
|
ans = cur;
|
|
|
}
|
|
|
}
|
|
|
dp[i][j] = ans;
|
|
|
}
|
|
|
}
|
|
|
return dp[N - 1][K];
|
|
|
}
|
|
|
|
|
|
// 课上现场写的方法,用了枚举优化,O(N * K)
|
|
|
public static int splitArray2(int[] nums, int K) {
|
|
|
int N = nums.length;
|
|
|
int[] sum = new int[N + 1];
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
sum[i + 1] = sum[i] + nums[i];
|
|
|
}
|
|
|
int[][] dp = new int[N][K + 1];
|
|
|
int[][] best = new int[N][K + 1];
|
|
|
for (int j = 1; j <= K; j++) {
|
|
|
dp[0][j] = nums[0];
|
|
|
best[0][j] = -1;
|
|
|
}
|
|
|
for (int i = 1; i < N; i++) {
|
|
|
dp[i][1] = sum(sum, 0, i);
|
|
|
best[i][1] = -1;
|
|
|
}
|
|
|
// 从第2列开始,从左往右
|
|
|
// 每一列,从下往上
|
|
|
// 为什么这样的顺序?因为要去凑(左,下)优化位置对儿!
|
|
|
for (int j = 2; j <= K; j++) {
|
|
|
for (int i = N - 1; i >= 1; i--) {
|
|
|
int down = best[i][j - 1];
|
|
|
// 如果i==N-1,则不优化上限
|
|
|
int up = i == N - 1 ? N - 1 : best[i + 1][j];
|
|
|
int ans = Integer.MAX_VALUE;
|
|
|
int bestChoose = -1;
|
|
|
for (int leftEnd = down; leftEnd <= up; leftEnd++) {
|
|
|
int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1];
|
|
|
int rightCost = leftEnd == i ? 0 : sum(sum, leftEnd + 1, i);
|
|
|
int cur = Math.max(leftCost, rightCost);
|
|
|
// 注意下面的if一定是 < 课上的错误就是此处!当时写的 <= !
|
|
|
// 也就是说,只有取得明显的好处才移动!
|
|
|
// 举个例子来说明,比如[2,6,4,4],3个画匠时候,如下两种方案都是最优:
|
|
|
// (2,6) (4) 两个画匠负责 | (4) 最后一个画匠负责
|
|
|
// (2,6) (4,4)两个画匠负责 | 最后一个画匠什么也不负责
|
|
|
// 第一种方案划分为,[0~2] [3~3]
|
|
|
// 第二种方案划分为,[0~3] [无]
|
|
|
// 两种方案的答案都是8,但是划分点位置一定不要移动!
|
|
|
// 只有明显取得好处时(<),划分点位置才移动!
|
|
|
// 也就是说后面的方案如果==前面的最优,不要移动!只有优于前面的最优,才移动
|
|
|
// 比如上面的两个方案,如果你移动到了方案二,你会得到:
|
|
|
// [2,6,4,4] 三个画匠时,最优为[0~3](前两个画家) [无](最后一个画家),
|
|
|
// 最优划分点为3位置(best[3][3])
|
|
|
// 那么当4个画匠时,也就是求解dp[3][4]时
|
|
|
// 因为best[3][3] = 3,这个值提供了dp[3][4]的下限
|
|
|
// 而事实上dp[3][4]的最优划分为:
|
|
|
// [0~2](三个画家处理) [3~3] (一个画家处理),此时最优解为6
|
|
|
// 所以,你就得不到dp[3][4]的最优解了,因为划分点已经越过2了
|
|
|
// 提供了对数器验证,你可以改成<=,对数器和leetcode都过不了
|
|
|
// 这里是<,对数器和leetcode都能通过
|
|
|
// 这里面会让同学们感到困惑的点:
|
|
|
// 为啥==的时候,不移动,只有<的时候,才移动呢?例子懂了,但是道理何在?
|
|
|
// 哈哈哈哈哈,看了邮局选址问题,你更懵,请看42节!
|
|
|
if (cur < ans) {
|
|
|
ans = cur;
|
|
|
bestChoose = leftEnd;
|
|
|
}
|
|
|
}
|
|
|
dp[i][j] = ans;
|
|
|
best[i][j] = bestChoose;
|
|
|
}
|
|
|
}
|
|
|
return dp[N - 1][K];
|
|
|
}
|
|
|
|
|
|
public static int splitArray3(int[] nums, int M) {
|
|
|
long sum = 0;
|
|
|
for (int i = 0; i < nums.length; i++) {
|
|
|
sum += nums[i];
|
|
|
}
|
|
|
long l = 0;
|
|
|
long r = sum;
|
|
|
long ans = 0;
|
|
|
while (l <= r) {
|
|
|
long mid = (l + r) / 2;
|
|
|
long cur = getNeedParts(nums, mid);
|
|
|
if (cur <= M) {
|
|
|
ans = mid;
|
|
|
r = mid - 1;
|
|
|
} else {
|
|
|
l = mid + 1;
|
|
|
}
|
|
|
}
|
|
|
return (int) ans;
|
|
|
}
|
|
|
|
|
|
public static int getNeedParts(int[] arr, long aim) {
|
|
|
for (int i = 0; i < arr.length; i++) {
|
|
|
if (arr[i] > aim) {
|
|
|
return Integer.MAX_VALUE;
|
|
|
}
|
|
|
}
|
|
|
int parts = 1;
|
|
|
int all = arr[0];
|
|
|
for (int i = 1; i < arr.length; i++) {
|
|
|
if (all + arr[i] > aim) {
|
|
|
parts++;
|
|
|
all = arr[i];
|
|
|
} else {
|
|
|
all += arr[i];
|
|
|
}
|
|
|
}
|
|
|
return parts;
|
|
|
}
|
|
|
|
|
|
public static int[] randomArray(int len, int maxValue) {
|
|
|
int[] arr = new int[len];
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
arr[i] = (int) (Math.random() * maxValue);
|
|
|
}
|
|
|
return arr;
|
|
|
}
|
|
|
|
|
|
public static void printArray(int[] arr) {
|
|
|
for (int i = 0; i < arr.length; i++) {
|
|
|
System.out.print(arr[i] + " ");
|
|
|
}
|
|
|
System.out.println();
|
|
|
}
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
int N = 100;
|
|
|
int maxValue = 100;
|
|
|
int testTime = 10000;
|
|
|
System.out.println("测试开始");
|
|
|
for (int i = 0; i < testTime; i++) {
|
|
|
int len = (int) (Math.random() * N) + 1;
|
|
|
int M = (int) (Math.random() * N) + 1;
|
|
|
int[] arr = randomArray(len, maxValue);
|
|
|
int ans1 = splitArray1(arr, M);
|
|
|
int ans2 = splitArray2(arr, M);
|
|
|
int ans3 = splitArray3(arr, M);
|
|
|
if (ans1 != ans2 || ans1 != ans3) {
|
|
|
System.out.print("arr : ");
|
|
|
printArray(arr);
|
|
|
System.out.println("M : " + M);
|
|
|
System.out.println("ans1 : " + ans1);
|
|
|
System.out.println("ans2 : " + ans2);
|
|
|
System.out.println("ans3 : " + ans3);
|
|
|
System.out.println("Oops!");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
System.out.println("测试结束");
|
|
|
}
|
|
|
}
|