问题
I've came across some similar problems to this one in the past, and I still haven't got good idea how to solve this problem. Problem goes like this:
You are given an positive integer array with size n <= 1000 and k <= n which is the number of contiguous subarrays that you will have to split your array into. You have to output minimum m, where m = max{s[1],..., s[k]}, and s[i] is the sum of the i-th subarray. All integers in the array are between 1 and 100. Example :
Input: Output:
5 3 >> n = 5 k = 3 3
2 1 1 2 3
Splitting array into 2+1 | 1+2 | 3 will minimize the m.
My brute force idea was to make first subarray end at position i (for all possible i) and then try to split the rest of the array in k-1 subarrays in the best way possible. However, this is exponential solution and will never work.
So I'm looking for good ideas to solve it. If you have one please tell me.
Thanks for your help.
回答1:
You can use dynamic programming to solve this problem, but you can actually solve with greedy and binary search on the answer. This algorithm's complexity is O(n log d)
, where d
is the output answer. (An upper bound would be the sum of all the elements in the array.) (or O( n d )
in the size of the output bits)
The idea is to binary search on what your m
would be - and then greedily move forward on the array, adding the current element to the partition unless adding the current element pushes it over the current m
-- in that case you start a new partition. The current m
is a success (and thus adjust your upper bound) if the numbers of partition used is less than or equal to your given input k
. Otherwise, you used too many partitions, and raise your lower bound on m
.
Some pseudocode:
// binary search
binary_search ( array, N, k ) {
lower = max( array ), upper = sum( array )
while lower < upper {
mid = ( lower + upper ) / 2
// if the greedy is good
if partitions( array, mid ) <= k
upper = mid
else
lower = mid
}
}
partitions( array, m ) {
count = 0
running_sum = 0
for x in array {
if running_sum + x > m
running_sum = 0
count++
running_sum += x
}
if running_sum > 0
count++
return count
}
This should be easier to come up with conceptually. Also note that because of the monotonic nature of the partitions function, you can actually skip the binary search and do a linear search, if you are sure that the output d
is not too big:
for i = 0 to infinity
if partitions( array, i ) <= k
return i
回答2:
Dynamic programming. Make an array
int best[k+1][n+1];
where best[i][j]
is the best you can achieve splitting the first j
elements of the array int i
subarrays. best[1][j]
is simply the sum of the first j
array elements. Having row i
, you calculate row i+1
as follows:
for(j = i+1; j <= n; ++j){
temp = min(best[i][i], arraysum[i+1 .. j]);
for(h = i+1; h < j; ++h){
if (min(best[i][h], arraysum[h+1 .. j]) < temp){
temp = min(best[i][h], arraysum[h+1 .. j]);
}
}
best[i+1][j] = temp;
}
best[m][n]
will contain the solution. The algorithm is O(n^2*k), probably something better is possible.
Edit: a combination of the ideas of ChingPing, toto2, Coffee on Mars and rds (in the order they appear as I currently see this page).
Set A = ceiling(sum/k)
. This is a lower bound for the minimum. To find a good upper bound for the minimum, create a good partition by any of the mentioned methods, moving borders until you don't find any simple move that still decreases the maximum subsum. That gives you an upper bound B, not much larger than the lower bound (if it were much larger, you'd find an easy improvement by moving a border, I think).
Now proceed with ChingPing's algorithm, with the known upper bound reducing the number of possible branches. This last phase is O((B-A)*n), finding B
unknown, but I guess better than O(n^2).
回答3:
I have a sucky branch and bound algorithm ( please dont downvote me )
First take the sum of array and dvide by k, which gives you the best case bound for you answer i.e. the average A. Also we will keep a best solution seen so far for any branch GO ( global optimal ).Lets consider we put a divider( logical ) as a partition unit after some array element and we have to put k-1 partitions. Now we will put the partitions greedily this way,
Traverse the array elements summing them up until you see that at the next position we will exceed A, now make two branches one where you put the divider at this position and other where you put at next position, Do this recursiely and set GO = min (GO, answer for a branch ). If at any point in any branch we have a partition greater then GO or the no of position are less then the partitions left to be put we bound. In the end you should have GO as you answer.
EDIT: As suggested by Daniel, we could modify the divider placing strategy a little to place it until you reach sum of elements as A or the remaining positions left are less then the dividers.
回答4:
This is just a sketch of an idea... I'm not sure that it works, but it's very easy (and probably fast too).
You start say by putting the separations evenly distributed (it does not actually matter how you start).
Make the sum of each subarray.
Find the subarray with the largest sum.
Look at the right and left neighbor subarrays and move the separation on the left by one if the subarray on the left has a lower sum than the one on the right (and vice-versa).
Redo for the subarray with the current largest sum.
You'll reach some situation where you'll keep bouncing the separation between the same two positions which will probably mean that you have the solution.
EDIT: see the comment by @rds. You'll have to think harder about bouncing solutions and the end condition.
回答5:
My idea, which unfortunately does not work:
- Split the array in N subarrays
- Locate the two contiguous subarrays whose sum is the least
- Merge the subarrays found in step 2 to form a new contiguous subarray
- If the total number of subarrays is greater than k, iterate from step 2, else finish.
回答6:
If your array has random numbers, you can hope that a partition where each subarray has n/k is a good starting point.
From there
- Evaluate this candidate solution, by computing the sums
- Store this candidate solution. For instance with:
- an array of the indexes of every sub-arrays
- the corresponding maximum of sum over sub-arrays
- Reduce the size of the max sub-array: create two new candidates: one with the sub-array starting at index+1 ; one with sub-array ending at index-1
- Evaluate the new candidates.
- If their maximum is higher, discard
- If their maximum is lower, iterate on 2, except if this candidate was already evaluated, in which case it is the solution.
来源:https://stackoverflow.com/questions/8592143/need-idea-for-solving-this-algorithm-puzzle