Search code examples
arraysalgorithmsplitpartition-problem

Need idea for solving this algorithm puzzle


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.


Solution

  • 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