public static int bestSplit1(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int N = arr.length;
int ans = 0;
for (int s = 0; s < N - 1; s++) {
int sumL = 0;
for (int L = 0; L <= s; L++) {
sumL += arr[L];
}
int sumR = 0;
for (int R = s + 1; R < N; R++) {
sumR += arr[R];
}
ans = Math.max(ans, Math.min(sumL, sumR));
}
return ans;
}
public static int bestSplit2(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int N = arr.length;
int sumAll = 0;
for (int num : arr) {
sumAll += num;
}
int ans = 0;
int sumL = 0;
// [0...s] [s+1...N-1]
for (int s = 0; s < N - 1; s++) {
sumL += arr[s];
int sumR = sumAll - sumL;
ans = Math.max(ans, Math.min(sumL, sumR));
}
return ans;
}
public static int[] randomArray(int len, int max) {
int[] ans = new int[len];
for (int i = 0; i < len; i++) {
ans[i] = (int) (Math.random() * max);
}
return ans;
}
public static void main(String[] args) {
int N = 20;
int max = 30;
int testTime = 1000000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * N);
int[] arr = randomArray(len, max);
int ans1 = bestSplit1(arr);
int ans2 = bestSplit2(arr);
if (ans1 != ans2) {
System.out.println(ans1);
System.out.println(ans2);
System.out.println("Oops!");
}
}
System.out.println("测试结束");
}
public static int[] bestSplit1(int[] arr) {
if (arr == null || arr.length == 0) {
return new int[0];
}
int N = arr.length;
int[] ans = new int[N];
ans[0] = 0;
for (int range = 1; range < N; range++) {
for (int s = 0; s < range; s++) {
int sumL = 0;
for (int L = 0; L <= s; L++) {
sumL += arr[L];
}
int sumR = 0;
for (int R = s + 1; R <= range; R++) {
sumR += arr[R];
}
ans[range] = Math.max(ans[range], Math.min(sumL, sumR));
}
}
return ans;
}
// 求原来的数组arr中,arr[L...R]的累加和
public static int sum(int[] sum, int L, int R) {
return sum[R + 1] - sum[L];
}
public static int[] bestSplit2(int[] arr) {
if (arr == null || arr.length == 0) {
return new int[0];
}
int N = arr.length;
int[] ans = new int[N];
ans[0] = 0;
int[] sum = new int[N + 1];
for (int i = 0; i < N; i++) {
sum[i + 1] = sum[i] + arr[i];
}
for (int range = 1; range < N; range++) {
for (int s = 0; s < range; s++) {
int sumL = sum(sum, 0, s);
int sumR = sum(sum, s + 1, range);
ans[range] = Math.max(ans[range], Math.min(sumL, sumR));
}
}
return ans;
}
public static int[] bestSplit3(int[] arr) {
if (arr == null || arr.length == 0) {
return new int[0];
}
int N = arr.length;
int[] ans = new int[N];
ans[0] = 0;
// arr = {5, 3, 1, 3}
// 0 1 2 3
// sum ={0, 5, 8, 9, 12}
// 0 1 2 3 4
// 0~2 -> sum[3] - sum[0]
// 1~3 -> sum[4] - sum[1]
int[] sum = new int[N + 1];
for (int i = 0; i < N; i++) {
sum[i + 1] = sum[i] + arr[i];
}
// 最优划分
// 0~range-1上,最优划分是左部分[0~best] 右部分[best+1~range-1]
int best = 0;
for (int range = 1; range < N; range++) {
while (best + 1 < range) {
int before = Math.min(sum(sum, 0, best), sum(sum, best + 1, range));
int after = Math.min(sum(sum, 0, best + 1), sum(sum, best + 2, range));
// 注意,一定要是>=,只是>会出错
// 课上会讲解
if (after >= before) {
best++;
} else {
break;
}
}
ans[range] = Math.min(sum(sum, 0, best), sum(sum, best + 1, range));
}
return ans;
}
public static int[] randomArray(int len, int max) {
int[] ans = new int[len];
for (int i = 0; i < len; i++) {
ans[i] = (int) (Math.random() * max);
}
return ans;
}
public static boolean isSameArray(int[] arr1, int[] arr2) {
if (arr1.length != arr2.length) {
return false;
}
int N = arr1.length;
for (int i = 0; i < N; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
public static void main(String[] args) {
int N = 20;
int max = 30;
int testTime = 1000000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * N);
int[] arr = randomArray(len, max);
int[] ans1 = bestSplit1(arr);
int[] ans2 = bestSplit2(arr);
int[] ans3 = bestSplit3(arr);
if (!isSameArray(ans1, ans2) || !isSameArray(ans1, ans3)) {
System.out.println("Oops!");
}
}
System.out.println("测试结束");
}
1,两个可变参数的区间划分问题
2,每个格子有枚举行为
3,当两个可变参数固定一个,另一个参数和答案之间存在单调性关系
4,而且两组单调关系是反向的:(升 升,降 降) (升 降,降 升)
5,能否获得指导枚举优化的位置对:上+右,或者,左+下
1,不要证明!用对数器验证!
2,枚举的时候面对最优答案相等的时候怎么处理?用对数器都试试!
3,可以把时间复杂度降低一阶
O(N^3) -> O(N^2)
O(N^2 * M) -> O(N * M)
O(N * M^2) -> O(N * M)
4,四边形不等式有些时候是最优解,有些时候不是
不是的原因:尝试思路,在根儿上不够好
public static int[] sum(int[] arr) {
int N = arr.length;
int[] s = new int[N + 1];
s[0] = 0;
for (int i = 0; i < N; i++) {
s[i + 1] = s[i] + arr[i];
}
return s;
}
public static int w(int[] s, int l, int r) {
return s[r + 1] - s[l];
}
public static int min1(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int N = arr.length;
int[] s = sum(arr);
return process1(0, N - 1, s);
}
public static int process1(int L, int R, int[] s) {
if (L == R) {
return 0;
}
int next = Integer.MAX_VALUE;
for (int leftEnd = L; leftEnd < R; leftEnd++) {
next = Math.min(next, process1(L, leftEnd, s) + process1(leftEnd + 1, R, s));
}
return next + w(s, L, R);
}
public static int min2(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int N = arr.length;
int[] s = sum(arr);
int[][] dp = new int[N][N];
// dp[i][i] = 0
for (int L = N - 2; L >= 0; L--) {
for (int R = L + 1; R < N; R++) {
int next = Integer.MAX_VALUE;
// dp(L..leftEnd) + dp[leftEnd+1...R] + 累加和[L...R]
for (int leftEnd = L; leftEnd < R; leftEnd++) {
next = Math.min(next, dp[L][leftEnd] + dp[leftEnd + 1][R]);
}
dp[L][R] = next + w(s, L, R);
}
}
return dp[0][N - 1];
}
public static int min3(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int N = arr.length;
int[] s = sum(arr);
int[][] dp = new int[N][N];
int[][] best = new int[N][N];
for (int i = 0; i < N - 1; i++) {
best[i][i + 1] = i;
dp[i][i + 1] = w(s, i, i + 1);
}
for (int L = N - 3; L >= 0; L--) {
for (int R = L + 2; R < N; R++) {
int next = Integer.MAX_VALUE;
int choose = -1;
for (int leftEnd = best[L][R - 1]; leftEnd <= best[L + 1][R]; leftEnd++) {
int cur = dp[L][leftEnd] + dp[leftEnd + 1][R];
if (cur <= next) {
next = cur;
choose = leftEnd;
}
}
best[L][R] = choose;
dp[L][R] = next + w(s, L, R);
}
}
return dp[0][N - 1];
}
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 main(String[] args) {
int N = 15;
int maxValue = 100;
int testTime = 1000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * N);
int[] arr = randomArray(len, maxValue);
int ans1 = min1(arr);
int ans2 = min2(arr);
int ans3 = min3(arr);
if (ans1 != ans2 || ans1 != ans3) {
System.out.println("Oops!");
}
}
System.out.println("测试结束");
}
// 求原数组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("测试结束");
}
public static int min1(int[] arr, int num) {
if (arr == null || num < 1 || arr.length < num) {
return 0;
}
int N = arr.length;
int[][] w = new int[N + 1][N + 1];
for (int L = 0; L < N; L++) {
for (int R = L + 1; R < N; R++) {
w[L][R] = w[L][R - 1] + arr[R] - arr[(L + R) >> 1];
}
}
int[][] dp = new int[N][num + 1];
for (int i = 0; i < N; i++) {
dp[i][1] = w[0][i];
}
for (int i = 1; i < N; i++) {
for (int j = 2; j <= Math.min(i, num); j++) {
int ans = Integer.MAX_VALUE;
for (int k = 0; k <= i; k++) {
ans = Math.min(ans, dp[k][j - 1] + w[k + 1][i]);
}
dp[i][j] = ans;
}
}
return dp[N - 1][num];
}
public static int min2(int[] arr, int num) {
if (arr == null || num < 1 || arr.length < num) {
return 0;
}
int N = arr.length;
int[][] w = new int[N + 1][N + 1];
for (int L = 0; L < N; L++) {
for (int R = L + 1; R < N; R++) {
w[L][R] = w[L][R - 1] + arr[R] - arr[(L + R) >> 1];
}
}
int[][] dp = new int[N][num + 1];
int[][] best = new int[N][num + 1];
for (int i = 0; i < N; i++) {
dp[i][1] = w[0][i];
best[i][1] = -1;
}
for (int j = 2; j <= num; j++) {
for (int i = N - 1; i >= j; i--) {
int down = best[i][j - 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 : w[leftEnd + 1][i];
int cur = leftCost + rightCost;
if (cur <= ans) {
ans = cur;
bestChoose = leftEnd;
}
}
dp[i][j] = ans;
best[i][j] = bestChoose;
}
}
return dp[N - 1][num];
}
// for test
public static int[] randomSortedArray(int len, int range) {
int[] arr = new int[len];
for (int i = 0; i != len; i++) {
arr[i] = (int) (Math.random() * range);
}
Arrays.sort(arr);
return arr;
}
// for test
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int N = 30;
int maxValue = 100;
int testTime = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * N) + 1;
int[] arr = randomSortedArray(len, maxValue);
int num = (int) (Math.random() * N) + 1;
int ans1 = min1(arr, num);
int ans2 = min2(arr, num);
if (ans1 != ans2) {
printArray(arr);
System.out.println(num);
System.out.println(ans1);
System.out.println(ans2);
System.out.println("Oops!");
}
}
System.out.println("测试结束");
}
public static int superEggDrop1(int kChess, int nLevel) {
if (nLevel < 1 || kChess < 1) {
return 0;
}
return Process1(nLevel, kChess);
}
// rest还剩多少层楼需要去验证
// k还有多少颗棋子能够使用
// 一定要验证出最高的不会碎的楼层!但是每次都是坏运气。
// 返回至少需要扔几次?
public static int Process1(int rest, int k) {
if (rest == 0) {
return 0;
}
if (k == 1) {
return rest;
}
int min = Integer.MAX_VALUE;
for (int i = 1; i != rest + 1; i++) { // 第一次扔的时候,仍在了i层
min = Math.min(min, Math.max(Process1(i - 1, k - 1), Process1(rest - i, k)));
}
return min + 1;
}
public static int superEggDrop2(int kChess, int nLevel) {
if (nLevel < 1 || kChess < 1) {
return 0;
}
if (kChess == 1) {
return nLevel;
}
int[][] dp = new int[nLevel + 1][kChess + 1];
for (int i = 1; i != dp.length; i++) {
dp[i][1] = i;
}
for (int i = 1; i != dp.length; i++) {
for (int j = 2; j != dp[0].length; j++) {
int min = Integer.MAX_VALUE;
for (int k = 1; k != i + 1; k++) {
min = Math.min(min, Math.max(dp[k - 1][j - 1], dp[i - k][j]));
}
dp[i][j] = min + 1;
}
}
return dp[nLevel][kChess];
}
public static int superEggDrop3(int kChess, int nLevel) {
if (nLevel < 1 || kChess < 1) {
return 0;
}
if (kChess == 1) {
return nLevel;
}
int[][] dp = new int[nLevel + 1][kChess + 1];
for (int i = 1; i != dp.length; i++) {
dp[i][1] = i;
}
int[][] best = new int[nLevel + 1][kChess + 1];
for (int i = 1; i != dp[0].length; i++) {
dp[1][i] = 1;
best[1][i] = 1;
}
for (int i = 2; i < nLevel + 1; i++) {
for (int j = kChess; j > 1; j--) {
int ans = Integer.MAX_VALUE;
int bestChoose = -1;
int down = best[i - 1][j];
int up = j == kChess ? i : best[i][j + 1];
for (int first = down; first <= up; first++) {
int cur = Math.max(dp[first - 1][j - 1], dp[i - first][j]);
if (cur <= ans) {
ans = cur;
bestChoose = first;
}
}
dp[i][j] = ans + 1;
best[i][j] = bestChoose;
}
}
return dp[nLevel][kChess];
}
public static int superEggDrop4(int kChess, int nLevel) {
if (nLevel < 1 || kChess < 1) {
return 0;
}
int[] dp = new int[kChess];
int res = 0;
while (true) {
res++;
int previous = 0;
for (int i = 0; i < dp.length; i++) {
int tmp = dp[i];
dp[i] = dp[i] + previous + 1;
previous = tmp;
if (dp[i] >= nLevel) {
return res;
}
}
}
}
public static int superEggDrop5(int kChess, int nLevel) {
if (nLevel < 1 || kChess < 1) {
return 0;
}
int bsTimes = log2N(nLevel) + 1;
if (kChess >= bsTimes) {
return bsTimes;
}
int[] dp = new int[kChess];
int res = 0;
while (true) {
res++;
int previous = 0;
for (int i = 0; i < dp.length; i++) {
int tmp = dp[i];
dp[i] = dp[i] + previous + 1;
previous = tmp;
if (dp[i] >= nLevel) {
return res;
}
}
}
}
public static int log2N(int n) {
int res = -1;
while (n != 0) {
res++;
n >>>= 1;
}
return res;
}
public static void main(String[] args) {
int maxN = 500;
int maxK = 30;
int testTime = 1000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int N = (int) (Math.random() * maxN) + 1;
int K = (int) (Math.random() * maxK) + 1;
int ans2 = superEggDrop2(K, N);
int ans3 = superEggDrop3(K, N);
int ans4 = superEggDrop4(K, N);
int ans5 = superEggDrop5(K, N);
if (ans2 != ans3 || ans4 != ans5 || ans2 != ans4) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
}