4

I have wanted to try some challenges on Codility and started from beginning. All assignments were relatively easy up to the one called MaxCounters. I do not believe that this one is especially hard although it is the first one marked as not painless.

I have read the task and started coding in C# language:

public static int[] maxPart(int N, int[] A){
    int[] counters = new int[N];
    for(int i = 0; i < A.Length; i++){
        for(int j = 0; j < counters.Length; j++){
            if(A[i] == counters[j] && (counters[j] >= 1 && counters[j] <= N )){
                counters [j] = counters [j] + 1;
            }
            if(A[i] == N + 1 ){
                int tmpMax = counters.Max ();
                for(int h = 0; h < counters.Length; h++){
                    counters [h] = tmpMax;
                }
            }
        }
    }
    return counters;
}

Having 3 loops of course makes it really slow, but lets leave it for later. My concern is how I understood this like this and all the other people see it like on this question here.

From the assignment's description.

it has 2 actions:

  • increase(X) − counter X is increased by 1,
  • max counter − all counters are set to the maximum value of any counter.

which occur under conditions:

  • if A[K] = X, such that 1 ≤ X ≤ N, then operation K is increase(X),
  • if A[K] = N + 1 then operation K is max counter.

Both conditions are stated in the code above. Obviusly it is wrong but I am confused, and I do not know how could I understand it differently.

Why is this code wrong, what am I missing from task description?

One of the top rated answers looks like this:

public int[] solution(int N, int[] A) {
    int[] result = new int[N];
    int maximum = 0;
    int resetLimit = 0;

    for (int K = 0; K < A.Length; K++)
    {
        if (A[K] < 1 || A[K] > N + 1)
            throw new InvalidOperationException();

        if (A[K] >= 1 && A[K] <= N)
        {
            if (result[A[K] - 1] < resetLimit) {
                result[A[K] - 1] = resetLimit + 1;
            } else {
                result[A[K] - 1]++;
            }

            if (result[A[K] - 1] > maximum)
            {
                maximum = result[A[K] - 1];
            }
        }
        else
        {
            // inefficiency here
            //for (int i = 0; i < result.Length; i++)
            //    result[i] = maximum;
            resetLimit = maximum;
        }
    }

    for (int i = 0; i < result.Length; i++)
        result[i] = Math.max(resetLimit, result[i]);

    return result;
}

This code results with 100% on Codility.

Question:

I would like to know how the author knew from the task to use result[A[K] - 1]? What would resetLimit represent?

Maybe I completely misunderstood the question due to my English I am not sure. I just can not go over it.

EDIT:

Based on my code provided, how did I misunderstood the assignment? Generally I am asking for explanation of the problem. Whether to explain what needs to be done, or take the code as correct result and provide and explanation why is this done this way?

Community
  • 1
  • 1
eomeroff
  • 9,599
  • 30
  • 97
  • 138

7 Answers7

2

In my opinion you somehow mixed the index of the counter (values in A) and the value of the counter (values in counter). So there is no magic in using A[i]-1 - it is the value X from the problem description (adjusted to 0-based index).

My naive approach would be, the way I understood the problem (I hope it makes clear, what your code is doing wrong):

public static int[] maxPart(int N, int[] A){
    int[] counters = new int[N];
    for(int i = 0; i < A.Length; i++){
        int X=A[i];
        if(X>=1 && X<=N){ // this encodes increment(X), with X=A[i] 
             counters [X-1] = counters [X-1] + 1; //-1, because our index is 0-based  
         }
         if(X == N + 1 ){// this encodes setting all counters to the max value
             int tmpMax = counters.Max ();
             for(int h = 0; h < counters.Length; h++){
                    counters [h] = tmpMax;
                }
            }
        }
    }
    return counters;
}

Clearly, this would be too slow as the complexity isO(n^2) with n=10^5 number of operations (length of the array A), in the case of the following operation sequence:

max counter, max counter, max counter, ....

The top rated solution solves the problem in a lazy manner and does not update all values explicitly every time a max counter operation is encountered, but just remembers which minimal value all counters must have after this operation in resetLimit. Thus, every time he must increment a counter, he looks up whether its value must be updated due to former max counter operations and makes up for all max counter operation he didn't execute on this counter

if(result[A[K] - 1] < resetLimit) {
                result[A[K] - 1] = resetLimit + 1;
            }

His solution runs in O(n) and is fast enough.

ead
  • 32,758
  • 6
  • 90
  • 153
1

Here is my solution in JavaScript.

const maxCounters = (N, A) => {
  for (let t = 0; t < A.length; t++) {
    if (A[t] < 1 || A[t] > N + 1) {
      throw new Error('Invalid input array A');
    }
  }
  let lastMaxCounter = 0; // save the last max counter is applied to all counters
  let counters = []; // counters result
  // init values by 0
  for (let i = 0; i < N; i++) {
    counters[i] = 0;
  }
  let currentMaxCounter = 0; // save the current max counter each time any counter is increased
  let maxApplied = false;
  for (let j = 0; j < A.length; j++) {
    const val = A[j];
    if (1 <= val && val <= N) {
      if (maxApplied && counters[val - 1] < lastMaxCounter) {
        counters[val - 1] = lastMaxCounter;
      }
      counters[val - 1] = counters[val - 1] + 1;
      if (currentMaxCounter < counters[val - 1]) {
        currentMaxCounter = counters[val - 1];
      }
    } else if (val === N + 1) {
      maxApplied = true;
      lastMaxCounter = currentMaxCounter;
    }
  }
  // apply the lastMaxCounter to all counters
  for (let k = 0; k < counters.length; k++) {
    counters[k] = counters[k] < lastMaxCounter ? lastMaxCounter : counters[k];
  }
  return counters;
};
1

Here is C# solution give me 100% score

public int[] solution(int N, int[] A) {
        int[] operation = new int[N];
            int max = 0, globalMax = 0;
            foreach (var item in A)
            {
                if (item > N)
                {
                    globalMax = max;
                }
                else
                {
                    if (operation[item - 1] < globalMax)
                    {
                        operation[item - 1] = globalMax;
                    }
                    operation[item - 1]++;
                    if (max < operation[item - 1])
                    {
                        max = operation[item - 1];
                    }
                }
            }
            for (int i = 0; i < operation.Length; i++)
            {
                if (operation[i] < globalMax)
                {
                    operation[i] = globalMax;
                }
            }

            return operation;
    }
Andy
  • 332
  • 1
  • 13
0

Here is a pretty elegant soulution in Swift:

public func solution(_ N : Int, _ A : inout [Int]) -> [Int] {
    var globalMax = 0
    var currentMax = 0
    var maximums: [Int: Int] = [:]

    for x in A {
        if x > N {
            globalMax = currentMax
            continue
        }
        let newValue = max(maximums[x] ?? globalMax, globalMax) + 1
        currentMax = max(newValue, currentMax)
        maximums[x] = newValue
    }

    var result: [Int] = []
    for i in 1...N {
        result.append(max(maximums[i] ?? globalMax, globalMax))
    }

    return result
}
Frane Poljak
  • 2,315
  • 23
  • 25
0

Try this Java snippet. Its more readable and neater, you don't need to worry about bounds check and might evacuate your first findings related to the more efficient approach you have found, btw the max is on the main forloop not causing any overhead.

public final int[] solution(int N, int[] A)
 {
    int condition = N + 1;
    int currentMax = 0;
    int lastUpdate = 0;
    int[] counters = new int[N];

    for (int i = 0; i < A.length; i++)
        {
        int currentValue = A[i];
            if (currentValue == condition)
                {
                    lastUpdate = currentMax;
                }
                else
                {
                    int position = currentValue - 1;
                    if (counters[position] < lastUpdate)
                    {
                        counters[position] = lastUpdate + 1;
                    }
                    else
                    {
                        counters[position]++;
                    }

                    if (counters[position] > currentMax)
                    {
                        currentMax = counters[position];
                    }
                }

            }

            for (int i = 0; i < N; i++)
            {
                if (counters[i] < lastUpdate)
                {
                    counters[i] = lastUpdate;
                }
            }
            return counters;
 }
jmvcollaborator
  • 2,141
  • 1
  • 6
  • 17
0

Inspired by Andy's solution, here is a solution in Python that is O(N + M) and gets a score of 100. The key is to avoid the temptation of updating all the counters every time A[K] > 5. Instead you keep track of a global max and reset an individual counter to global max just before you have to increment it. At the end, you set the remaining un-incremented counters to global max. See the comments in the code below:

def solution(N,A):
    max = 0
    global_max = 0
    counters = [0] * N
    for operation in  A:
        if operation > N:
            #don't update counters.
            #Just keep track of global max until you have to increment one of the counters.
            global_max = max
        else:
            #now update the target counter with global max
            if global_max > counters[operation - 1]:
                counters[operation - 1] = global_max
            #increment the target counter
            counters[operation - 1] += 1
            #update max after having incremented the counter
            if counters[operation - 1] > max:
                max = counters[operation - 1]

    for i in range(N):
        #if any counter is smaller than global max, it means that it was never
        #incremented after the global_max was reset.  Its value can now be updated
        #to global max.
        if counters[i] < global_max:
            counters[i] = global_max

    return counters
pakpe
  • 5,391
  • 2
  • 8
  • 23
0

Here's a C# solution that gave me 100% score. The idea is to simply not update the max counters on the spot but rather do it when you actually get to that counter, and then even out any counters that were not set to the max in another loop.


class Solution
{
    public int[] solution(int N, int[] A)
    {
        var result = new int[N];
        var latestMax = 0;
        var currentMax = 0;
        for (int i = 0; i < A.Length; i++)
        {
            var currentValue = A[i];

            if (currentValue >= 1 && currentValue <= N)
            {
                if (result[currentValue - 1] < currentMax)
                {
                    result[currentValue - 1] = currentMax;
                }
                result[currentValue - 1]++;
                if (result[currentValue - 1] > latestMax)
                {
                    latestMax = result[currentValue - 1];
                }
            }
            else if (currentValue == N + 1)
            {
                currentMax = latestMax;
            }
        }

        for (int i = 0; i < result.Length; i++)
        {
            if (result[i] < currentMax)
            {
                result[i] = currentMax;
            }
        }
        return result;
    }
}