I have the following problem:
You are given N counters, initially set to 0, and you have two possible operations on them:
A non-empty zero-indexed array A of M integers is given. This array represents consecutive operations:
For example, given integer N = 5 and array A such that:
A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4
the values of the counters after each consecutive operation will be:
(0, 0, 1, 0, 0)
(0, 0, 1, 1, 0)
(0, 0, 1, 2, 0)
(2, 2, 2, 2, 2)
(3, 2, 2, 2, 2)
(3, 2, 2, 3, 2)
(3, 2, 2, 4, 2)
The goal is to calculate the value of every counter after all operations.
I did the following solution but it runs at O(NK) where K = length of array A.
public int[] solution(int N, int[] A) {
int[] result = new int[N];
int maximum = 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)
{
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;
}
}
return result;
}
Could anyone show me how this can be better done with O(N + K) where K is the length of array A? Sorry for may terrible coding, I am doing these exercises to improve my programming. Thanks!
This is what I came up with, but I am not sure if it works 100%:
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;
}
Remember:
"Making your code readable is as important as making it executable."
-- Robert C Martin
Even when trying to solve a hard problem...
So trying to achieve a better readability I've created a class to encapsulate the counters array and its operations (Law of Demeter). Sadly my first solution got only 60% in the performance test, so at the cost of a bit of readability I've improved it with a smarter solution and finally got 100%.
Here are my two implementations with comments:
//I didn't refactored the names of the variables N and A
//to maintain it aligned with the question description
public int[] solution(int N, int[] A)
{
var counters = new Counters(N);
for (int k = 0; k < A.Length; k++)
{
if (A[k] <= N)
counters.IncreaseCounter(A[k]);
else
counters.MaxAllCounters();
}
return counters.ToArray();
}
public class Counters
{
private int[] counters;
private int greaterValueInCounter = 0;
public Counters(int length)
{
counters = new int[length];
}
public void MaxAllCounters()
{
for (int i = 0; i < counters.Length; i++)
{
counters[i] = greaterValueInCounter;
}
}
public void IncreaseCounter(int counterPosition)
{
//The counter is one-based, but our array is zero-based
counterPosition--;
//Increments the counter
counters[counterPosition]++;
if (counters[counterPosition] > greaterValueInCounter)
greaterValueInCounter = counters[counterPosition];
}
//The counters array is encapsuled in this class so if we provide external
//acess to it anyone could modify it and break the purpose of the encapsulation
//So we just exposes a copy of it :)
public int[] ToArray()
{
return (int[])counters.Clone();
}
}
Codility result
Note the beauty of the encapsulation: to improve the algorithm I just have to edit some methods of the Counters
class without changing a single character on the solution
method.
Methods edited in the Counters
class:
IncreaseCounter()
MaxAllCounters()
ToArray()
Final code:
//Exactly the same code
public int[] solution(int N, int[] A)
{
var counters = new Counters(N);
for (int k = 0; k < A.Length; k++)
{
if (A[k] <= N)
counters.IncreaseCounter(A[k]);
else
counters.MaxAllCounters();
}
return counters.ToArray();
}
public class Counters
{
private int[] counters;
private int greaterValueInCounter = 0;
private int currentEquilibratedScore = 0;
public Counters(int length)
{
counters = new int[length];
}
public void MaxAllCounters()
{
//We don't update the entire array anymore - that was what caused the O(N*M)
//We just save the current equilibrated score value
currentEquilibratedScore = greaterValueInCounter;
}
public void IncreaseCounter(int counterPosition)
{
//The counter is one-based, but our array is zero-based
counterPosition--;
//We need to add this "if" here because with this new solution the array
//is not always updated, so if we detect that this position is lower than
//the currentEquilibratedScore, we update it before any operation
if (counters[counterPosition] < currentEquilibratedScore)
counters[counterPosition] = currentEquilibratedScore + 1;
else
counters[counterPosition]++;
if (counters[counterPosition] > greaterValueInCounter)
greaterValueInCounter = counters[counterPosition];
}
//The counters array is encapsuled in this class so if we provide external
//acess to it anyone could modify it and break the purpose of the encapsulation
//So we just exposes a copy of it :)
public int[] ToArray()
{
//Now we need to fix the unupdated values in the array
//(the values that are less than the equilibrated score)
for (int i = 0; i < counters.Length; i++)
{
if (counters[i] < currentEquilibratedScore)
counters[i] = currentEquilibratedScore;
}
return (int[])counters.Clone();
}
}
Codility result
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With