7

An array is manipulated k times so that each time the max value is devided by 2 and rounded up. I need to find its minimum sum after these k manipulations. k and all numbers in array num > 1. The method minSum recieves an array called num and an integer k. The brute Python code that works for me with very bad time complexity is:

import bisect

def minSum(num, k):
    num.sort()
    lastIndex = len(num)-1
    for i in range(k):
        if num[lastIndex]==1:
            return sum(num)

        num[lastIndex]= math.ceil(num[lastIndex]/2)
        lastNumber = num[len(num)-1]
        num.pop()
        bisect.insort(num, lastNumber)

    return sum(num)

I've also tried to sort the array first but then every algorithm I tried failed. could you think of a good algorithm with good time complexity? preferably without using special imports.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
Ohad Marbach
  • 91
  • 1
  • 1
  • 3
  • I think you can reduce the number of iterations `k` to `k/2`. If at a given iteration you take the largest and the second largest value and reduce both of them to half of their values in the same iteration. – Rahil Hastu Feb 26 '19 at 12:55
  • @RahilHastu That won't work if the largest value is more than twice as large as the second largest value. – blhsing Feb 26 '19 at 12:56
  • @Chris that didn't cross my mind. Thankyou – Rahil Hastu Feb 26 '19 at 12:59
  • @Chris I don't understand, isn't it clear that the method recievs an array called num and an integer k? I also mentioned that thay are both larger than 1. this question is not as easy as it seems at first sight. I searched for a solution all day and tried various algorithms. – Ohad Marbach Feb 26 '19 at 13:07
  • @RoryDaulton Daulton yes I wrote this "k and all numbers in array num > 1" – Ohad Marbach Feb 26 '19 at 13:09
  • By "without using special imports" do you mean that *regular* imports, such as those from the standard library, are acceptable? – Rory Daulton Feb 26 '19 at 13:14
  • Please indent your code properly (SO gives you code formatting when you indent the code 4 spaces, a shortcut for doing this is to select the code, then press the button marked `{}`). – thebjorn Feb 26 '19 at 19:00

5 Answers5

11

The standard library (ie. no special imports) comes with a heapq module that makes the algorithm O(3n + k*(2 lg n)) => O(lg n):

import math
import heapq

def minSum(num, k):
    heap = [-n for n in num]  # negate values for max-heap
    heapq.heapify(heap)

    for i in range(k):
        # Find max value
        max_value = heapq.heappop(heap)
        # Change max value to rounded half
        # use floor since we've negated the values
        heapq.heappush(heap, math.floor(max_value/2))

    # Calculate minimum sum
    return -sum(heap)  # reverse polarity again

update 1: (from comment by @raury-daulton) combine pop/push, O(3n + k*(lg n)) => O(lg n):

def minSum(num, k):
    heap = [-n for n in num]  # negate values for max-heap
    heapq.heapify(heap)

    for i in range(k):
        max_value = heap[0]
        heapq.heapreplace(heap, math.floor(max_value/2))

    # Calculate minimum sum
    return -sum(heap)  # reverse polarity again

update 2: use a max heap directly O(2n + k*(lg n)) => O(lg n)

def heapreplace_max(heap, item):
    "We need to implement this ourselves from primitives."
    returnitem = heap[0]
    heap[0] = item
    heapq._siftup_max(heap, 0)
    return returnitem


def minSum(num, k):
    heap = num   # alias for consistency with other samples
    heapq._heapify_max(heap)  # make it a max heap (no negation hack)

    for i in range(k):
        max_value = heap[0]
        heapreplace_max(heap, math.ceil(max_value/2))

    return sum(heap)

update 3: final optimizations (this requires the input array to be an array of ints).

def minSum(num, k):
    heap = num   # alias for consistency with other samples
    heapq._heapify_max(heap)

    for i in range(k):
        max_value = heap[0]
        if max_value == 1:  # exit early if there is no more work
            break
        new_val = (max_value >> 1) + (max_value & 1)  # same as, but much faster than math.ceil(max_value/2)
        heapreplace_max(heap, new_val)

    return sum(heap)

The upper bound on k is sum(math.floor(math.log(v, 2)+1) for v in num) (i.e. the total number of bits required to represent all the input numbers). It may be faster to precompute the loop variable than to have if max_value == 1: in the loop, i.e.:

for i in range(min(k, int(sum(floor(log(v, 2)+1) for v in num)))):
    max_value = heap[0]
    new_val = ...

but I haven't actually measured this.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • I think `-sum(heap)` would make it even faster :) – Chris Feb 26 '19 at 13:36
  • @Chris absolutely, fixing.. ;-) – thebjorn Feb 26 '19 at 13:37
  • PS: there is an undocumented api in `heapq` for creating max-heaps directly (`heapq._heapify_max` and friends). I'll leave that for the adventurous. – thebjorn Feb 26 '19 at 13:39
  • You could speed up this code by using `heap[0]` to get `max_value` (an order `1` operation) and `heapreplace` to "change max value to rounded half" (an order `log(n)` operation). That changes the two order `log(n)` operations your code does to only one such operation, and thus could cut the run time of that loop in about half. – Rory Daulton Feb 26 '19 at 13:58
  • I editted my code in the question, it is better now but still not good enough in time complexity.... why did your code work and this one didn't? – Ohad Marbach Feb 26 '19 at 15:15
  • @OhadMarbach: Just what time complexity do you need? And what code do you mean by "this one"--the one in your question? – Rory Daulton Feb 26 '19 at 18:14
  • The time complexity of your current solution (using `bisect.insort`) is O(sort + chance of exiting early from k * O(bisect.insert)). The sort is O(n lg n), let's say the probability (P) of exiting early is P(k), and of course O(bisect.insort) is O(find insertion point + shift elements) == O(lg n + n/2) => O(n), combining all that give O(n lg n + P(k) * n) which is at least O(n lg n) but maybe as bad as O(k * n). – thebjorn Feb 26 '19 at 19:13
  • @RoryDaulton thanks for the tip about heapreplace. I've updated my answer. You're right that in theory it could cut the run time in half -- and in theory there isn't a difference between theory and practice ;-) I haven't measured the actual difference (since that's much more difficult). – thebjorn Feb 26 '19 at 19:17
  • Time Complexity here will be O(n + klg(n)), no ? How can it be O(lgn) – Kartavya Ramnani Oct 02 '20 at 07:23
3

Each loop of your code goes through the complete array twice, so your algorithm is of execution order kn, where n is the length of your array. You can speed this up by not taking the sum after every operation, since your operation "divide by 2 and round up" cannot decrease the sum. (This depends on the fact that none of the array values may be negative.) Take the sum only after applying all k operations. This should cut your execution roughly in half.

Another, more significant optimization is to avoid the search through the array for the maximum value. I suggest you use a heapq (priority queue) structure. You store the negatives of the array values, so the minimum of the heapq is the negative of the maximum of your array. Finding the max array value is then an order 1 operation, and changing it is an order log(n) operation. Use the heapreplace method to do the replacing, to get maximum speed. Making the array into a heap using heapify is the only order n operation, but that happens only once.

I'll add code later today if I get the time. This algorithm does require one import, namely of heapq, but that is in the standard library so does not really count as a "special import."

Rory Daulton
  • 21,934
  • 6
  • 42
  • 50
  • As I said K>1 and num[i]>1 so no ned to deal with negatives. does this give a simpler solution without heapq? – Ohad Marbach Feb 26 '19 at 13:25
  • The `heapq` module is pretty simple, so you could implement the relevant parts of the code for yourself. I have done this in other languages (but use `heapq` in Python). Is that what you want? That would avoid the complication of storing the negatives of the array values (since you could code a max heap rather than heapq's min heap), so it is fairly simple, but I'm not sure that you would call it "simple." I do not have to time to show such a solution until this evening. – Rory Daulton Feb 26 '19 at 13:28
  • I am a beginner so i'll need to learn about heapq to understand your code as for now I'm afraid it is like chinese to me. yes, it is needed for python. – Ohad Marbach Feb 26 '19 at 13:58
  • thebjorn's code is very close to what I would have written. The only difference I would do, I explain in my comment to that answer. – Rory Daulton Feb 26 '19 at 14:10
0

Solution in O(n+10000)=O(n) is possible, in each time to minimize the final answer you should choose the maximal x value, you could use say a heap to solve the problem, but since the numbers are small a counting idea solves the problem in better time.

 int minSum(vector<int>v, int k){
// in time O(n+10000)
    
    vector<int>C(10001,0);
    
    for(int x:v)C[x]++;
 
    int ans=0;
    for(int i=10000;i>=1;i--){
        int count=min(C[i],k);
        C[i]-=count;
        C[(i+1)/2]+=count;
        k-=count;
        ans+=i*C[i];
    }
        
    return ans;
}


int main(void){
    
    cout<<minSum({10,20,7},4)<<endl;
    cout<<minSum({1,2},1)<<endl;
    cout<<minSum({1,2,1},2)<<endl;
    return 0;    
}
// output 14, 2, 3
Nimish goel
  • 2,561
  • 6
  • 27
  • 42
0

this one in js?

s= [89,62,3,4,55,6]
console.log(s.length)
n=3

for(k=0; k < n; k++){
    max = s[0]
    j=0
  for(i=0; i < s.length; i++){
   if(max < s[i]) {
     max = s[i]
      j=i
   }
   if(i === s.length-1){
     s[j]= s[j]/2
   }
  }
}

console.log(s)
Selva Ganapathi
  • 982
  • 10
  • 21
-1
Java : Minimum sum K operations 
public static int minimumSum(int[] A, int k) {
              int sum = 0;
              Arrays.sort(A);
              for (int i = 0; i < k; i++) {

                     int len = A.length;

                     if (A[len - 1] % 2 == 0) {

                           A[len - 1] = A[len - 1] / 2;

                     } else {

                           A[len - 1] = (int) Math.ceil((double) A[len - 1] / 2);

                     }
                     Arrays.sort(A);
              }

              for (int i = 0; i < A.length; i++) {
                     sum += A[i];
              }
              return sum;

       }
Laxman
  • 1
  • 1
    Please post context around your answer, instead of just code. Explain how it answers the question. Also, the question is in Python, yet your code is Java. – ChrisMM Dec 31 '19 at 20:05