I have the following problem:
You are given N counters, initially set to 0, and you have two possible operations on them:
Ruby Codility Code that got 100/100
def solution(a)
if a.length < 3
0
end
a.sort!
for i in 2..a.length - 1
if (a[i-2] + a[i-1]) > a[i]
return 1
end
end
0
end
Ruby 100%
def solution(n, a)
max = 0
offsets = a.inject(Hash.new(max)) do |acc, el|
next Hash.new(max) if el == n+1
acc[el] +=1
max = acc[el] if max < acc[el]
acc
end
(1..n).map{|i| offsets[i]}
end
Java, 100%/100%
public int[] solution(int N, int[] A) {
int[] counters = new int[N];
int currentMax = 0;
int sumOfMaxCounters = 0;
boolean justDoneMaxCounter = false;
for (int i = 0; i < A.length ; i++) {
if (A[i] <= N) {
justDoneMaxCounter = false;
counters[A[i]-1]++;
currentMax = currentMax < counters[A[i]-1] ? counters[A[i]-1] : currentMax;
}else if (!justDoneMaxCounter){
sumOfMaxCounters += currentMax;
currentMax = 0;
counters = new int[N];
justDoneMaxCounter = true;
}
}
for (int j = 0; j < counters.length; j++) {
counters[j] = counters[j] + sumOfMaxCounters;
}
return counters;
}
Same principle as everybody scoring 100% really, it is just that I find this version easier to read (and it is probably only because I wrote it).
using System;
using System.Linq;
class Solution
{
public int[] solution(int N, int[] A)
{
var currentMax = 0;
var resetValue = 0;
var counters = Enumerable.Range(1, N).ToDictionary(i => i, i => 0);
foreach (var a in A)
{
if (a == N + 1) resetValue = currentMax;
else
{
counters[a] = Math.Max(counters[a], resetValue) + 1;
currentMax = Math.Max(currentMax, counters[a]);
}
}
return counters.Values.Select(v => Math.Max(v,resetValue)).ToArray();
}
}
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
A 100/100 solution in php
function solution($N, $A){
$cond = $N + 1;
$cur_max = 0;
$last_upd = 0;
$cnt_arr = array();
$cnt = count($A);
for($i = 0; $i < $cnt; $i++){
$cur = $A[$i];
if($cur == $cond){
$last_upd = $cur_max;
}
else{
$pos = $cur - 1;
if(!isset($cnt_arr[$pos])){
$cnt_arr[$pos] = 0;
}
if($cnt_arr[$pos] < $last_upd){
$cnt_arr[$pos] = $last_upd + 1;
}
else{
$cnt_arr[$pos] ++;
}
if($cnt_arr[$pos] > $cur_max){
$cur_max = $cnt_arr[$pos];
}
}
}
for($i = 0; $i < $N; $i++){
if(!isset($cnt_arr[$i])){
$cnt_arr[$i] = 0;
}
if($cnt_arr[$i] < $last_upd){
$cnt_arr[$i] = $last_upd;
}
}
return $cnt_arr;
}