Minimum transfer to make array equal

Viewed 322

This question is asked in the interview. I am still not able to find what should be right approach to attempt this problem.

Given an array = [7,2,2] find the minimum number of transfer required to make array elements almost equal. If this is not possible the larger elements should come to the left side.

In above example the final state of array would be [4,4,3] and the answer will be 2+ 1 =3. We are transfering 2 from 7 to first 2 and then we are transfering another 1 from 7 to 2.

If the input is [2,2,7] then the answer will be 4 since we need to keep bigger elements on the left side. final state = [4,4,3] 2 transfered from 7 to both 2 to make the final count as 4.

4 Answers

The minimum amount of transfers done 1 unit at a time is half the total amount by which the input differs from the desired array. "Almost equal" doesn't seem to mean any complication according to what you've given.

The solution is to imagine what the target array will be. This target array will depend only on the sum of the values in the original array, and the length of the array (which obviously must remain the same).

If the sum of the values is a multiple of the array length, then in the target array all values will be the same. If however there is a remainder, that remainder represents the number of array values that will be one more than some of the value(s) at the end of the array.

We don't actually have to store that target array. It is implicitly defined by the quotient and the remainder of the division of the sum by the array length.

The output of the function is the sum of differences with the actual input array value and the expected value at any array index. We should only count positive differences (i.e. transfers out of a value) as otherwise we would count transfers twice -- once on the outgoing side and again on the incoming side.

Here is an implementation in basic JavaScript:

function solve(arr) {
    // Sum all array values
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i];
    }

    // Get the integer quotient and remainder
    let quotient = Math.floor(sum / arr.length);
    let remainder = sum % arr.length;

    // Determine the target value until the remainder is completely consumed:        
    let expected = quotient + 1;

    // Collect all the positive differences with the expected value
    let result = 0;
    for (let i = 0; i < arr.length; i++) {
       // If we have consumed the remainder, reduce the expected value
       if (i == remainder) {
           expected = quotient;
       }
       let transfer = arr[i] - expected;
       // Only account for positive transfers to avoid double counting
       if (transfer > 0) {
           result += transfer;
       }
    }
    
    return result;
}

let array = [7,2,2];
console.log(solve(array)); // 6

Let's start form target array. What is it?

Having {7, 2, 2} we want to obtain {4, 4, 3}. So every item is at least 3 and some top items are 3 + 1 == 4. The algorithm is

let sum = sum(original)
let rem = sum(original) % length(original)  # here % stands for remainder

target[i] = sum / length(original) + (i < rem ? 1 : 0) 

Having original and target

original: 7 2 2
target:   4 4 3
transfer: 3 2 1 (6 in total)

note, that

  1. transfer[i] is just an absolute difference: abs(original[i] - target[i])
  2. we count each transfer twice: once we subtract and then we add.

So the answer is

sum(transfer[i]) / 2 == sum(abs(original[i] - target[i])) / 2 

Code (c#):

private static int Solve(int[] initial) {
  // Don't forget about degenerated cases
  if (initial is null || initial.Length <= 0)
    return 0;  

  int sum = initial.Sum();
  int rem = sum % initial.Length;

  int result = 0;

  for (int i = 0; i < initial.Length; ++i) 
    result += Math.Abs(sum / initial.Length + ((i < rem) ? 1 : 0) - initial[i]);
  
  return result / 2;  
}

Demo: (Fiddle)

    int[][] tests = new int[][] {
        new int[] {7, 2, 2},
        new int[] {2, 2, 7},
        new int[] {},
        new int[] {2, 2, 2},
        new int[] {1, 2, 3},
    };
    
    string report = string.Join(Environment.NewLine, tests
      .Select(test => $"[{string.Join(", ", test)}] => {Solve(test)}"));
    
    Console.Write(report);

Outcome:

[7, 2, 2] => 3
[2, 2, 7] => 4
[] => 0
[2, 2, 2] => 0
[1, 2, 3] => 1

Seems to me like a simple problem that can be solved with greedy approach.

Steps:

  1. Sum up the input array-elements S, divide by its length n. Lets say, the quotient is Q and remainder (mod) is R. Then, final array target will have 1st R elements with value = Q+1. Rest of the elements will be Q.
  2. Number of transfers will be half of the sum of absolute difference at each (corresponding) position in input and target arrays.

Example:

Input [7, 2, 2]
S=11 n=3 Q=11/3=3 R=11%3=2
Target [3+1, 3+1, 3]
Answer = (abs(7-4) + abs(2-4) + abs(2-3)) / 2 = 3
Related