我将Codility问题作为实践解决,并且无法回答其中一个问题。我在互联网上找到答案,但我不知道这个算法的工作原理。有人可以一步一步地指导我吗? 这是一个问题:
/*
You are given integers K, M and a non-empty zero-indexed array A consisting of N integers.
Every element of the array is not greater than M.
You should divide this array into K blocks of consecutive elements.
The size of the block is any integer between 0 and N. Every element of the array should belong to some block.
The sum of the block from X to Y equals A[X] + A[X + 1] + ... + A[Y]. The sum of empty block equals 0.
The large sum is the maximal sum of any block.
For example, you are given integers K = 3, M = 5 and array A such that:
A[0] = 2
A[1] = 1
A[2] = 5
A[3] = 1
A[4] = 2
A[5] = 2
A[6] = 2
The array can be divided, for example, into the following blocks:
[2, 1, 5, 1, 2, 2, 2], [], [] with a large sum of 15;
[2], [1, 5, 1, 2], [2, 2] with a large sum of 9;
[2, 1, 5], [], [1, 2, 2, 2] with a large sum of 8;
[2, 1], [5, 1], [2, 2, 2] with a large sum of 6.
The goal is to minimize the large sum. In the above example, 6 is the minimal large sum.
Write a function:
class Solution { public int solution(int K, int M, int[] A); }
that, given integers K, M and a non-empty zero-indexed array A consisting of N integers, returns the minimal large sum.
For example, given K = 3, M = 5 and array A such that:
A[0] = 2
A[1] = 1
A[2] = 5
A[3] = 1
A[4] = 2
A[5] = 2
A[6] = 2
the function should return 6, as explained above. Assume that:
N and K are integers within the range [1..100,000];
M is an integer within the range [0..10,000];
each element of array A is an integer within the range [0..M].
Complexity:
expected worst-case time complexity is O(N*log(N+M));
expected worst-case space complexity is O(1), beyond input storage (not counting the storage required for input arguments).
Elements of input arrays can be modified.
*/
以下是我发现的有关我不理解的部分的评论的解决方案:
public static int solution(int K, int M, int[] A) {
int lower = max(A); // why lower is max?
int upper = sum(A); // why upper is sum?
while (true) {
int mid = (lower + upper) / 2;
int blocks = calculateBlockCount(A, mid); // don't I have specified number of blocks? What blocks do? Don't get that.
if (blocks < K) {
upper = mid - 1;
} else if (blocks > K) {
lower = mid + 1;
} else {
return upper;
}
}
}
private static int calculateBlockCount(int[] array, int maxSum) {
int count = 0;
int sum = array[0];
for (int i = 1; i < array.length; i++) {
if (sum + array[i] > maxSum) {
count++;
sum = array[i];
} else {
sum += array[i];
}
}
return count;
}
// returns sum of all elements in an array
private static int sum(int[] input) {
int sum = 0;
for (int n : input) {
sum += n;
}
return sum;
}
// returns max value in an array
private static int max(int[] input) {
int max = -1;
for (int n : input) {
if (n > max) {
max = n;
}
}
return max;
}
答案 0 :(得分:6)
所以代码所做的是使用二进制搜索的形式(二进制搜索的工作方式在这里解释得非常好,https://www.topcoder.com/community/data-science/data-science-tutorials/binary-search/。它还使用了一个与你的问题非常相似的例子。)。在哪里搜索每个块需要包含的最小总和。在示例中,您需要将数组划分为3个部分
进行二分查找时,您需要定义2个边界,您可以确定在两者之间可以找到答案。这里,下边界是数组中的最大值(lower
)。例如,这是5(这是你将数组分成7个块)。上边界(upper
)是15,它是数组中所有元素的总和(这是如果你将数组分成1个块。)
现在出现了搜索部分:在solution()
中,你从界限和中点开始(例如10)。
如果您的总和最多为10(您的中间点/或calculateBlockCount
中的count ++
),则maxSum
计算(calculateBlockCount
这样做)您可以制作多少块。
对于示例10(在while循环中),这是2个块,现在代码将此(blocks
)返回到solution
。然后它检查是否小于或大于K
,这是您想要的块数。如果它的K
点mid
小于K
,那么因为你在块中添加了许多数组元素。如果它超过mid
,则upper = mid-1
点太高,并且您在阵列中放置的数组元素太少。
现在检查完之后,它将解空间减半(mid
)。
这发生在每个循环中,它将解空间减半,使其快速收敛。
现在,您一直在调整K
,直到这会给出输入Mid =10 , calculateBlockCount returns 2 blocks
solution. 2 blocks < K so upper -> mid-1 =9, mid -> 7 (lower is 5)
Mid =7 , calculateBlockCount returns 2 blocks
solution() 2 blocks < K so upper -> mid-1 =6, mid -> 5 (lower is 5, cast to int makes it 5)
Mid =5 , calculateBlockCount returns 4 blocks
solution() 4 blocks < K so lower -> mid+1 =6, mid -> 6 (lower is 6, upper is 6
Mid =6 , calculateBlockCount returns 3 blocks
So the function returns mid =6....
中的数量块。
所以一步一步地去做:
{{1}}
希望这有帮助,
学习编码:)
答案 1 :(得分:1)
似乎您的解决方案有一些问题。我将其重写如下:
class Solution {
public int solution(int K, int M, int[] A) {
// write your code in Java SE 8
int high = sum(A);
int low = max(A);
int mid = 0;
int smallestSum = 0;
while (high >= low) {
mid = (high + low) / 2;
int numberOfBlock = blockCount(mid, A);
if (numberOfBlock > K) {
low = mid + 1;
} else if (numberOfBlock <= K) {
smallestSum = mid;
high = mid - 1;
}
}
return smallestSum;
}
public int sum(int[] A) {
int total = 0;
for (int i = 0; i < A.length; i++) {
total += A[i];
}
return total;
}
public int max(int[] A) {
int max = 0;
for (int i = 0; i < A.length; i++) {
if (max < A[i]) max = A[i];
}
return max;
}
public int blockCount(int max, int[] A) {
int current = 0;
int count = 1;
for (int i = 0; i< A.length; i++) {
if (current + A[i] > max) {
current = A[i];
count++;
} else {
current += A[i];
}
}
return count;
}
}
答案 2 :(得分:1)
从anhtuannd的代码中,我使用Java 8进行了重构。它稍微慢一些。谢谢anthuannd。
static String mostFrequentWord(Collection<String> values) {
return values.stream()
.collect(Collectors.groupingBy(v -> v, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElseThrow(() -> new UnsupportedOperationException(
"Empty collection as values in multimap"));
}
答案 3 :(得分:0)
万一其他人觉得有帮助,这对我有帮助。
将其视为函数:给定k
(块计数),我们得到一些largeSum
。
此函数的反函数是什么?就是说,给定largeSum
,我们得到一个k
。此反函数在下面实现。
在solution()
中,我们不断将largeSum
的猜测插入反函数中,直到它返回练习中给出的k
。
为了加快猜测过程,我们使用二进制搜索。
public class Problem {
int SLICE_MAX = 100 * 1000 + 1;
public int solution(int blockCount, int maxElement, int[] array) {
// maxGuess is determined by looking at what the max possible largeSum could be
// this happens if all elements are m and the blockCount is 1
// Math.max is necessary, because blockCount can exceed array.length,
// but this shouldn't lower maxGuess
int maxGuess = (Math.max(array.length / blockCount, array.length)) * maxElement;
int minGuess = 0;
return helper(blockCount, array, minGuess, maxGuess);
}
private int helper(int targetBlockCount, int[] array, int minGuess, int maxGuess) {
int guess = minGuess + (maxGuess - minGuess) / 2;
int resultBlockCount = inverseFunction(array, guess);
// if resultBlockCount == targetBlockCount this is not necessarily the solution
// as there might be a lower largeSum, which also satisfies resultBlockCount == targetBlockCount
if (resultBlockCount <= targetBlockCount) {
if (minGuess == guess) return guess;
// even if resultBlockCount == targetBlockCount
// we keep searching for potential lower largeSum that also satisfies resultBlockCount == targetBlockCount
// note that the search range below includes 'guess', as this might in fact be the lowest possible solution
// but we need to check in case there's a lower one
return helper(targetBlockCount, array, minGuess, guess);
} else {
return helper(targetBlockCount, array, guess + 1, maxGuess);
}
}
// think of it as a function: given k (blockCount) we get some largeSum
// the inverse of the above function is that given largeSum we get a k
// in solution() we will keep guessing largeSum using binary search until
// we hit k given in the exercise
int inverseFunction(int[] array, int largeSumGuess) {
int runningSum = 0;
int blockCount = 1;
for (int i = 0; i < array.length; i++) {
int current = array[i];
if (current > largeSumGuess) return SLICE_MAX;
if (runningSum + current <= largeSumGuess) {
runningSum += current;
} else {
runningSum = current;
blockCount++;
}
}
return blockCount;
}
}
答案 4 :(得分:0)
我用python here写了100%的解决方案。结果为here。
记住:您正在搜索的是可能答案的集合,而不是数组A
在给出的示例中,他们正在搜索可能的答案。认为[5]为5是一个块的最小最大值。并将[2,1,5,1,2,2,2] 15作为一个块的最大值。
Mid =(5 + 15)// 2.一次切出10个块的总数不会超过3个。
将10-1设为上限,然后重试(5 + 9)// 2为7。一次切出7个块的总数不会超过3个。
将7-1设为较高,然后重试(5 + 6)// 2为5。一次切出5个区块将总共创建3个以上的区块。
将5 + 1设为较低,然后重试(6 + 6)// 2为5。一次切出6个块的总数不会超过3个。
因此,对一个块的总和施加的最低限制为6,这将允许分成3个块。